Il problema
Il problema è il modo in cui dc (e bc) comprendono le costanti numeriche.
Ad esempio, il valore (in esadecimale) 0.3
(diviso per 1) viene trasformato in un valore vicino a0.2
$ dc <<<"20k 16 d i o 0.3 1 / p"
.199999999999999999999999999
In effetti, anche la costante normale 0.3
viene cambiata:
$ dc <<<"20 k 16 d i o 0.3 p"
.1
Sembra che sia in un modo strano, ma non lo è (più tardi).
L'aggiunta di più zeri rende l'approccio alla risposta il valore corretto:
$ 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
L'ultimo valore è esatto e rimarrà esatto indipendentemente da come possano essere aggiunti più zeri.
$ dc <<<"20 k 16 d i o 0.30000000 p"
.3000000
Il problema è presente anche in 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
Una cifra per bit?
Il fatto molto intuitivo per i numeri in virgola mobile è che il numero di cifre richieste (dopo il punto) è uguale al numero di bit binari (anche dopo il punto). Un numero binario 0,101 è esattamente uguale a 0,625 in decimale. Il numero binario 0,0001110001 è (esattamente) uguale a 0.1103515625
(dieci cifre decimali)
$ bc <<<'scale=30;obase=10;ibase=2; 0.101/1; 0.0001110001/1'; echo ".1234567890"
.625000000000000000000000000000
.110351562500000000000000000000
.1234567890
Inoltre, per un numero in virgola mobile come 2 ^ (- 10), che in binario ha solo un bit (impostato):
$ bc <<<"scale=20; a=2^-10; obase=2;a; obase=10; a"
.0000000001000000000000000000000000000000000000000000000000000000000
.00097656250000000000
Ha lo stesso numero di cifre binarie .0000000001
(10) delle cifre decimali .0009765625
(10). Questo potrebbe non essere il caso in altre basi, ma la base 10 è la rappresentazione interna dei numeri sia in cc che in cc e quindi è l'unica base di cui dobbiamo davvero preoccuparci.
La prova matematica è alla fine di questa risposta.
scala in bc
Il numero di cifre dopo il punto può essere contato con il scale()
modulo di funzione incorporato bc:
$ bc <<<'obase=16;ibase=16; a=0.FD; scale(a); a; a*100'
2
.FA
FA.E1
Come mostrato, 2 cifre non sono sufficienti per rappresentare la costante 0.FD
.
Inoltre, contare il numero di caratteri utilizzati dopo il punto è un modo molto errato di riportare (e utilizzare) la scala del numero. La scala di un numero (in qualsiasi base) dovrebbe calcolare il numero di bit necessari.
Cifre binarie in un float esadecimale.
Come è noto, ogni cifra esadecimale utilizza 4 bit. Pertanto, ogni cifra esadecimale dopo il punto decimale richiede 4 cifre binarie, che a causa del fatto (dispari?) Sopra richiedono anche 4 cifre decimali.
Pertanto, un numero simile 0.FD
richiederà 8 cifre decimali per essere rappresentato correttamente:
$ bc <<<'obase=10;ibase=16;a=0.FD000000; scale(a);a;a*100'
8
.98828125
253.00000000
Aggiungi zeri
La matematica è semplice (per numeri esadecimali):
- Contare il numero di cifre esadecimali (
h
) dopo il punto.
- Moltiplicare
h
per 4.
- Aggiungi
h×4 - h = h × (4-1) = h × 3 = 3×h
zeri.
Nel codice shell (per 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
Che stamperà (correttamente sia in cc che in cc):
$ sh ./script
F423F.FD000000
F423FFD.0000000
F423FFD.0000000
Internamente, bc (o dc) potrebbe far corrispondere il numero di cifre richieste al numero calcolato sopra ( 3*h
) per convertire i float esadecimali nella rappresentazione decimale interna. O qualche altra funzione per altre basi (supponendo che il numero di cifre sia finito rispetto alla base 10 (interna di bc e dc) in tale altra base). Come 2 i (2,4,8,16, ...) e 5,10.
posix
La specifica posix afferma che (per bc, su cui si basa dc):
I calcoli interni devono essere condotti come in decimali, indipendentemente dalle basi di input e output, al numero specificato di cifre decimali.
Ma "... il numero specificato di cifre decimali." potrebbe essere inteso come "... il numero necessario di cifre decimali per rappresentare la costante numerica" (come descritto sopra) senza influire sui "calcoli interni decimali"
Perché:
bc <<<'scale=50;obase=16;ibase=16; a=0.FD; a+1'
1.FA
bc non utilizza realmente 50 ("il numero specificato di cifre decimali") come impostato sopra.
Solo se diviso viene convertito (ancora erroneamente poiché utilizza una scala 2 per leggere la costante 0.FD
prima di espanderla a 50 cifre):
$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD/1; a'
.FAE147AE147AE147AE147AE147AE147AE147AE147A
Tuttavia, questo è esatto:
$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD000000/1; a'
.FD0000000000000000000000000000000000000000
Ancora una volta, la lettura di stringhe numeriche (costanti) dovrebbe utilizzare il numero corretto di bit.
Prova matematica
In due passaggi:
Una frazione binaria può essere scritta come / 2 n
Una frazione binaria è una somma finita di poteri negativi di due.
Per esempio:
= 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 = (con zeri rimossi)
In una frazione binaria di n bit, l'ultimo bit ha un valore di 2 -n o 1/2 n . In questo esempio: 2 -11 o 1/2 11 .
= 1/2 3 + 1/2 4 + 1/2 6 + 1/2 8 + 1/2 9 + 1/2 11 = (con inverso)
In generale, il denominatore potrebbe diventare 2 n con un esponente numeratore positivo di due. Tutti i termini possono quindi essere combinati in un singolo valore a / 2 n . Per questo esempio:
= 2 8 /2 11 + 2 7 /2 11 + 2 5 /2 11 + 2 3 /2 11 + 2 2 /2 11 + 1/2 11 = (espressa con 2 11 )
= (2 8 + 2 7 + 2 5 + 2 3 + 2 2 + 1) / 2 11 = (estrazione del fattore comune)
= (256 + 128 + 32 + 8 + 4 + 1) / 2 11 = (convertito in valore)
= 429/2 11
Ogni frazione binaria può essere espressa come b / 10 n
Moltiplicare a / 2 n per 5 n
/ 5 n , ottenendo (a × 5 n ) / (2 n × 5 n ) = (a × 5 n ) / 10 n = b / 10 n , dove b = a × 5 n . Ha n cifre.
Per l'esempio, abbiamo:
(429 · 5 11 ) / 10 11 = 20947265625/10 11 = 0,20947265625
È stato dimostrato che ogni frazione binaria è una frazione decimale con lo stesso numero di cifre.
dc
da usare e poi scrivere direttamente un parser! (L'input può avere o meno un decimale e può trovarsi in altre basi, quindi la quantità di riempimento varia.)