Python中将字符串转换为浮点数的标准是什么?

3

对于相对简单的浮点数,数值精度足以准确表示它们。例如,17.5等于17.5。

对于更复杂的浮点数,例如

17.4999999999999982236431605997495353221893310546874 = 17.499999999999996447286321199499070644378662109375
17.4999999999999982236431605997495353221893310546875 = 17.5

使用as_integer_ratio()方法得到第一个数字的比例为(4925812092436479, 281474976710656),因为(4925812092436479*2+1)/(2*281474976710656)等于第二个数字,因此可以明显地看出大于或等于17.5和小于17.5之间的分割点是1/(2*281474976710656)。

Python标准是否保证特定的浮点数会被分到特定的区间?还是这取决于具体实现?如果有保证,它是如何确定的?

对于上述问题,我使用的是Python 3.5.6版本,但如果存在一般答案来适用于Python 3.x。


我猜这取决于 C 编译器使用的 strtod() 版本以及 Python 运行时编译的版本。可能会有轻微的差异。 - Rudy Velthuis
没有官方的保证,但对于CPython来说,你使用的极有可能是IEEE 754二进制64位浮点数,并且代码中的数字字面量会被转换为最接近的可精确表示的浮点数,如果存在并列的情况,则会将其舍入到具有偶数最后一位的值。如果在主流平台上没有发生这种情况,我认为这是一个值得报告给bugs.python.org的错误。令人高兴的是,你展示的值遵循了这个规则。你给出的第二个值恰好处于两个可精确表示的值之间,因此按照舍入至偶数规则进行四舍五入。 - Mark Dickinson
3个回答

2
“对于相对简单的浮点数,数值精度足以准确表示它们。”
并不完全如此。是的,“17.5”可以被准确表示,因为它是2的幂的倍数(确切地说是2的-1次幂的倍数)。但即使是非常简单的浮点数,比如“0.1”,也无法被准确表示。这取决于文本到浮点数转换程序来获得尽可能接近的表示形式。
转换是由运行时(或编译器的C或Java运行时,用于文字)执行的,它使用C或Java函数(例如C的strtod())来执行此操作(Java实现David Gay的strtod()代码,但使用Java语言)。
并非每个strtod()实现,即不是每个C/Java编译器都使用相同的方法进行转换,因此在一些结果中可能会有轻微但通常无关紧要的差异。
供参考,网站Exploring Binary(我与其无关,只是一个热心的粉丝)有很多关于这个主题的文章。显然,这并不像预期的那么简单。

对于CPython,在几乎所有的机器上,转换都不是由C运行时完成的:Python有自己的strtoddtoa实现,基于David Gay的代码,并使用它们。 - Mark Dickinson
@Mark:“通常使用David Gay的代码”。其他代码(Burger / Dybvig等)是轮换代码,即最小输出,如果再次解析,则会产生相同的值。但实际上,许多语言都使用Gay的代码,包括Java、C、C++和Python。 - Rudy Velthuis
我们正在使用的代码也是循环码。我相信Gay的代码是基于你提到的算法。使用Gay的代码的整个目的是实现从浮点数到字符串再到浮点数的往返转换(并获得最小长度的字符串,使其四舍五入为正确的浮点数)。 - Mark Dickinson
据我所知,Gay的代码只关心正确的舍入,而回转输出并不总是正确舍入的。请参见正确值1.100...01的示例,其中较短的回转输出将为1.1。 - Rudy Velthuis
我不确定你指的是哪个具体的例子。Gay的dtoa实现有各种模式,包括正确舍入到小数点后(或前)给定位数,正确舍入到给定数量的有效数字以及最短字符串,该字符串将回舍为给定值。Python使用最后一种模式来进行其repr,并使用前两种模式进行%f%e%g风格的浮点格式化。我对此有些了解,因为我实现了Python对dtoa.c的适应(在此过程中发现了几个dtoa.c错误),并为其编写了广泛的测试。 - Mark Dickinson

1

对于相对简单的浮点数,数字精度足以准确表示它们。

不,即使是简单的小数也不一定有一个精确的IEEE-754表示:

>>> format(0.1, '.20f')
'0.10000000000000000555'
>>> format(0.2, '.20f')
'0.20000000000000001110'
>>> format(0.3, '.20f')
'0.29999999999999998890'
>>> format(0.1 + 0.2, '.20f')
'0.30000000000000004441'

2的幂次方(x.0,x.5,x.25,x.125,...)可以准确表示,除了精度问题。

Python标准保证特定的浮点数会被“分组”到上面的特定组中吗?还是依赖于实现?

我很确定Python只是委托给底层系统,所以它主要依赖于硬件。如果你想要保证,使用decimal。我记得本地(C)实现已经在3.3中合并,因此使用十进制数字的性能影响比在Python 2中低得多。


CPython 至少不会简单地委托给底层系统。 CPython 代码库包括一个名为 dtoa.c 的文件,基于 David Gay 的代码,在大多数机器上实现了浮点数和字符串之间的转换。(免责声明:非 IEEE 754 机器无法使用此代码,但这些机器极为罕见。) - Mark Dickinson
字符串格式化不是问题的主题,只是一种演示IEEE 754浮点数固有精度问题的方式。 - Masklinn
当然可以,但是语句“Pretty sure Python simply delegates to the underlying system, so it's mostly hardware-dependent.”是具有误导性的。至少对于CPython来说,它并不是这样的。 - Mark Dickinson
那个语句是有上下文的,而且上下文是问题的主题,更重要的是在那个语句之前引用的部分。 - Masklinn

0

1
Python将浮点数留给底层平台处理:“您受制于底层机器架构(以及C或Java实现)的接受范围和溢出处理。” 它通常接近于IEEE-754基本64位二进制浮点数,但这并不是Python所保证的,并且它并不总是完全符合IEEE 754。 - Eric Postpischil
@Eric:那差不多就是我写的内容。不过我忘了提到Java实现。 - Rudy Velthuis

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