Problem
Problemem jest sposób, w jaki dc (i bc) rozumieją stałe numeryczne.
Na przykład wartość (szesnastkowa) 0.3
(podzielona przez 1) zostaje przekształcona w wartość zbliżoną do0.2
$ dc <<<"20k 16 d i o 0.3 1 / p"
.199999999999999999999999999
W rzeczywistości zmienia się 0.3
również zwykła stała :
$ dc <<<"20 k 16 d i o 0.3 p"
.1
Wydaje się, że jest to dziwne, ale nie jest (więcej później).
Dodanie większej liczby zer powoduje, że odpowiedź zbliża się do poprawnej wartości:
$ 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
Ostatnia wartość jest dokładna i pozostanie dokładna bez względu na to, jak można dodać więcej zer.
$ dc <<<"20 k 16 d i o 0.30000000 p"
.3000000
Problem występuje również w 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
Jedna cyfra na bit?
Bardzo nieintuicyjny fakt w przypadku liczb zmiennoprzecinkowych polega na tym, że wymagana liczba cyfr (po kropce) jest równa liczbie bitów binarnych (również po kropce). Liczba binarna 0,101 jest dokładnie równa 0,625 po przecinku. Liczba binarna 0,0001110001 jest (dokładnie) równa 0.1103515625
(dziesięć cyfr dziesiętnych)
$ bc <<<'scale=30;obase=10;ibase=2; 0.101/1; 0.0001110001/1'; echo ".1234567890"
.625000000000000000000000000000
.110351562500000000000000000000
.1234567890
Również dla liczby zmiennoprzecinkowej, takiej jak 2 ^ (- 10), która w systemie binarnym ma tylko jeden (ustawiony) bit:
$ bc <<<"scale=20; a=2^-10; obase=2;a; obase=10; a"
.0000000001000000000000000000000000000000000000000000000000000000000
.00097656250000000000
Ma taką samą liczbę cyfr binarnych .0000000001
(10) jak cyfry dziesiętne .0009765625
(10). Może się tak nie zdarzyć w innych bazach, ale podstawa 10 to wewnętrzna reprezentacja liczb zarówno w dc, jak i bc, a zatem jest to jedyna podstawa, o którą naprawdę musimy dbać.
Dowód matematyczny znajduje się na końcu tej odpowiedzi.
skala BC
Liczbę cyfr po kropce można policzyć za pomocą wbudowanej funkcji scale()
bc:
$ bc <<<'obase=16;ibase=16; a=0.FD; scale(a); a; a*100'
2
.FA
FA.E1
Jak pokazano, 2 cyfry są niewystarczające do przedstawienia stałej 0.FD
.
Również samo zliczanie liczby znaków użytych po kropce jest bardzo niepoprawnym sposobem zgłaszania (i używania) skali liczby. Skala liczby (w dowolnej bazie) powinna obliczyć liczbę potrzebnych bitów.
Cyfry binarne w kodzie szesnastkowym.
Jak wiadomo, każda cyfra szesnastkowa używa 4 bitów. Dlatego każda cyfra szesnastkowa po kropce dziesiętnej wymaga 4 cyfr binarnych, które ze względu na (nieparzysty?) Powyżej również wymagają 4 cyfr dziesiętnych.
Dlatego liczba taka 0.FD
będzie wymagać prawidłowej reprezentacji 8 cyfr dziesiętnych:
$ bc <<<'obase=10;ibase=16;a=0.FD000000; scale(a);a;a*100'
8
.98828125
253.00000000
Dodaj zera
Matematyka jest prosta (dla liczb szesnastkowych):
- Policz liczbę cyfr szesnastkowych (
h
) po kropce.
- Pomnóż
h
przez 4.
- Dodaj
h×4 - h = h × (4-1) = h × 3 = 3×h
zera.
W kodzie powłoki (dla 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
Który wydrukuje (poprawnie zarówno w DC, jak i BC):
$ sh ./script
F423F.FD000000
F423FFD.0000000
F423FFD.0000000
Wewnętrznie bc (lub dc) może sprawić, że wymagana liczba cyfr będzie zgodna z liczbą obliczoną powyżej ( 3*h
) w celu konwersji liczb szesnastkowych na wewnętrzną reprezentację dziesiętną. Lub jakąś inną funkcję dla innych baz (przy założeniu, że liczba cyfr jest skończona w stosunku do bazy 10 (wewnętrznej dla bc i dc) w takiej innej bazie). Jak 2 i (2,4,8,16, ...) i 5,10.
posix
Specyfikacja posix stwierdza, że (dla bc, na którym opiera się dc):
Obliczenia wewnętrzne należy przeprowadzać jak w systemie dziesiętnym, niezależnie od podstaw wejściowych i wyjściowych, do określonej liczby cyfr dziesiętnych.
Ale „… określona liczba cyfr dziesiętnych”. może być rozumiane jako „… potrzebna liczba cyfr dziesiętnych do przedstawienia stałej liczbowej” (jak opisano powyżej) bez wpływu na „wewnętrzne obliczenia dziesiętne”
Bo:
bc <<<'scale=50;obase=16;ibase=16; a=0.FD; a+1'
1.FA
bc tak naprawdę nie używa 50 („określonej liczby cyfr dziesiętnych”), jak określono powyżej.
Tylko jeśli podzielony jest konwertowany (nadal niepoprawnie, ponieważ używa skali 2 do odczytu stałej 0.FD
przed rozwinięciem jej do 50 cyfr):
$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD/1; a'
.FAE147AE147AE147AE147AE147AE147AE147AE147A
Jest to jednak dokładne:
$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD000000/1; a'
.FD0000000000000000000000000000000000000000
Ponownie, czytanie ciągów liczbowych (stałych) powinno używać poprawnej liczby bitów.
Dowód matematyczny
W dwóch krokach:
Ułamek binarny można zapisać jako a / 2 n
Ułamek binarny jest skończoną sumą ujemnych potęg dwóch.
Na przykład:
= 0.00110101101 =
= 0. 0 0 1 1 0 1 0 1 1 0 1
= 0 + 0 x 2 -1 + 0 x 2 -2 + 1 x 2 -3 + 1 x 2 -4 + 0 x 2 -5 + 1 x 2 -6 + 0 x 2 -7 + 1 x 2 -8 + 1 × 2–9 + 0 × 2–10 + 1 × 2–11
= 2-3 + 2-4 + 2-6 + 2-8 + 2-9 + 2-11 = (z usuniętymi zerami)
W binarnej części n bitów ostatni bit ma wartość 2 -n lub 1/2 n . W tym przykładzie: 2–11 lub 1/2 11 .
= 1/2 3 + 1/2 4 + 1/2 6 + 1/2 8 + 1/2 9 + 1/2 11 = (z odwrotnością)
Ogólnie rzecz biorąc, może się mianownika 2 n o dodatniej licznik wykładnik dwóch. Wszystkie warunki można następnie połączyć w jedną wartość a / 2 n . W tym przykładzie:
= 2, 8 /2 11 + 2 7 /2 11 + 2 5 /2 11 + 2 3 /2 11 + 2 2 /2 11 + 1/2 11 = (wyrażone 2 11 )
= (2 8 + 2 7 + 2 5 + 2 3 + 2 2 + 1) / 2 11 = (wyodrębnianie wspólnego czynnika)
= (256 + 128 + 32 + 8 + 4 + 1) / 2 11 = (przeliczone na wartość)
= 429/2 11
Każda frakcja binarna może być wyrażona jako b / 10 n
Pomnóż a / 2 n przez 5 n
/ 5 n , otrzymując (a × 5 n ) / (2 n × 5 n ) = (a × 5 n ) / 10 n = b / 10 n , gdzie b = a × 5 n . Ma n cyfr.
Na przykład mamy:
(429 · 5 11 ) / 10 11 = 20947265625/10 11 = 0,20947265625
Wykazano, że każda ułamek dwójkowy jest ułamkiem dziesiętnym o tej samej liczbie cyfr.
dc
aby użyć, a następnie po prostu napisać parser bezpośrednio! (Dane wejściowe mogą, ale nie muszą być dziesiętne, i mogą być w innych bazach, więc ilość dopełnienia jest różna.)