我已将其输入python shell:
>>> 0.1*0.1
0.010000000000000002
我预计0.1 * 0.1不是0.01,因为我知道基数10中的0.1在基数2中是周期性的。
>>> len(str(0.1*0.1))
4
我预计会得到20分,因为我看过20个字符。为什么我会得到4?
>>> str(0.1*0.1)
'0.01'
好的,这解释了为什么我len
给了我4,但为什么str
会返回'0.01'
?
>>> repr(0.1*0.1)
'0.010000000000000002'
为什么str
轮,但repr
没有? (我已阅读this answer,但我想知道他们在str
轮次浮动时以及何时没有决定
>>> str(0.01) == str(0.0100000000001)
False
>>> str(0.01) == str(0.01000000000001)
True
所以它似乎是浮子的准确性问题。我以为Python会使用IEEE 754单精度浮点数。所以我已经这样检查过了:
#include <stdint.h>
#include <stdio.h> // printf
union myUnion {
uint32_t i; // unsigned integer 32-bit type (on every machine)
float f; // a type you want to play with
};
int main() {
union myUnion testVar;
testVar.f = 0.01000000000001f;
printf("%f\n", testVar.f);
testVar.f = 0.01000000000000002f;
printf("%f\n", testVar.f);
testVar.f = 0.01f*0.01f;
printf("%f\n", testVar.f);
}
我得到了:
0.010000
0.010000
0.000100
Python给了我:
>>> 0.01000000000001
0.010000000000009999
>>> 0.01000000000000002
0.010000000000000019
>>> 0.01*0.01
0.0001
为什么Python会给我这些结果?
(我使用Python 2.6.5。如果你知道Python版本的差异,我也会对它们感兴趣。)
答案 0 :(得分:14)
repr
的关键要求是它应该往返;也就是说,eval(repr(f)) == f
在所有情况下都应该True
。
在Python 2.x中(在2.7之前)repr
通过执行格式为printf
的{{1}}并丢弃尾随零来工作。这保证了IEEE-754的正确(对于64位浮点数)。从2.7和3.1开始,Python使用更智能的算法,在%.17g
给出不必要的非零终端数字或终端9的某些情况下,可以找到更短的表示。请参阅What's new in 3.1?和issue 1580。
即使在Python 2.7下,%.17g
也会提供repr(0.1 * 0.1)
。这是因为"0.010000000000000002"
在IEEE-754解析和算术下是0.1 * 0.1 == 0.01
;也就是说,当与False
相乘时,最接近的64位浮点值与自身相乘,产生一个64位浮点值,该值不是最接近{{1}的64位浮点值}:
0.1
0.01
和>>> 0.1.hex()
'0x1.999999999999ap-4'
>>> (0.1 * 0.1).hex()
'0x1.47ae147ae147cp-7'
>>> 0.01.hex()
'0x1.47ae147ae147bp-7'
^ 1 ulp difference
(2.7 / 3.1之前)之间的差异是repr
格式有12位小数而不是17位,这是非圆形的,但是产生在许多情况下,结果更具可读性。
答案 1 :(得分:5)
我可以确认你的行为
ActivePython 2.6.4.10 (ActiveState Software Inc.) based on
Python 2.6.4 (r264:75706, Jan 22 2010, 17:24:21) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> repr(0.1)
'0.10000000000000001'
>>> repr(0.01)
'0.01'
现在,Python中的文档claim&lt; 2.7
repr(1.1)
的值计算为format(1.1, '.17g')
这是一个小小的简化。
请注意,这与字符串格式化代码有关 - 在内存中,所有Python浮点数都只是存储为C ++双精度数,因此它们之间永远不会有区别。 / p>
此外,即使您知道有一个更好的字符串,使用浮动的全长字符串也是一种令人不愉快的事情。实际上,在现代Pythons中,一种新算法用于浮点格式化,以智能方式选择最短的表示。
我花了一些时间在源代码中查找,所以我会在这里包含详细信息,以防您感兴趣。您可以跳过此部分。
在floatobject.c
中,我们看到了
static PyObject *
float_repr(PyFloatObject *v)
{
char buf[100];
format_float(buf, sizeof(buf), v, PREC_REPR);
return PyString_FromString(buf);
}
让我们看看format_float
。省略NaN / inf特殊情况,它是:
format_float(char *buf, size_t buflen, PyFloatObject *v, int precision)
{
register char *cp;
char format[32];
int i;
/* Subroutine for float_repr and float_print.
We want float numbers to be recognizable as such,
i.e., they should contain a decimal point or an exponent.
However, %g may print the number as an integer;
in such cases, we append ".0" to the string. */
assert(PyFloat_Check(v));
PyOS_snprintf(format, 32, "%%.%ig", precision);
PyOS_ascii_formatd(buf, buflen, format, v->ob_fval);
cp = buf;
if (*cp == '-')
cp++;
for (; *cp != '\0'; cp++) {
/* Any non-digit means it's not an integer;
this takes care of NAN and INF as well. */
if (!isdigit(Py_CHARMASK(*cp)))
break;
}
if (*cp == '\0') {
*cp++ = '.';
*cp++ = '0';
*cp++ = '\0';
return;
}
<some NaN/inf stuff>
}
我们可以看到
所以这首先初始化一些变量并检查v
是一个格式正确的浮点数。然后它准备一个格式字符串:
PyOS_snprintf(format, 32, "%%.%ig", precision);
现在PREC_REPR在floatobject.c
中的其他位置定义为17,因此计算为"%.17g"
。现在我们打电话
PyOS_ascii_formatd(buf, buflen, format, v->ob_fval);
随着隧道尽头的结束,我们会查找PyOS_ascii_formatd
并发现它在内部使用snprintf
。
答案 2 :(得分:0)
在Python 2.7和Python 3.1之前的版本中,Python将此值四舍五入为17位有效数字,得到
‘0.10000000000000001’
。在当前版本中,Python显示一个基于最小小数的值,该小数正确地回滚到真正的二进制值,仅在‘0.1’
中生成。