使用浮点数的计算结果不准确 - 简单的解决方案
inaccurate results for calculations using floats - Simple solution
关于 Python 与使用浮点数的计算的混淆行为,人们在 Whosebug 和其他地方提出了许多问题 - 通常会返回一个明显有少量错误的结果。 explanation for this 总是链接到。然而,通常不会提供实用的简单解决方案。
这不仅仅是错误(通常可以忽略不计) - 它更多的是获得像 3.999999999999999
这样的结果而得到像 8.7 - 4.7
.[=15 这样的简单总和的混乱和不优雅=]
我已经为此编写了一个简单的解决方案,我的问题是,为什么 Python 在幕后 不会自动实现这样的东西?
基本概念是将所有浮点数转换为整数,进行运算,然后适当地转换回浮点数。上面链接的文档中解释的困难仅适用于浮点数,不适用于整数,这就是它起作用的原因。这是代码:
def justwork(x,operator,y):
numx = numy = 0
if "." in str(x):
numx = len(str(x)) - str(x).find(".") -1
if "." in str(y):
numy = len(str(y)) - str(y).find(".") -1
num = max(numx,numy)
factor = 10 ** num
newx = x * factor
newy = y * factor
if operator == "%":
ans1 = x % y
ans = (newx % newy) / factor
elif operator == "*":
ans1 = x * y
ans = (newx * newy) / (factor**2)
elif operator == "-":
ans1 = x - y
ans = (newx - newy) / factor
elif operator == "+":
ans1 = x + y
ans = (newx + newy) / factor
elif operator == "/":
ans1 = x / y
ans = (newx / newy)
elif operator == "//":
ans1 = x // y
ans = (newx // newy)
return (ans, ans1)
诚然,这有点不雅,可能可以通过一些思考加以改进,但是 它完成了工作。函数 returns 具有正确结果(通过转换为整数)和错误结果(自动提供)的元组。以下是如何提供准确结果的示例,而不是正常执行。
#code #returns tuple with (correct, incorrect) result
print(justwork(0.7,"%",0.1)) #(0.0, 0.09999999999999992)
print(justwork(0.7,"*",0.1)) #(0.07, 0.06999999999999999)
print(justwork(0.7,"-",0.2)) #(0.5, 0.49999999999999994)
print(justwork(0.7,"+",0.1)) #(0.8, 0.7999999999999999)
print(justwork(0.7,"/",0.1)) #(7.0, 6.999999999999999)
print(justwork(0.7,"//",0.1)) #(7.0, 6.0)
TLDR:本质上的问题是,当浮点数可以像整数一样存储(这只是工作)时,为什么将浮点数存储为以 2 为底的二进制分数(本质上是不精确的)?
三分:
- 提出的question/general方法中的函数,虽然在很多情况下确实避免了这个问题,但也有很多其他情况,即使是比较简单的情况,也有同样的问题。
- 有一个
decimal
模块 始终 提供准确的答案(即使问题中的 justwork()
功能失败)
- 使用
decimal
模块会大大减慢速度 - 大约需要 100 倍的时间。默认方法牺牲准确性以优先考虑速度。 [将此设置为默认值是否是正确的做法值得商榷]。
为了说明这三点,考虑以下函数,大致基于问题中的函数:
def justdoesntwork(x,operator,y):
numx = numy = 0
if "." in str(x):
numx = len(str(x)) - str(x).find(".") -1
if "." in str(y):
numy = len(str(y)) - str(y).find(".") -1
factor = 10 ** max(numx,numy)
newx = x * factor
newy = y * factor
if operator == "+": myAns = (newx + newy) / factor
elif operator == "-": myAns = (newx - newy) / factor
elif operator == "*": myAns = (newx * newy) / (factor**2)
elif operator == "/": myAns = (newx / newy)
elif operator == "//": myAns = (newx //newy)
elif operator == "%": myAns = (newx % newy) / factor
return myAns
和
from decimal import Decimal
def doeswork(x,operator,y):
if operator == "+": decAns = Decimal(str(x)) + Decimal(str(y))
elif operator == "-": decAns = Decimal(str(x)) - Decimal(str(y))
elif operator == "*": decAns = Decimal(str(x)) * Decimal(str(y))
elif operator == "/": decAns = Decimal(str(x)) / Decimal(str(y))
elif operator == "//": decAns = Decimal(str(x)) //Decimal(str(y))
elif operator == "%": decAns = Decimal(str(x)) % Decimal(str(y))
return decAns
然后遍历许多值以找到 myAns
与 decAns
不同的地方:
operatorlist = ["+", "-", "*", "/", "//", "%"]
for a in range(1,1000):
x = a/10
for b in range(1,1000):
y=b/10
counter = 0
for operator in operatorlist:
myAns, decAns = justdoesntwork(x, operator, y), doeswork(x, operator, y)
if (float(decAns) != myAns) and len(str(decAns)) < 5 :
print(x,"\t", operator, " \t ", y, " \t= ", decAns, "\t\t{", myAns, "}")
=> 这会遍历所有值到 1 d.p。从 0.1 到 99.9 - 确实找不到 myAns
不同于 decAns
.
的任何值
不过如果改成给2d.p。 (即 x = a/100
或 y = b/100
),然后会出现许多示例。例如,0.1+1.09
- 这可以通过在控制台中键入 ((0.1*100)+(1.09*100)) / (100)
轻松检查,它使用问题的基本方法,其中 returns 1.1900000000000002
而不是 1.19
。错误的来源在 1.09*100
其中 returns 109.00000000000001
。 [简单地输入 0.1+1.09
也会给出同样的错误]。所以问题中建议的方法并不总是有效。
但使用 Decimal() returns 正确答案:Decimal('0.1')+Decimal('1.09')
returns Decimal('1.19')
.
[注意:不要忘记用引号将 0.1 和 1.09 括起来。如果不这样做,Decimal(0.1)+Decimal(1.09)
returns Decimal('1.190000000000000085487172896')
- 因为它以浮点数 0.1 开头,存储不准确,然后将 that 转换为 Decimal - 吉戈。 Decimal() 必须输入一个字符串。取一个浮点数,将其转换为字符串,然后从那里转换为十进制,这似乎确实有效,但问题仅在直接从浮点数转换为十进制时出现。
在时间成本方面,运行这个:
import timeit
operatorlist = ["+", "-", "*", "/", "//", "%"]
for operator in operatorlist:
for a in range(1,10):
a=a/10
for b in range(1,10):
b=b/10
DECtime = timeit.timeit("Decimal('" +str(a)+ "') " +operator+ " Decimal('" +str(b)+ "')", setup="from decimal import Decimal")
NORMtime = timeit.timeit(str(a) +operator+ str(b))
timeslonger = DECtime // NORMtime
print("Operation: ", str(a) +operator +str(b) , "\tNormal operation time: ", NORMtime, "\tDecimal operation time: ", DECtime, "\tSo Decimal operation took ", timeslonger, " times longer")
这表明,对于所有测试的运算符,小数运算始终需要大约 100 倍的时间。
[在运算符列表中包括求幂表明求幂可能需要 3000 - 5000 倍的时间。然而,这部分是因为 Decimal() 的计算精度远高于正常操作 - Decimal() 默认精度为 28 位 - Decimal("1.5")**Decimal("1.5")
returns 1.837117307087383573647963056
,而 1.5**1.5
returns 1.8371173070873836
。如果通过将 b=b/10
替换为 b=float(b)
来将 b
限制为整数(这将防止结果具有高 SF),与其他运算符一样,小数计算需要大约 100 倍的时间]。 =41=]
仍然有人认为,时间成本仅对执行数十亿次计算的用户来说很重要,并且大多数用户会优先考虑获得可理解的结果,而不是在大多数适度应用程序中微不足道的时间差异。
关于 Python 与使用浮点数的计算的混淆行为,人们在 Whosebug 和其他地方提出了许多问题 - 通常会返回一个明显有少量错误的结果。 explanation for this 总是链接到。然而,通常不会提供实用的简单解决方案。
这不仅仅是错误(通常可以忽略不计) - 它更多的是获得像 3.999999999999999
这样的结果而得到像 8.7 - 4.7
.[=15 这样的简单总和的混乱和不优雅=]
我已经为此编写了一个简单的解决方案,我的问题是,为什么 Python 在幕后 不会自动实现这样的东西?
基本概念是将所有浮点数转换为整数,进行运算,然后适当地转换回浮点数。上面链接的文档中解释的困难仅适用于浮点数,不适用于整数,这就是它起作用的原因。这是代码:
def justwork(x,operator,y):
numx = numy = 0
if "." in str(x):
numx = len(str(x)) - str(x).find(".") -1
if "." in str(y):
numy = len(str(y)) - str(y).find(".") -1
num = max(numx,numy)
factor = 10 ** num
newx = x * factor
newy = y * factor
if operator == "%":
ans1 = x % y
ans = (newx % newy) / factor
elif operator == "*":
ans1 = x * y
ans = (newx * newy) / (factor**2)
elif operator == "-":
ans1 = x - y
ans = (newx - newy) / factor
elif operator == "+":
ans1 = x + y
ans = (newx + newy) / factor
elif operator == "/":
ans1 = x / y
ans = (newx / newy)
elif operator == "//":
ans1 = x // y
ans = (newx // newy)
return (ans, ans1)
诚然,这有点不雅,可能可以通过一些思考加以改进,但是 它完成了工作。函数 returns 具有正确结果(通过转换为整数)和错误结果(自动提供)的元组。以下是如何提供准确结果的示例,而不是正常执行。
#code #returns tuple with (correct, incorrect) result
print(justwork(0.7,"%",0.1)) #(0.0, 0.09999999999999992)
print(justwork(0.7,"*",0.1)) #(0.07, 0.06999999999999999)
print(justwork(0.7,"-",0.2)) #(0.5, 0.49999999999999994)
print(justwork(0.7,"+",0.1)) #(0.8, 0.7999999999999999)
print(justwork(0.7,"/",0.1)) #(7.0, 6.999999999999999)
print(justwork(0.7,"//",0.1)) #(7.0, 6.0)
TLDR:本质上的问题是,当浮点数可以像整数一样存储(这只是工作)时,为什么将浮点数存储为以 2 为底的二进制分数(本质上是不精确的)?
三分:
- 提出的question/general方法中的函数,虽然在很多情况下确实避免了这个问题,但也有很多其他情况,即使是比较简单的情况,也有同样的问题。
- 有一个
decimal
模块 始终 提供准确的答案(即使问题中的justwork()
功能失败) - 使用
decimal
模块会大大减慢速度 - 大约需要 100 倍的时间。默认方法牺牲准确性以优先考虑速度。 [将此设置为默认值是否是正确的做法值得商榷]。
为了说明这三点,考虑以下函数,大致基于问题中的函数:
def justdoesntwork(x,operator,y):
numx = numy = 0
if "." in str(x):
numx = len(str(x)) - str(x).find(".") -1
if "." in str(y):
numy = len(str(y)) - str(y).find(".") -1
factor = 10 ** max(numx,numy)
newx = x * factor
newy = y * factor
if operator == "+": myAns = (newx + newy) / factor
elif operator == "-": myAns = (newx - newy) / factor
elif operator == "*": myAns = (newx * newy) / (factor**2)
elif operator == "/": myAns = (newx / newy)
elif operator == "//": myAns = (newx //newy)
elif operator == "%": myAns = (newx % newy) / factor
return myAns
和
from decimal import Decimal
def doeswork(x,operator,y):
if operator == "+": decAns = Decimal(str(x)) + Decimal(str(y))
elif operator == "-": decAns = Decimal(str(x)) - Decimal(str(y))
elif operator == "*": decAns = Decimal(str(x)) * Decimal(str(y))
elif operator == "/": decAns = Decimal(str(x)) / Decimal(str(y))
elif operator == "//": decAns = Decimal(str(x)) //Decimal(str(y))
elif operator == "%": decAns = Decimal(str(x)) % Decimal(str(y))
return decAns
然后遍历许多值以找到 myAns
与 decAns
不同的地方:
operatorlist = ["+", "-", "*", "/", "//", "%"]
for a in range(1,1000):
x = a/10
for b in range(1,1000):
y=b/10
counter = 0
for operator in operatorlist:
myAns, decAns = justdoesntwork(x, operator, y), doeswork(x, operator, y)
if (float(decAns) != myAns) and len(str(decAns)) < 5 :
print(x,"\t", operator, " \t ", y, " \t= ", decAns, "\t\t{", myAns, "}")
=> 这会遍历所有值到 1 d.p。从 0.1 到 99.9 - 确实找不到 myAns
不同于 decAns
.
不过如果改成给2d.p。 (即 x = a/100
或 y = b/100
),然后会出现许多示例。例如,0.1+1.09
- 这可以通过在控制台中键入 ((0.1*100)+(1.09*100)) / (100)
轻松检查,它使用问题的基本方法,其中 returns 1.1900000000000002
而不是 1.19
。错误的来源在 1.09*100
其中 returns 109.00000000000001
。 [简单地输入 0.1+1.09
也会给出同样的错误]。所以问题中建议的方法并不总是有效。
但使用 Decimal() returns 正确答案:Decimal('0.1')+Decimal('1.09')
returns Decimal('1.19')
.
[注意:不要忘记用引号将 0.1 和 1.09 括起来。如果不这样做,Decimal(0.1)+Decimal(1.09)
returns Decimal('1.190000000000000085487172896')
- 因为它以浮点数 0.1 开头,存储不准确,然后将 that 转换为 Decimal - 吉戈。 Decimal() 必须输入一个字符串。取一个浮点数,将其转换为字符串,然后从那里转换为十进制,这似乎确实有效,但问题仅在直接从浮点数转换为十进制时出现。
在时间成本方面,运行这个:
import timeit
operatorlist = ["+", "-", "*", "/", "//", "%"]
for operator in operatorlist:
for a in range(1,10):
a=a/10
for b in range(1,10):
b=b/10
DECtime = timeit.timeit("Decimal('" +str(a)+ "') " +operator+ " Decimal('" +str(b)+ "')", setup="from decimal import Decimal")
NORMtime = timeit.timeit(str(a) +operator+ str(b))
timeslonger = DECtime // NORMtime
print("Operation: ", str(a) +operator +str(b) , "\tNormal operation time: ", NORMtime, "\tDecimal operation time: ", DECtime, "\tSo Decimal operation took ", timeslonger, " times longer")
这表明,对于所有测试的运算符,小数运算始终需要大约 100 倍的时间。
[在运算符列表中包括求幂表明求幂可能需要 3000 - 5000 倍的时间。然而,这部分是因为 Decimal() 的计算精度远高于正常操作 - Decimal() 默认精度为 28 位 - Decimal("1.5")**Decimal("1.5")
returns 1.837117307087383573647963056
,而 1.5**1.5
returns 1.8371173070873836
。如果通过将 b=b/10
替换为 b=float(b)
来将 b
限制为整数(这将防止结果具有高 SF),与其他运算符一样,小数计算需要大约 100 倍的时间]。 =41=]
仍然有人认为,时间成本仅对执行数十亿次计算的用户来说很重要,并且大多数用户会优先考虑获得可理解的结果,而不是在大多数适度应用程序中微不足道的时间差异。