Précision arbitraire de Python floating point disponible?

juste pour le plaisir et parce que c'était vraiment facile, j'ai écrit un programme court pour générer numéros de greffe , mais en raison de problèmes de précision de pointe flottante, il ne trouve pas certains des plus grands exemples.

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 greffe connu. 9999999998 => 99999.99998999999999949999999994999999999374999999912... il semble baisser la précision après multiplication 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

donc j'ai écrit un petit programme C++ pour voir si c'était mon CPU tronquant le nombre de virgule flottante ou python en quelque sorte.

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

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

qui produit:

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 suis en train de courir contre les limites de la précision de la virgule flottante et le CPU coupe les bits restants parce qu'il pense que la différence restante est une erreur de virgule flottante. Est-il un moyen de contourner ce sous Python? Ou est-ce que je dois passer à C et utiliser GMP ou quelque chose?

37
demandé sur OmnipotentEntity 2012-07-17 16:54:48

5 réponses

dans la bibliothèque standard, le module decimal peut être ce que vous recherchez. En outre, j'ai trouvé mpmath pour être très utile. Le documentation a beaucoup de grands exemples aussi bien (malheureusement mon ordinateur de bureau n'a pas mpmath installé; sinon je vérifierais quelques exemples et les posterais).

Une mise en garde au sujet de la decimal module, 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 produit les deux premières lignes

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

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

34
répondu Ricardo Altamirano 2015-02-05 09:19:47

pour ce problème particulier, decimal est une excellente façon d'aller, parce qu'il stocke les chiffres décimaux comme tuples!

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

comme vous recherchez une propriété qui s'exprime le plus naturellement en notation décimale, c'est un peu bête d'utiliser une représentation binaire. La page Wikipédia à laquelle vous avez fait référence n'indiquait pas combien de "chiffres non greffés" peuvent apparaître avant que les" chiffres greffés "commencent, donc cela vous permet de 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 une bonne chance que le résultat de Decimal.sqrt() sera plus précis, au moins pour cela, que le résultat de math.sqrt() en raison de la conversion entre la représentation binaire et la représentation décimale. Prenons l'exemple suivant:

>>> 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
répondu senderle 2012-07-17 13:46:18

vous pouvez essayer avec décimal au lieu de floatingpoint.

7
répondu f p 2012-07-17 13:04:23

Python n'a pas de flotteurs de précision arbitraires intégrés, mais il existe des paquets Python tiers qui utilisent GMP: gmpy et PyGMP .

5
répondu Ned Batchelder 2012-07-17 13:02:49

utiliser 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
répondu U9-Forward 2018-09-18 01:29:41