질문

질문

16진수 포인트를 사용하여 일부 16진수 숫자를 처리하고 싶지만 dc정밀도 문제가 발생합니다. 예를 들어, 아래에서는 둘 다 16진수로 F423F.FD곱 합니다 100. 예상되는 대답은 입니다 F423FFD. 대신에 제공되는 것은 F423FFA.E1가깝지만 반올림 후에도 충분히 정확하지 않습니다.

$ dc
16 d i o F423F.FD 100 * p
F423FFA.E1

dc나는 이것이 무한정밀도 계산기이고 결코 큰 숫자가 아니라는 것을 읽었습니다 . 내가 뭐 잘못 했어요?

답변해 주셔서 감사합니다. 문제를 고려하여 dc나는 총알을 깨물고 다른 기초의 실수를 구문 분석하기 위해 나만의 파서를 작성했습니다. 누구든지 코드에 관심이 있다면 여기에 게시할 수 있습니다.

답변1

10진수 표기법( dc변환용)으로 표현하면 999999.98(버림)×256에 해당하며,255999994.88(16진수 F423FFA.E1)

따라서 차이점은 다음 dc과 같은 반올림 동작에서 발생합니다. 256 × (999999 + 253 ¼ 256)(255999997 제공)을 계산하는 대신 253 ¼ 256을 반올림하여 결과를 곱합니다.

dc평상복정밀 계산기는 사용자가 원하는 정밀도로 계산할 수 있지만 그것이 무엇인지 알려주어야 함을 의미합니다. 기본적으로 정밀도는 0입니다. 즉, 나누기는 정수 값만 생성하는 반면 곱셈은 입력의 자릿수를 사용함을 의미합니다. 정밀도를 설정하려면 다음을 사용하십시오 k(정밀도는 입력 또는 출력 기준에 관계없이 항상 소수점 숫자로 표현된다는 점을 기억하십시오).

10 k
16 d i o
F423FFD 100 / p
F423F.FD0000000
100 * p
F423FFD.000000000

(10진수로 1 ¼ 256을 표현하는 데 필요한 정밀도는 8자리이면 충분합니다.)

답변2

원래 숫자를 인쇄하면 반올림된 것으로 표시됩니다.

$ dc <<<'16 d i o F423F.FD p'
F423F.FA

더 나은 정밀도를 위해 후행 0을 많이 추가하여 이 문제를 해결할 수 있습니다.

$ dc <<<'16 d i o F423F.FD000000 100 * p'
F423FFD.0000000

답변3

질문

문제는 dc(및 bc)가 숫자 상수를 이해하는 방식에 있습니다.
예를 들어 값(16진수) 0.3(1로 나눈 값)은 다음과 가까운 값으로 변환됩니다.0.2

$ dc <<<"20k 16 d i o 0.3 1 / p"
.199999999999999999999999999

실제로 간단한 상수도 0.3변경되었습니다.

$ dc <<<"20 k 16 d i o     0.3     p"
.1

이상하게 보일 수도 있지만 그렇지 않습니다(자세한 내용은 나중에 설명).
0을 더 추가하면 답이 올바른 값에 더 가까워집니다.

$ dc <<<"20 k 16 d i o     0.30     p"
.2E

$ dc <<<"20 k 16 d i o     0.300     p"
.2FD

$ dc <<<"20 k 16 d i o     0.3000     p"
.3000

마지막 값은 정확하며 0이 몇 개 추가되더라도 정확한 상태로 유지됩니다.

$ dc <<<"20 k 16 d i o     0.30000000     p"
.3000000

이 문제는 BC에도 존재합니다.

$ bc <<< "scale=20; obase=16; ibase=16;    0.3 / 1"
.19999999999999999

$ bc <<< "scale=20; obase=16; ibase=16;    0.30 / 1"
.2E147AE147AE147AE

$ bc <<< "scale=20; obase=16; ibase=16;    0.300 / 1"
.2FDF3B645A1CAC083

$ bc <<< "scale=20; obase=16; ibase=16;    0.3000 / 1"
.30000000000000000

각 숫자?

부동 소수점 숫자의 경우 매우 비직관적인 사실은 필요한 비트 수(점 뒤)가 이진수 수(역시 점 뒤)와 동일하다는 것입니다. 이진수 0.101은 십진수 0.625와 정확히 같습니다. 이진수 0.0001110001은 (정확히) 0.1103515625(십진수 10자리) 와 같습니다.

$ bc <<<'scale=30;obase=10;ibase=2; 0.101/1; 0.0001110001/1'; echo ".1234567890"
.625000000000000000000000000000
.110351562500000000000000000000
.1234567890

또한 2^(-10)과 같은 부동 소수점 숫자의 경우 이진수에는 단 하나의 (설정된) 비트만 있습니다.

$ bc <<<"scale=20; a=2^-10; obase=2;a; obase=10; a"
.0000000001000000000000000000000000000000000000000000000000000000000
.00097656250000000000

이진수 자릿수 .0000000001(10) .0009765625는 십진수 자릿수(10)와 같습니다. 이것은 다른 진수에는 해당되지 않을 수 있지만, 진수 10은 dc와 bc의 숫자를 내부적으로 표현한 것이므로 우리가 실제로 신경써야 할 유일한 진수입니다.

수학적 증명은 이 답변의 끝에 있습니다.

BC 규모

내장 함수 scale()bc를 사용하여 점 뒤의 자릿수를 계산할 수 있습니다.

$ bc <<<'obase=16;ibase=16; a=0.FD; scale(a); a; a*100'
2
.FA
FA.E1

