Le problème
Le problème est la manière dont dc (et bc) comprennent les constantes numériques.
Par exemple, la valeur (en hexadécimal) 0.3
(divisée par 1) est transformée en une valeur proche de0.2
$ dc <<<"20k 16 d i o 0.3 1 / p"
.199999999999999999999999999
En fait, la constante simple est 0.3
également modifiée:
$ dc <<<"20 k 16 d i o 0.3 p"
.1
Il semble que ce soit d'une manière étrange, mais ce n'est pas le cas (plus tard).
L'ajout de zéros permet à la réponse d'approcher la valeur correcte:
$ 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
La dernière valeur est exacte et restera exacte, quel que soit le nombre de zéros ajoutés.
$ dc <<<"20 k 16 d i o 0.30000000 p"
.3000000
Le problème est également présent dans 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
Un chiffre par bit?
Le fait très intuitif pour les nombres à virgule flottante est que le nombre de chiffres requis (après le point) est égal au nombre de bits binaires (également après le point). Un nombre binaire 0,101 est exactement égal à 0,625 en décimal. Le nombre binaire 0,0001110001 est (exactement) égal à 0.1103515625
(dix chiffres décimaux)
$ bc <<<'scale=30;obase=10;ibase=2; 0.101/1; 0.0001110001/1'; echo ".1234567890"
.625000000000000000000000000000
.110351562500000000000000000000
.1234567890
De plus, pour un nombre à virgule flottante comme 2 ^ (- 10), qui en binaire n'a qu'un seul bit (défini):
$ bc <<<"scale=20; a=2^-10; obase=2;a; obase=10; a"
.0000000001000000000000000000000000000000000000000000000000000000000
.00097656250000000000
Formé du même nombre de chiffres binaires .0000000001
(10) que de chiffres décimaux .0009765625
(10). Ce n'est peut-être pas le cas dans d'autres bases, mais la base 10 est la représentation interne des nombres en dc et en bc et est donc la seule base dont nous devons vraiment nous préoccuper.
La preuve mathématique se trouve à la fin de cette réponse.
échelle bc
Le nombre de chiffres après le point peut être compté avec la scale()
forme de fonction intégrée bc:
$ bc <<<'obase=16;ibase=16; a=0.FD; scale(a); a; a*100'
2
.FA
FA.E1
Comme indiqué, 2 chiffres sont insuffisants pour représenter la constante 0.FD
.
De plus, le simple fait de compter le nombre de caractères utilisés après le point est une manière très incorrecte de signaler (et d'utiliser) l'échelle du nombre. L'échelle d'un nombre (dans n'importe quelle base) doit calculer le nombre de bits nécessaires.
Chiffres binaires dans un flotteur hexadécimal.
Comme on le sait, chaque chiffre hexadécimal utilise 4 bits. Par conséquent, chaque chiffre hexadécimal après le point décimal nécessite 4 chiffres binaires, qui en raison du fait (impair?) Ci-dessus nécessitent également 4 chiffres décimaux.
Par conséquent, un nombre comme 0.FD
nécessitera 8 chiffres décimaux pour être représenté correctement:
$ bc <<<'obase=10;ibase=16;a=0.FD000000; scale(a);a;a*100'
8
.98828125
253.00000000
Ajouter des zéros
Le calcul est simple (pour les nombres hexadécimaux):
- Comptez le nombre de chiffres hexadécimaux (
h
) après le point.
- Multipliez
h
par 4.
- Ajoutez des
h×4 - h = h × (4-1) = h × 3 = 3×h
zéros.
En code shell (pour 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
Qui s'imprimera (correctement en dc et en bc):
$ sh ./script
F423F.FD000000
F423FFD.0000000
F423FFD.0000000
En interne, bc (ou dc) pourrait faire correspondre le nombre de chiffres requis au nombre calculé ci-dessus ( 3*h
) pour convertir les flottants hexadécimaux en représentation décimale interne. Ou une autre fonction pour d'autres bases (en supposant que le nombre de chiffres est fini par rapport à la base 10 (interne de bc et dc) dans cette autre base). Comme 2 i (2,4,8,16, ...) et 5,10.
posix
La spécification posix indique que (pour bc, sur lequel dc est basé):
Les calculs internes doivent être effectués comme en décimal, quelles que soient les bases d'entrée et de sortie, au nombre spécifié de chiffres décimaux.
Mais "... le nombre spécifié de chiffres décimaux." pourrait être compris comme "… le nombre nécessaire de chiffres décimaux pour représenter la constante numérique" (comme décrit ci-dessus) sans affecter les "calculs internes décimaux"
Car:
bc <<<'scale=50;obase=16;ibase=16; a=0.FD; a+1'
1.FA
bc n'utilise pas vraiment 50 ("le nombre spécifié de chiffres décimaux") comme défini ci-dessus.
Seulement s'il est divisé, il est converti (toujours de manière incorrecte car il utilise une échelle de 2 pour lire la constante 0.FD
avant de l'étendre à 50 chiffres):
$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD/1; a'
.FAE147AE147AE147AE147AE147AE147AE147AE147A
Cependant, c'est exact:
$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD000000/1; a'
.FD0000000000000000000000000000000000000000
Encore une fois, la lecture de chaînes numériques (constantes) doit utiliser le nombre correct de bits.
Preuve mathématique
En deux étapes:
Une fraction binaire peut s'écrire a / 2 n
Une fraction binaire est une somme finie de puissances négatives de deux.
Par exemple:
= 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 = (avec les zéros supprimés)
Dans une fraction binaire de n bits, le dernier bit a une valeur de 2 -n , ou 1/2 n . Dans cet exemple: 2 -11 ou 1/2 11 .
= 1/2 3 + 1/2 4 + 1/2 6 + 1/2 8 + 1/2 9 + 1/2 11 = (avec inverse)
En général, le dénominateur pourrait devenir 2 n avec un exposant numérateur positif de deux. Tous les termes peuvent ensuite être combinés en une seule valeur a / 2 n . Pour cet exemple:
2 = 8 /2 11 + 2 7 /2 11 + 2 cinq / 2 11 + 2 3 /2 11 + 2 2 /2 11 + 1/2 11 = (exprimé avec deux 11 )
= (2 8 + 2 7 + 2 5 + 2 3 + 2 2 + 1) / 2 11 = (extraction du facteur commun)
= (256 + 128 + 32 + 8 + 4 + 1) / 2 11 = (converti en valeur)
= 429/2 11
Chaque fraction binaire peut être exprimée en b / 10 n
Multipliez a / 2 n par 5 n
/ 5 n , en obtenant (a × 5 n ) / (2 n × 5 n ) = (a × 5 n ) / 10 n = b / 10 n , où b = a × 5 n . Il a n chiffres.
Pour l'exemple, nous avons:
(429 · 5 11 ) / 10 11 = 20947265625/10 11 = 0,20947265625
Il a été démontré que chaque fraction binaire est une fraction décimale avec le même nombre de chiffres.
dc
à utiliser puis pour écrire directement un analyseur! (L'entrée peut avoir ou non une décimale, et peut être dans d'autres bases, donc la quantité de remplissage varie.)