处理 Python 中非常非常小的数字
Handling very very small numbers in Python
我需要乘以 1e6
个数量级为 0.01
的数字。预期结果的顺序是 1e-100000000
。显然,典型的浮点运算无法处理这个问题。
在网络上进行一些研究后,我发现 decimal library 似乎可以解决此问题。然而,它似乎有一些限制,无法满足我的需要:
>>> Decimal('.01')**Decimal('1e5') # Seems to handle this
Decimal('1E-200000')
>>> Decimal('.01')**Decimal('1e5')*Decimal('1E200000') # Yeah! It works!
Decimal('1')
>>> Decimal('.01')**Decimal('1e6') # This result is strange...
Decimal('0E-1000026')
>>> Decimal('.01')**Decimal('1e6')*Decimal('0E1000026') # Wrong result
Decimal('0')
有人知道解决这个问题的方法吗?
你的结果不正确,因为小数也有精度(小数是定点数学),所以你在这里也遇到下溢问题:
Decimal('.01')**Decimal('1e6')
Decimal('0E-1000026')
但是:
getcontext().prec = 1000000000 # sets precision to 1000000000
Decimal('.01')**Decimal('1e6')
Decimal('1E-2000000')
您可以通过手动设置精度来解决您的问题,如上例所示或手动计算幂,例如:
Decimal('.01')**Decimal('1e6')
可以转换为
Decimal('1e-2') ** Decimal('1e6')
以后到
1 ** ((-2) ** 1e6) = 1 ** (-2000000)
为什么不用对数?
您想计算:
RESULT = x1 * x2 * x3 * x4 ... * xn
表示为:
ln(RESULT) = ln(x1) + ln(x2) + ln(x3) + ln(x4) ... + ln(xn)
如果存储自然对数,非常小的正数可以很好地存储到浮点数中:
ln(0.000001) ≈ -13.81551
不是存储数字本身,而是存储值的对数。
假设您将 ln(0.0000011)
添加到自身 10^6
次。你得到大约 -13815510.558
。 float
比 0.000001^(10^6)
丢失的精度更低
无论你最后得到什么数字,你都知道你的结果就是数字 e
的那个次方。例如,RESULT = e^-13815510.558
您可以使用以下代码:
import math
class TinyNum:
def __init__(self, other=None, *, pow=None):
"""
x = TinyNum(0.0000912922)
x = TinyNum("0.12345") # strings are okay too
x = TinyNum(pow = -110) # e^-110
y = TinyNum(x) # copy constructor
"""
if other:
if isinstance(other, type(self)):
self._power = other._power
else:
self._power = math.log(float(str(other)))
else: # other == None
self._power = float(str(pow))
def __str__(self):
return "e^"+str(self._power)
def __mul__(lhs, rhs):
rhs = type(lhs)(rhs)
return type(lhs)(pow=lhs._power + rhs._power)
def __rmul__(rhs, lhs):
lhs = type(rhs)(lhs)
return type(rhs)(pow=lhs._power + rhs._power)
def __imul__(total, margin):
total._power = total._power + type(total)(margin)._power
lyst = [
0.00841369,
0.004766949,
0.003188046,
0.002140916,
0.004780032
]
sneaky_lyst = map(TinyNum, lyst)
print(math.prod(sneaky_lyst))
打印到控制台的消息是:
e^-27.36212057035477
我需要乘以 1e6
个数量级为 0.01
的数字。预期结果的顺序是 1e-100000000
。显然,典型的浮点运算无法处理这个问题。
在网络上进行一些研究后,我发现 decimal library 似乎可以解决此问题。然而,它似乎有一些限制,无法满足我的需要:
>>> Decimal('.01')**Decimal('1e5') # Seems to handle this
Decimal('1E-200000')
>>> Decimal('.01')**Decimal('1e5')*Decimal('1E200000') # Yeah! It works!
Decimal('1')
>>> Decimal('.01')**Decimal('1e6') # This result is strange...
Decimal('0E-1000026')
>>> Decimal('.01')**Decimal('1e6')*Decimal('0E1000026') # Wrong result
Decimal('0')
有人知道解决这个问题的方法吗?
你的结果不正确,因为小数也有精度(小数是定点数学),所以你在这里也遇到下溢问题:
Decimal('.01')**Decimal('1e6')
Decimal('0E-1000026')
但是:
getcontext().prec = 1000000000 # sets precision to 1000000000
Decimal('.01')**Decimal('1e6')
Decimal('1E-2000000')
您可以通过手动设置精度来解决您的问题,如上例所示或手动计算幂,例如:
Decimal('.01')**Decimal('1e6')
可以转换为
Decimal('1e-2') ** Decimal('1e6')
以后到
1 ** ((-2) ** 1e6) = 1 ** (-2000000)
为什么不用对数?
您想计算:
RESULT = x1 * x2 * x3 * x4 ... * xn
表示为:
ln(RESULT) = ln(x1) + ln(x2) + ln(x3) + ln(x4) ... + ln(xn)
如果存储自然对数,非常小的正数可以很好地存储到浮点数中:
ln(0.000001) ≈ -13.81551
不是存储数字本身,而是存储值的对数。
假设您将 ln(0.0000011)
添加到自身 10^6
次。你得到大约 -13815510.558
。 float
比 0.000001^(10^6)
无论你最后得到什么数字,你都知道你的结果就是数字 e
的那个次方。例如,RESULT = e^-13815510.558
您可以使用以下代码:
import math
class TinyNum:
def __init__(self, other=None, *, pow=None):
"""
x = TinyNum(0.0000912922)
x = TinyNum("0.12345") # strings are okay too
x = TinyNum(pow = -110) # e^-110
y = TinyNum(x) # copy constructor
"""
if other:
if isinstance(other, type(self)):
self._power = other._power
else:
self._power = math.log(float(str(other)))
else: # other == None
self._power = float(str(pow))
def __str__(self):
return "e^"+str(self._power)
def __mul__(lhs, rhs):
rhs = type(lhs)(rhs)
return type(lhs)(pow=lhs._power + rhs._power)
def __rmul__(rhs, lhs):
lhs = type(rhs)(lhs)
return type(rhs)(pow=lhs._power + rhs._power)
def __imul__(total, margin):
total._power = total._power + type(total)(margin)._power
lyst = [
0.00841369,
0.004766949,
0.003188046,
0.002140916,
0.004780032
]
sneaky_lyst = map(TinyNum, lyst)
print(math.prod(sneaky_lyst))
打印到控制台的消息是:
e^-27.36212057035477