그림과 같이 2자리 숫자로는 상수를 표현하기에는 부족합니다 0.FD.

또한 단순히 점 뒤에 사용된 문자 수를 세는 것은 숫자 비율을 보고(및 사용)하는 데 매우 잘못된 방법입니다. 숫자의 소수 자릿수(어떤 진법으로든 표현됨)는 필요한 자릿수를 계산해야 합니다.

16진수 부동 소수점 숫자의 이진수입니다.

우리 모두 알고 있듯이 각 16진수는 4비트를 사용합니다. 따라서 소수점 뒤의 각 16진수에는 4개의 이진수가 필요하며, 위에서 언급한 (이상한?) 사실로 인해 4개의 십진수도 필요합니다.

따라서 이와 같은 숫자를 0.FD올바르게 표시하려면 십진수 8자리가 필요합니다.

$ bc <<<'obase=10;ibase=16;a=0.FD000000; scale(a);a;a*100'
8
.98828125
253.00000000

0을 더하다

수학은 간단합니다(16진수의 경우).

  • h포인트 뒤의 16진수 자릿수( )를 셉니다.
  • 4를 곱합니다 h.
  • 0을 추가합니다 h×4 - h = h × (4-1) = h × 3 = 3×h.

쉘 코드에서(sh의 경우):

a=F423F.FD
h=${a##*.}
h=${#h}
a=$a$(printf '%0*d' $((3*h)) 0)
echo "$a"

echo "obase=16;ibase=16;$a*100" | bc

echo "20 k 16 d i o $a 100 * p" | dc

인쇄됩니다(dc와 bc 모두에서 정확함).

$  sh ./script
F423F.FD000000
F423FFD.0000000
F423FFD.0000000

내부적으로 bc(또는 dc)는 3*h16진수 부동 소수점 숫자를 내부 10진수 표현으로 변환하기 위해 필요한 자릿수를 위에서 계산된 숫자( )와 일치시킬 수 있습니다. 또는 다른 진수의 다른 함수(그러한 다른 진수의 자릿수는 진수 10(bc 및 dc의 내부)에 비해 유한하다고 가정). 2i (2,4,8,16,...) 및 5,10 등이 있습니다 .

POSIX

posix 사양에는 다음과 같이 명시되어 있습니다(bc의 경우 dc의 기반임).

입출력 기수에 관계없이 내부 계산은 지정된 소수 자릿수까지 10진수로 이루어져야 합니다.

그러나 "...지정된 소수 자릿수"는 "내부 소수 계산"에 영향을 주지 않고 "...수치 상수를 나타내는 데 필요한 소수 자릿수"(위와 같음)로 이해될 수 있습니다.

왜냐하면:

bc <<<'scale=50;obase=16;ibase=16; a=0.FD; a+1'
1.FA

bc는 위에서 설정한 50("지정된 소수 자릿수")을 실제로 사용하지 않습니다.

변환은 나눌 때만 발생합니다. (2의 소수점을 사용하여 상수를 읽은 다음 0.FD50자리로 확장하기 때문에 여전히 올바르지 않습니다.)

$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD/1; a'
.FAE147AE147AE147AE147AE147AE147AE147AE147A

그러나 이는 정확합니다.

$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD000000/1; a'
.FD0000000000000000000000000000000000000000

마찬가지로 숫자 문자열(상수)을 읽으려면 올바른 자릿수를 사용해야 합니다.


수학적 증명

두 단계로:

이진 분수는 a/2 n 으로 쓸 수 있습니다.

이진 분수는 2의 음수 거듭제곱의 유한합입니다.

예를 들어:

= 0.00110101101 = 
= 0. 0     0      1     1      0      1     0      1      1     0       1

= 0 + 0×2 -1 + 0×2 -2 + 1×2 -3 + 1 ×2 -4 + 0×2 -5 + 1×2 -6 + 0×2 -7 + 1×2 -8 + 1×2 -9 + 0×2 -10 + 1×2 -11

= 2 -3 + 2 -4 + 2 -6 + 2 -8 + 2 -9 + 2 -11 = (0 제거)

n자리 이진 분수에서 마지막 비트의 값은 2 -n 또는 1/2 n 입니다 . 이 예에서는 2 -11 또는 1/2 11 입니다 .

= 1/2 3 + 1/2 4 + 1/2 6 + 1/2 8 + 1/2 9 + 1/2 11 = (역수 포함)

일반적으로 분모는 2n, 분자 지수는 2가 될 수 있습니다 . 그런 다음 모든 항을 단일 값 a/2 n 으로 결합할 수 있습니다 . 이 예의 경우:

= 2 8 /2 11 + 2 7 /2 11 + 2 5 /2 11 + 2 3 /2 11 + 2 2 /2 11 + 1/2 11 = (2 11 로 표현 )

= (2 8 + 2 7 + 2 5 + 2 3 + 2 2 + 1 ) / 2 11 = (공약수 추출)

= (256 + 128 + 32 + 8 + 4 + 1) / 2 11 = (값으로 변환)

= 429 / 2 11

모든 이진 분수는 b/10 n 으로 표현될 수 있습니다.

a/2n 5n / 5n 을 곱하여 (a× 5n )/(2n × 5n ) = (a×5n ) /10n = b/10n 얻습니다 . 여기서 b = a× 5n . n개의 숫자가 있습니다.

예를 들어 다음과 같습니다.

(429·5 11 )/10 11 = 20947265625 / 10 11 = 0.20947265625

모든 이진 십진수는 동일한 자릿수를 갖는 십진 십진수라는 것이 입증되었습니다.

관련 정보