[PYTHON] Pourquoi le nombre à virgule flottante de 0,1 est-il supérieur à 0,1, mais lorsqu'il est ajouté 10 fois, il est inférieur à 1,0 [Partie 1]

Dans cet article, j'ai constaté que la conversion de 0,1 '' en un nombre à virgule flottante aboutissait à un nombre légèrement supérieur à 0,1 ''. Pourquoi 0,1 est-il affiché comme 0,1 lors de l'impression même si 0,1 ne peut pas être représenté avec précision par un nombre à virgule flottante Cela signifie que si vous ajoutez 0,1 '', les erreurs positives devraient s'accumuler. Par exemple, je m'attendais à ce que l'ajout de 0,1 '' 10 fois soit légèrement plus grand que `` 1 ''. Je vais essayer.

total = 0

for i in range(10):
    total += 0.1

print(total)
#=> 0.9999999999999999

De manière inattendue, un nombre inférieur à `` 1 '' a été émis. Cette fois, j'ai étudié ce phénomène.

environnement

Cet article utilise Python 3.7.

[Hypothèse] Nombre à virgule flottante

Dans cet article, le terme «nombre à virgule flottante» fait référence ci-après à «IEEE 754 fois la précision».

Le format de nombre à virgule flottante est une conversion du nombre au format suivant, dans lequel sign, ʻexp`` et `frac`` sont classés dans l'ordre.

(-1)^{sign} \times 2^{exp - 1023} \times (1 + frac \times 2^{-52})

Le nom et le nombre de bits de chaque symbole sont les suivants.

symbole Nom japonais nom anglais Nombre de bits
sign Partie de code sign 1
exp Partie d'index exponent 11
frac Partie formelle fraction 52

Lorsque 0,1 est exprimé sous forme de nombre à virgule flottante

to_float_point_0_1.png https://tools.m-bsys.com/calculators/ieee754.php

`` 0,1 '' est la représentation binaire des nombres à virgule flottante

Code= 0
indice= 01111111011
mantisse= 1001100110011001100110011001100110011001100110011010

Une fois converti en nombre décimal,

Code= 0
indice= 1019
mantisse= 2702159776422298

est. L'exposant à virgule flottante a 1023 '' ajouté, donc en soustrayant cela donne -4 ''.

Conversion de `` 0,1 '', qui est représenté par un nombre à virgule flottante, en un nombre décimal

0.1000000000000000055511151231257827021181583404541015625

est.

Nombre lors de l'ajout de 0,1 chacun

Le nombre à virgule flottante de 0,1 '' est légèrement supérieur à 0,1 '', mais s'il ne devient pas 1 '' en ajoutant 10 fois, il semble qu'un phénomène d'inversion se produit quelque part. Voyons le résultat du calcul en cours de route. Si vous passez une virgule flottante à Decimal '' et `` print '', la valeur lorsque le nombre à virgule flottante est converti en nombre décimal sera affichée.

from decimal import Decimal

total = 0
d_total = Decimal('0')

for i in range(10):
    total += 0.1
    d_total += Decimal('0.1')
    print(f'{d_total}|{Decimal(total)}')

Voici le résultat de l'exécution.

Décimal Résultat du calcul
0.1 0.1000000000000000055511151231257827021181583404541015625
0.2 0.200000000000000011102230246251565404236316680908203125
0.3 0.3000000000000000444089209850062616169452667236328125
0.4 0.40000000000000002220446049250313080847263336181640625
0.5 0.5
0.6 0.59999999999999997779553950749686919152736663818359375
0.7 0.6999999999999999555910790149937383830547332763671875
0.8 0.79999999999999993338661852249060757458209991455078125
0.9 0.899999999999999911182158029987476766109466552734375
1.0 0.99999999999999988897769753748434595763683319091796875

0,1 '' à 0,4 '' sont plus que calculés sous forme de nombres décimaux, 0,5 '' est exactement et 0,6 '' à `` 1,0 '' sont plus que calculés sous forme de nombres décimaux. Le résultat était qu'il était également petit.

Lors de l'ajout de 0,1 chacun, vérifiez combien est ajouté

Maintenant, vérifions combien est réellement ajouté lors de l'ajout de `` 0,1 '' à la fois. Le programme est ici. Le décimal de Python a 28 chiffres valides par défaut, alors changez-le à une taille suffisante à l'avance.

from decimal import Decimal, getcontext

#Changement du nombre de chiffres valides de Decimal à 64 chiffres
getcontext().prec = 64

total = prev = 0
d_total = Decimal('0')

for i in range(10):
    total += 0.1
    d_total += Decimal('0.1')
    print(f' |{Decimal(total) - Decimal(prev)}')
    print(f'{d_total}|{Decimal(total)}')
    prev = total

C'est le résultat de l'exécution. Les résultats du calcul utilisant des nombres à virgule flottante sont affichés sur les lignes impaires à l'exclusion de l'en-tête, et les différences sont affichées sur les lignes paires.

Décimal Résultat du calcul / différence
0.1 0.1000000000000000055511151231257827021181583404541015625
0.1000000000000000055511151231257827021181583404541015625
0.2 0.200000000000000011102230246251565404236316680908203125
0.100000000000000033306690738754696212708950042724609375
0.3 0.3000000000000000444089209850062616169452667236328125
0.09999999999999997779553950749686919152736663818359375
0.4 0.40000000000000002220446049250313080847263336181640625
0.09999999999999997779553950749686919152736663818359375
0.5 0.5
0.09999999999999997779553950749686919152736663818359375
0.6 0.59999999999999997779553950749686919152736663818359375
0.09999999999999997779553950749686919152736663818359375
0.7 0.6999999999999999555910790149937383830547332763671875
0.09999999999999997779553950749686919152736663818359375
0.8 0.79999999999999993338661852249060757458209991455078125
0.09999999999999997779553950749686919152736663818359375
0.9 0.899999999999999911182158029987476766109466552734375
0.09999999999999997779553950749686919152736663818359375
1.0 0.99999999999999988897769753748434595763683319091796875

La différence entre 0,1 → 0,2 '' est la même que la valeur de 0,1 '' exprimée en nombre à virgule flottante, mais 0,2 → 0,3 '' est plus grande. Après 0,3 '', la même valeur est ajoutée, qui est inférieure à la valeur de `` 0,1 '' exprimée en nombre à virgule flottante.

À propos, toutes les valeurs après 0,4 '' ne sont pas ajoutées moins de 0,1 '', mais augmentent à nouveau avec `` 1,1 → 1,2 ''.

Valeur attendue Résultat du calcul / différence
1.0 0.99999999999999988897769753748434595763683319091796875
0.09999999999999997779553950749686919152736663818359375
1.1 1.0999999999999998667732370449812151491641998291015625
0.1000000000000000888178419700125232338905334472656250
1.2 1.1999999999999999555910790149937383830547332763671875
0.1000000000000000888178419700125232338905334472656250
1.3 1.3000000000000000444089209850062616169452667236328125
0.1000000000000000888178419700125232338905334472656250
1.4 1.4000000000000001332267629550187848508358001708984375
0.1000000000000000888178419700125232338905334472656250
1.5 1.5000000000000002220446049250313080847263336181640625

Pourquoi 0,1 '' n'augmente-t-il pas du nombre de points flottants lors de l'ajout de 0,1 ''? Cela implique des erreurs d'arrondi.

Procédure d'ajout de nombres à virgule flottante

L'addition de nombres à virgule flottante positifs est effectuée par la procédure suivante.

① Faites correspondre l'index le plus petit avec l'index le plus grand (2) Ajustez en réduisant le nombre incorrect à mesure que l'exposant augmente. ③ Ajoutez les numéros incorrects ④ En cas de report, ajoutez 1 à l'exposant et réduisez le pseudonyme en conséquence. ⑤ Arrondissez les chiffres débordants du nombre incorrect (même arrondi)

Exemple d'addition en décimal

La procédure d'ajout de nombres à virgule flottante est illustrée à l'aide d'un exemple de nombre décimal. Considérez l'ajout de nombres décimaux à 5 chiffres.

\begin{array}{llcll}
    &9.8192 & \times & 10^2 & (= 981.92)\\
 + &4.7533 & \times & 10^1 & (= 47.533)\\
\hline
\end{array}

La procédure est expliquée ci-dessous.

① Faites correspondre l'index le plus petit avec l'index le plus grand (2) Ajustez en réduisant le nombre incorrect à mesure que l'exposant augmente.

Alignez les exposants pour l'ajout. Cette fois, celui avec l'index le plus grand est 9.8192 x 10 ^ 2, donc nous allons transformer 4.7533 x 10 ^ 1 pour l'adapter.

\begin{array}{llcr}
&4.7533 & \times & 10^1\\
 = &0.47533 & \times & 10^2
\end{array}

③ Ajoutez les numéros incorrects Additionner.

\begin{array}{llcr}
    &\phantom{1}9.8192 & \times & 10^2\\
 + &\phantom{1}0.47533 & \times & 10^2\\
\hline
 & 10.29453 & \times & 10^2
\end{array}

④ En cas de report, ajoutez 1 à l'exposant et réduisez le pseudonyme en conséquence.

Puisque la partie entière est 10, le report se produit. Pour faire de la partie entière un chiffre, ajoutez 1 à l'exposant et divisez le nombre incorrect par 10.

\begin{array}{llcr}
&10.29453 & \times & 10^2\\
 = &1.029453 & \times & 10^3
\end{array}

⑤ Autour de la poutre débordante (même ronde)

Cette fois, le nombre de chiffres valides est de 5, nous devons donc considérer le traitement du `` 53 '' de fin. Puisque même l'arrondi est utilisé ici, il sera arrondi.

\begin{array}{llcr}
&1.029453 & \times & 10^3\\
\rightarrow &1.0295 & \times & 10^3
\end{array}

La procédure d'ajout de nombres à virgule flottante a été confirmée en nombres décimaux.

Même arrondi

L'arrondi uniforme est une méthode d'arrondi similaire à l'arrondi, mais lorsque l'objet à arrondir se trouve exactement au milieu des deux valeurs, il est arrondi vers le haut ou vers le bas pour que le chiffre supérieur soit pair.

Par exemple, en arrondissant un nombre pair au premier chiffre, 1,5 '' devient 2 '' et 4,5 '' devient 4 ''. 4,51 '' est 5 ''.

Poutre de protection et mors collant

Dans l'exemple d'ajout en décimal, le dernier chiffre (`` 53 '' dans l'exemple ci-dessus) débordait des chiffres effectifs lorsque les exposants étaient combinés. Ces valeurs doivent être mises de côté car elles seront arrondies à la fin. (Ne tronquez pas simplement parce que l'exposant est augmenté et que le nombre incorrect est diminué et ajusté à l'étape 2).

Avec même arrondi,

-Plus de 5 → Arrondir ―― 5 Parfait → Arrondir vers le haut ou vers le bas dans la même direction ―― Moins de 5 → Tronquer

Par conséquent, nous avons besoin d'informations sur les chiffres en dessous ainsi que sur le chiffre le plus significatif des chiffres qui débordent. Cependant, ce qui est nécessaire pour les informations sur les chiffres autres que le chiffre le plus significatif est de savoir s'il s'agit ou non exactement de 0, il semble donc suffisant de préparer une valeur booléenne.

Les chiffres de garde sont la zone pour stocker les chiffres qui débordent. Pour l'ajout de nombres positifs, un chiffre de protection suffit. (Un bit de plus est nécessaire pour traiter la soustraction et les nombres négatifs). Si le chiffre de protection est un chiffre, dans l'exemple ci-dessus, le bit le plus significatif 5 '' des chiffres de débordement 53 '' est stocké. Une valeur booléenne qui indique si un nombre inférieur ou égal au chiffre de protection contient un nombre supérieur ou égal à 1 est appelée un bit persistant. Dans l'exemple ci-dessus, vrai '' (ou 1 '') est défini car le chiffre de débordement `` 3 '' est 1 ou plus.

Réécrire l'addition en décimal à l'aide de chiffres protégés et de bits persistants

Appliquez les chiffres de protection et les bits persistants à l'exemple d'addition décimale ci-dessus.

\begin{array}{llcll}
    &9.8192 & \times & 10^2 & (= 981.92)\\
 + &4.7533 & \times & 10^1 & (= 47.533)\\
\hline
\end{array}

① Faites correspondre l'index le plus petit avec l'index le plus grand (2) Ajustez en réduisant le nombre incorrect à mesure que l'exposant augmente.

\begin{array}{lrcll}
&4.7533 & \times & 10^1&\\
 = &0.47533 & \times & 10^2&(Poutre de protection=3)
\end{array}

Depuis que le dernier `` 3 '' a débordé, je l'ai sauvegardé dans le chiffre de protection.

③ Ajoutez les numéros incorrects

\begin{array}{llcr}
    &\phantom{1}9.8192 & \times & 10^2&\\
 + &\phantom{1}0.4753 & \times & 10^2&(Poutre de protection=3)\\
\hline
 & 10.2945 & \times & 10^2&(Poutre de protection=3)
\end{array}

④ En cas de report, ajoutez 1 à l'exposant et réduisez le pseudonyme en conséquence.

\begin{array}{llcrl}
&10.2945 & \times & 10^2&\\
 = &1.0294 & \times & 10^3&(Poutre de protection=5,Bit collant=true)
\end{array}

Le 5 '' de fin a débordé, je l'ai donc sauvegardé dans le chiffre de protection. À ce stade, 3 '', qui était à l'origine enregistré dans le chiffre de protection, est enregistré dans le bit collant. Le bit collant tient si le nombre est 1 ou plus, donc `` vrai '' est défini.

⑤ Autour de la poutre débordante (même ronde) Utilisez la poutre de protection et le mors collant pour arrondir, L'arrondissement est effectué car le bit collant a été vrai `` avec le chiffre de protection 00 ''.

\begin{array}{llcrl}
&1.0294 & \times & 10^3&(Poutre de protection=5,Bit collant=true)\\
\rightarrow &1.0295 & \times & 10^3
\end{array}

La procédure d'ajout de nombres à virgule flottante à l'aide de chiffres protégés et de bits persistants a été confirmée en nombres décimaux. Faites de même pour les nombres binaires.

Vers la deuxième partie

Puisque c'est devenu long, je vais diviser l'article. Dans la deuxième partie, nous vérifierons le comportement lors de l'ajout de `` 0,1 ''. Les sites auxquels j'ai fait référence sont également résumés dans la deuxième partie.

Pourquoi le nombre à virgule flottante de 0,1 est-il supérieur à 0,1, mais il est inférieur à 1,0 lorsqu'il est ajouté 10 fois [Partie 2]

Recommended Posts

Pourquoi le nombre à virgule flottante de 0,1 est-il supérieur à 0,1, mais lorsqu'il est ajouté 10 fois, il est inférieur à 1,0 [Partie 1]
Pourquoi le nombre à virgule flottante de 0,1 est-il supérieur à 0,1, mais lorsqu'il est ajouté 10 fois, il est inférieur à 1,0 [Partie 2]
Pourquoi 0,1 est-il affiché comme 0,1 lors de l'impression alors que 0,1 ne peut pas être représenté avec précision par un nombre à virgule flottante?
Vérifions la chaîne d'octets en mémoire du nombre flottant flottant en Python
Il semble que la version de pyflakes ne soit pas la dernière lorsque flake8 est installé
L'apprentissage en profondeur! L'histoire des données elles-mêmes qui sont lues lorsqu'elles ne suivent pas après la reconnaissance des nombres manuscrits