web-dev-qa-db-fra.com

Python précision flottante à virgule flottante disponible?

Juste pour le plaisir et parce que c’était très facile, j’ai écrit un programme court pour générer Greffage de nombres , mais à cause de problèmes de précision en virgule flottante, il n’a pas été possible de trouver des exemples plus volumineux.

def isGrafting(a):
  for i in xrange(1, int(ceil(log10(a))) + 2):
    if a == floor((sqrt(a) * 10**(i-1)) % 10**int(ceil(log10(a)))):
      return 1

a = 0
while(1):
  if (isGrafting(a)):
    print "%d %.15f" % (a, sqrt(a))
  a += 1

Ce code manque au moins un numéro de greffage connu. 9999999998 => 99999.99998999999999949999999994999999999374999999912... Il semble perdre de la précision après avoir multiplié par 10**5.

>>> a = 9999999998
>>> sqrt(a)
99999.99999
>>> a == floor((sqrt(a) * 10**(5)) % 10**int(ceil(log10(a))))
False
>>> floor((sqrt(a) * 10**(5)) % 10**int(ceil(log10(a))))
9999999999.0
>>> print "%.15f" % sqrt(a)
99999.999989999996615
>>> print "%.15f" % (sqrt(a) * 10**5)
9999999999.000000000000000

J'ai donc écrit un court programme C++ pour voir si c'était mon processeur qui tronçonnait le nombre à virgule flottante ou le python.

#include <cstdio>
#include <cmath>
#include <stdint.h>

int main()
{
  uint64_t a = 9999999998;
  printf("%ld %.15f %.15f %.15f %.15f\n", a, sqrt((double)a), sqrt((double)a)*1e4, sqrt((double)a)*1e5, sqrt((double)a)*1e6);
  a = 999999999998;
  printf("%ld %.15f %.15f %.15f %.15f\n", a, sqrt((double)a), sqrt((double)a)*1e5, sqrt((double)a)*1e6, sqrt((double)a)*1e7);
  a = 99999999999998;
  printf("%ld %.15f %.15f %.15f %.15f\n", a, sqrt((double)a), sqrt((double)a)*1e6, sqrt((double)a)*1e7, sqrt((double)a)*1e8);
  return 0;
}

Quelles sorties:

9999999998 99999.999989999996615 999999999.899999976158142 9999999999.000000000000000 99999999990.000000000000000
999999999998 999999.999998999992386 99999999999.899993896484375 999999999999.000000000000000 9999999999990.000000000000000
99999999999998 9999999.999999899417162 9999999999999.900390625000000 99999999999999.000000000000000 999999999999990.000000000000000

Il semble donc que je me heurte aux limites de la précision en virgule flottante et que la CPU coupe les bits restants car elle pense que la différence qui reste est une erreur en virgule flottante. Existe-t-il un moyen de contourner ce problème sous Python? Ou dois-je passer à C et utiliser GMP ou quelque chose?

39
OmnipotentEntity

Dans la bibliothèque standard, le module decimal peut être ce que vous recherchez. De plus, j’ai trouvé mpmath très utile. La documentation contient également de nombreux exemples intéressants (malheureusement, mpmath n'est pas installé sur mon ordinateur; sinon, je vérifierais quelques exemples et les posterais).

Une mise en garde à propos du module decimal , cependant. Le module contient plusieurs fonctions intégrées pour des opérations mathématiques simples (par exemple, sqrt), mais les résultats de ces fonctions peuvent ne pas toujours correspondre à la fonction correspondante dans math ou à d’autres modules à des précisions plus élevées (bien qu’ils puissent être plus précis). Par exemple, 

from decimal import *
import math

getcontext().prec = 30
num = Decimal(1) / Decimal(7)

print("   math.sqrt: {0}".format(Decimal(math.sqrt(num))))
print("decimal.sqrt: {0}".format(num.sqrt()))

En Python 3.2.3, cela sort les deux premières lignes

   math.sqrt: 0.37796447300922719758631274089566431939601898193359375
decimal.sqrt: 0.377964473009227227214516536234
actual value: 0.3779644730092272272145165362341800608157513118689214

comme indiqué, ce n'est pas exactement ce à quoi vous vous attendiez, et vous pouvez voir que plus la précision est élevée, moins les résultats correspondent. Notez que le module decimal a plus de précision dans cet exemple, car il correspond plus étroitement à la valeur réelle

37
Ricardo Altamirano

Pour ce problème particulier, decimal est une excellente solution, car il stocke les chiffres décimaux sous forme de n-uplets! 

>>> a = decimal.Decimal(9999999998)
>>> a.as_Tuple()
DecimalTuple(sign=0, digits=(9, 9, 9, 9, 9, 9, 9, 9, 9, 8), exponent=0)

Puisque vous recherchez une propriété qui s'exprime le plus naturellement en notation décimale, il est un peu idiot d'utiliser une représentation binaire. La page wikipedia que vous avez liée n’indiquait pas le nombre de "chiffres non greffés" pouvant apparaître avant le début des "chiffres greffés". Vous pouvez donc spécifier:

>>> def isGrafting(dec, max_offset=5):
...     dec_digits = dec.as_Tuple().digits
...     sqrt_digits = dec.sqrt().as_Tuple().digits
...     windows = [sqrt_digits[o:o + len(dec_digits)] for o in range(max_offset)]
...     return dec_digits in windows
... 
>>> isGrafting(decimal.Decimal(9999999998))
True
>>> isGrafting(decimal.Decimal(77))
True

Je pense qu'il y a de bonnes chances que le résultat de Decimal.sqrt() soit plus précis, du moins pour cela, que le résultat de math.sqrt() en raison de la conversion entre représentation binaire et représentation décimale. Considérez ce qui suit, par exemple: 

>>> num = decimal.Decimal(1) / decimal.Decimal(7)
>>> decimal.Decimal(math.sqrt(num) ** 2) * 7
Decimal('0.9999999999999997501998194593')
>>> decimal.Decimal(num.sqrt() ** 2) * 7
Decimal('1.000000000000000000000000000')
8
senderle

Vous pouvez essayer avec Decimal au lieu de virgule flottante.

7
f p

Python n'a pas de flotteurs intégrés de précision arbitraire, mais il existe des packages Python tiers utilisant GMP: gmpy et PyGMP .

5
Ned Batchelder

utilisez decimal, (voici un exemple plus clair):

>>> 2.3-2.2
0.09999999999999964
>>> from decimal import Decimal
>>> Decimal('2.3')-Decimal('2.2')
Decimal('0.1')
>>> float(Decimal('2.3')-Decimal('2.2'))
0.1
>>> 
0
U9-Forward