Tracę precyzję dzięki dc


12

Chcę używać dcdo obsługi niektórych podstawowych liczb 16 z punktami szesnastkowymi, ale mam problemy z precyzją. Na przykład poniżej mnożę F423F.FDprzez 100oba hex. Oczekiwaną odpowiedzią jest F423FFD, zamiast tego F423FFA.E1, ścisłe, ale niewystarczające nawet po zaokrągleniu.

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

Przeczytałem, że dcbył to kalkulator o nieograniczonej precyzji i pod żadnym względem nie jest to duża liczba. Czy coś robię źle?

Dziękuję za odpowiedzi. Biorąc pod uwagę problemy dc, ugryzłem kulę i napisałem własny parser dla liczb rzeczywistych w innych bazach. Jeśli ktoś jest zainteresowany kodem, mogę go opublikować tutaj.

Odpowiedzi:


6

Pamiętaj, że samo wydrukowanie oryginalnego numeru pokazuje, że jest on zaokrąglony:

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

Możesz obejść ten problem, dodając wiele zer końcowych dla większej precyzji:

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

Dzięki. Myślę, że skończy się na tym, że zajmie więcej kodu do masowania liczb, dcaby 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.)
Yimin Rong

2
Oznaczę to jako zaakceptowaną odpowiedź. Osoby odpowiedzialne za utrzymanie dcodpowiedziały: Prawidłowe postępowanie z cyframi ułamkowymi dziesiętnymi wymagałoby zupełnie innego modelu niż model w skali dziesiętnej używany przez dc i bc (tak jak narzuca POSIX dla bc i zgodnie z tradycją historyczną dla obu). , więc technicznie można to naprawić dc, ale prawdopodobnie się zepsuje bc, tak sklasyfikowane jako WONTFIX.
Yimin Rong

8

Wyrażony jako dziesiętny (przy użyciu dcdo konwersji) odpowiada 999999.98 (w zaokrągleniu w dół) × 256, tj. 255999994.88, czyli F423FFA.E1 w systemie szesnastkowym.

Różnica wynika więc z dczachowania zaokrąglania: zamiast obliczać 256 × (999999 + 253 ÷ 256), co dałoby 255999997, zaokrągla 253 ÷ 256 w dół i mnoży wynik.

dcjest dowolnym kalkulatorem precyzji, co oznacza, że ​​może obliczyć dowolną dokładność, ale musisz powiedzieć, co to jest. Domyślnie jego dokładność wynosi 0, co oznacza, że ​​dzielenie tworzy tylko wartości całkowite, a mnożenie używa liczby cyfr na wejściu. Aby ustawić precyzję, użyj k(i pamiętaj, że precyzja jest zawsze wyrażona cyframi dziesiętnymi, niezależnie od podstawki wejściowej lub wyjściowej):

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

(Dokładność 8 cyfr byłaby wystarczająca, ponieważ właśnie to należy przedstawić w zakresie 1 ÷ 256 w systemie dziesiętnym.)


1
Byłby to zupełnie nieoczekiwany wynik dla kalkulatora o „dowolnej precyzji”?
Yimin Rong,

3
Nadal traci precyzję, gdy kjest ustawiony: 10 k 16 d i o F423F.FD pF423F.FAwięc musiałbym skalować wszystkie liczby przed ich użyciem dc. Zasadniczo sprowadza się to do ich wstępnej analizy.
Yimin Rong,

2
@ Yimin tak, niestety dcskaluje dane wejściowe tylko przy użyciu liczby cyfr, co wydaje mi się błędem (ponieważ liczba cyfr jest obliczana przy użyciu podstawki wejściowej, ale stosowana do wartości dziesiętnej).
Stephen Kitt

1
@hag, to jest to, co określa POSIX (dla bcktórego, dcjest oparte): „Obliczenia wewnętrzne powinny być przeprowadzane jak w systemie dziesiętnym, niezależnie od podstaw wejściowych i wyjściowych, z określoną liczbą cyfr dziesiętnych.”
Stephen Kitt

1
Naprawdę jest to problem z analizowaniem stałej. Spróbuj 20 k 16 d i o 0.3 1 / p (który drukuje .1999999999999999999). Zrozumieć, że operacja jest właśnie dzielenie 0.2przez 1(co teoretycznie nie powinno zmienić wartość). Podczas 20 k 16 d i o 0.3000 1 / p(prawidłowego) drukowania .30000000000000000. (
Cd

1

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.3ró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.FDbę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óż hprzez 4.
  • Dodaj h×4 - h = h × (4-1) = h × 3 = 3×hzera.

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.FDprzed 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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.