0x5*_*539 37 python floating-point rounding python-2.7 python-3.x
我知道在浮点运算中会发生舍入错误,但有人可以解释这个问题的原因:
>>> 8.0 / 0.4 # as expected
20.0
>>> floor(8.0 / 0.4) # int works too
20
>>> 8.0 // 0.4 # expecting 20.0
19.0
Run Code Online (Sandbox Code Playgroud)
这在x64上的Python 2和3上都会发生.
据我所知,这是一个错误或非常愚蠢的规范,//因为我没有看到为什么最后一个表达式应该评估的原因19.0.
为什么a // b不简单定义为floor(a / b)?
编辑:8.0 % 0.4也评估为0.3999999999999996.至少这是因为随后8.0 // 0.4 * 0.4 + 8.0 % 0.4评估的结果8.0
编辑:这不是浮点数学的重复吗?因为我问为什么这个特定的操作受到(可能是可以避免的)舍入错误的影响,以及为什么a // b没有定义为/等于floor(a / b)
备注:我猜这个不起作用的深层原因是地板划分是不连续的,因此具有无限的条件数使其成为一个不适定的问题.地板划分和浮点数简单地基本上是不兼容的,你永远不应该使用//浮点数.只需使用整数或分数.
das*_*s-g 25
正如你和khelwood已经注意到的那样,0.4不能完全表现为浮动.为什么?它是五分之二(4/10 == 2/5),没有有限的二进制分数表示.
试试这个:
from fractions import Fraction
Fraction('8.0') // Fraction('0.4')
# or equivalently
# Fraction(8, 1) // Fraction(2, 5)
# or
# Fraction('8/1') // Fraction('2/5')
# 20
Run Code Online (Sandbox Code Playgroud)
然而
Fraction('8') // Fraction(0.4)
# 19
Run Code Online (Sandbox Code Playgroud)
在这里,0.4被解释为一个浮动文本(以及因此浮点二进制数)需要(二进制)的舍入,和只然后转化为有理数Fraction(3602879701896397, 9007199254740992),这几乎是但不完全4/10.然后执行地板划分,因为
19 * Fraction(3602879701896397, 9007199254740992) < 8.0
Run Code Online (Sandbox Code Playgroud)
和
20 * Fraction(3602879701896397, 9007199254740992) > 8.0
Run Code Online (Sandbox Code Playgroud)
结果是19,而不是20.
同样可能发生
8.0 // 0.4
Run Code Online (Sandbox Code Playgroud)
也就是说,似乎浮动除法是原子地确定的(但是在解释的浮点文字的唯一近似浮点值上).
那么为什么呢
floor(8.0 / 0.4)
Run Code Online (Sandbox Code Playgroud)
给出"正确"的结果?因为那里,两个舍入误差相互抵消.首先1)执行除法,产生略小于20.0的值,但不能表示为浮点数.它会四舍五入到最近的浮点数,这恰好是20.0.只有然后,将floor执行的操作,但现在作用于准确 20.0,因此不换号了.
1)由于凯尔-斯特兰德指出,该确切的结果确定则四舍五入不是什么实际发生的低2) -电平(CPython中的C代码,甚至CPU指令).然而,它可以是用于确定预期的一个有用的模型3)的结果.
2)然而,在最低4级,这可能不会太远.一些芯片组通过首先计算更精确(但仍然不精确,简单地具有更多二进制数字)内部浮点结果然后舍入到IEEE双精度来确定浮点结果.
3) Python规范的"预期",不一定是我们的直觉.
4)那么,逻辑门之上的最低级别.我们不必考虑使半导体有可能理解这一点的量子力学.
shi*_*iva 11
@jotasi解释了背后的真正原因.
但是,如果要阻止它,可以使用decimal基本上设计为表示十进制浮点数的模块,与二进制浮点表示形成对比.
所以在你的情况下,你可以这样做:
>>> from decimal import *
>>> Decimal('8.0')//Decimal('0.4')
Decimal('20')
Run Code Online (Sandbox Code Playgroud)
参考: https ://docs.python.org/2/library/decimal.html
0x5*_*539 10
经过一些研究后我发现了这个问题.似乎正在发生的事情是,正如@khelwood建议在0.4内部进行评估0.40000000000000002220,当分割8.0得到的东西略小于20.0.该/操作者然后舍入为最接近的浮点数,其是20.0,但//操作者立即截断结果,得到19.0.
这应该更快,我认为它"接近处理器",但我仍然不是用户想要/期望的.
那是因为在python(浮点有限表示)中没有0.4它实际上是一个浮点数0.4000000000000001,这使得除法的底限为19.
>>> floor(8//0.4000000000000001)
19.0
Run Code Online (Sandbox Code Playgroud)
但是如果参数是浮点数或复数,则真正的除法(/)返回除法结果的合理近似值.这就是结果8.0/0.4为20 的原因.它实际上取决于参数的大小(在C双参数中).(不舍入到最近的浮点数)
阅读Guido本人更多关于蟒蛇整数分区的信息.
另外,有关浮点数的完整信息,请阅读本文https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
对于那些有兴趣的人,以下函数是float_div在Cpython的源代码中执行浮点数的真正除法任务:
float_div(PyObject *v, PyObject *w)
{
double a,b;
CONVERT_TO_DOUBLE(v, a);
CONVERT_TO_DOUBLE(w, b);
if (b == 0.0) {
PyErr_SetString(PyExc_ZeroDivisionError,
"float division by zero");
return NULL;
}
PyFPE_START_PROTECT("divide", return 0)
a = a / b;
PyFPE_END_PROTECT(a)
return PyFloat_FromDouble(a);
}
Run Code Online (Sandbox Code Playgroud)
哪个最终结果将按功能计算PyFloat_FromDouble:
PyFloat_FromDouble(double fval)
{
PyFloatObject *op = free_list;
if (op != NULL) {
free_list = (PyFloatObject *) Py_TYPE(op);
numfree--;
} else {
op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));
if (!op)
return PyErr_NoMemory();
}
/* Inline PyObject_New */
(void)PyObject_INIT(op, &PyFloat_Type);
op->ob_fval = fval;
return (PyObject *) op;
}
Run Code Online (Sandbox Code Playgroud)
在github(https://github.com/python/cpython/blob/966b24071af1b320a1c7646d33474eeae057c20f/Objects/floatobject.c)上查看cpython中浮点对象的半官方来源后,可以理解这里发生了什么.
对于正常除法float_div被调用(第560行),它在内部将python floats 转换为c- doubles,进行除法,然后将得到的结果转换double为python float.如果您只是8.0/0.4在c中执行此操作,您将获得:
#include "stdio.h"
#include "math.h"
int main(){
double vx = 8.0;
double wx = 0.4;
printf("%lf\n", floor(vx/wx));
printf("%d\n", (int)(floor(vx/wx)));
}
// gives:
// 20.000000
// 20
Run Code Online (Sandbox Code Playgroud)
对于场地划分,还会发生其他事情.在内部,float_floor_div(第654行)被调用,然后调用float_divmod一个函数,该函数应该返回float包含内部除法的python s 元组,以及mod /余数,即使后者被抛弃PyTuple_GET_ITEM(t, 0).这些值按以下方式计算(转换为c- doubles后):
double mod = fmod(numerator, denominator).mod当你进行除法时,分子减去得到一个整数值.floor((numerator - mod) / denominator)(numerator - mod) / denominator到最接近的整数值.这给出不同结果的原因是,fmod(8.0, 0.4)由于浮点运算0.4而不是0.0.因此,实际计算的结果floor((8.0 - 0.4) / 0.4) = 19和捕捉(8.0 - 0.4) / 0.4) = 19到最接近的整数值并不能解决由"错误"结果引入的错误fmod.您也可以轻松地在c中查找:
#include "stdio.h"
#include "math.h"
int main(){
double vx = 8.0;
double wx = 0.4;
double mod = fmod(vx, wx);
printf("%lf\n", mod);
double div = (vx-mod)/wx;
printf("%lf\n", div);
}
// gives:
// 0.4
// 19.000000
Run Code Online (Sandbox Code Playgroud)
我猜,他们选择这种计算浮动除法的方式来保持有效性(numerator//divisor)*divisor + fmod(numerator, divisor) = numerator(正如在@ 0x539的答案中的链接中所提到的),即使这现在导致了一些意想不到的行为floor(8.0/0.4) != 8.0//0.4.