浮点数是否支持任意精度?

66
只是为了好玩,而且这个任务非常简单,我写了一个短小的程序来生成嫁接数,但由于浮点精度问题,它无法找到一些较大的例子。
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

这段代码缺少至少一个已知的嫁接数。9999999998 => 99999.99998999999999949999999994999999999374999999912... 在乘以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

所以我写了一个简短的C++程序来查看是不是我的CPU截断了浮点数,还是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;
}

这将输出:

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

看起来我正在遇到浮点精度的限制,CPU正在截断剩余的位数,因为它认为剩余的差异是浮点误差。在Python下有没有解决这个问题的方法?还是我需要转向C语言并使用GMP或其他工具?

3
要对有理数进行精确的算术运算,可以使用“fractions”模块。 - jfs
5个回答

66
在标准库中,decimal 模块可能是你正在寻找的。此外,我发现 mpmath 对我很有帮助。该文档也有许多很好的例子(不幸的是,我的办公电脑没有安装mpmath;否则我会验证一些示例并发布它们)。
但是,关于 decimal 模块,需要注意一点。该模块包含了一些用于简单数学运算(例如sqrt)的内置函数,但是这些函数的结果在更高精度下可能与math或其他模块中相应函数的结果不完全匹配(虽然它们可能更准确)。例如,
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()))

在Python 3.2.3中,这将输出前两行。

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

正如所述,这并不是您所期望的,而且您可以看到,精度越高,结果就越不匹配。请注意,decimal 模块在此示例中确实具有更高的准确性,因为它更接近实际值


5
对于 mpmath 给予赞赏。使用 Decimal 数字的问题在于你无法对 Decimal 对象执行太多数学函数,因此如果只是随意尝试,这会十分限制。 - DSM
4
只是明确一下——我相当确定在你对 math.sqrtDecimal.sqrt() 进行测试时,由于二进制到十进制的转换,math.sqrt 产生的结果更加不精确。考虑 decimal.Decimal(math.sqrt(num) ** 2) * 7decimal.Decimal(num.sqrt() ** 2) * 7 的输出。请注意保持原文意思不变,使翻译通俗易懂。 - senderle
5
考虑到sqrt(1/7)的实际值为0.377964473009227227214516536234180060815751311868921454338333494171581260461469089680056126639220515802...,似乎小数点后面的平方根函数更准确。 - OmnipotentEntity
7
与其使用Decimal(math.sqrt(num)),你只需使用num.sqrt()Decimal(math.sqrt(num))从低精度的数学函数构建一个Decimal对象,而不是进行高精度的平方根计算。 - DSM
5
嗯...如果“实际值”比那个词组本身更长,那么你不能把它写作“实际值”。 - nonopolarity
显示剩余4条评论

11
对于这个特定的问题,decimal是一个很好的选择,因为它将十进制数字存储为元组!

对于这个特定的问题,decimal是一个很好的选择,因为它以元组形式存储十进制位数!

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

既然您正在寻找一种最自然表达为十进制符号的属性,那么使用二进制表示有点傻。您链接的维基百科页面没有指出在“移植数字”开始之前可能会出现多少个“非移植数字”,因此这使您可以进行指定:

>>> 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

我认为使用Decimal.sqrt()的结果会比math.sqrt()更准确,至少对于这个情况来说是这样的,因为涉及到二进制和十进制之间的转换。例如,考虑以下内容:

>>> 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')

9
您可以尝试使用Decimal代替浮点数。

6

Python没有内置的任意精度浮点数,但有第三方Python包使用GMP: gmpyPyGMP


3

使用 decimal 数据类型,(这里有一个更清晰的示例):

>>> 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
>>> 

Decimal('0.1') != Decimal(0.1) - Smart Manoj
@SmartManoj 我刚刚在3.11.2、3.10.11和2.7中尝试了一下,结果返回了False。 - Jürgen A. Erhard
@JürgenA.Erhard https://imgur.com/a/oTGnwec - Smart Manoj
哦,我把 Decimal(0.1) 读成 Decimal('0.1') 了,我的错,抱歉。 - Jürgen A. Erhard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接