我的浮点问题 - 在 C++/Python 中试用

问题描述 投票:0回答:2

下文中理所当然地使用IEEE-754双精度浮点格式。

Python:“...几乎所有机器都使用 IEEE 754 二进制浮点运算,并且几乎所有平台都将 Python 浮点映射到 IEEE 754 二进制 64 位‘双精度’值。”

C++:将

double
与 IEEE-754 双精度相关联。

机器 epsilon 是 st. fl(1+ε)>1.

使用双精度,正式 epsilon = 2^-52,但大多数实现(因为四舍五入到最近的值)为 epsilon = 2^-53。

在 Python (Spyder) 中,当我这样做时:

i = 1.0 + 2**-53

print(i)

>> 1.0

C++版本:

#include <iostream>
#include <cmath>
#include <iomanip>
    
int main() {
        
    double i = std::pow(2.0, -53);
        
    double j = 1.0 + i;
        
    std::cout << j;
    
    return 0;
        
}

>> 1.0

此外,当我使用 Python 时:

i = 1.0 + 2**-52 + 2**-53

print(i)

>> 1.0000000000000004

C++版本:

#include <iostream>
#include <cmath>
#include <iomanip>
        
int main() {
                
    double i = std::pow(2.0, -52);
                
    double j = 1.0 + i;
                
    double k = j + std::pow(2.0, -53);
                
    std::cout << std::setprecision(17) << k;
        
    return 0;

}
    
>> 1.0000000000000004

其中加法的顺序是从左到右,这样非结合性的魔力就不会发挥作用,即

i = 1.0 + 2**-52 + 2**-53 <=> i = (1.0 + 2**-52) + 2**-53
.

(对我来说)奇怪的事情发生在这里。首先,1 + 2**-52 存储(在寄存器中)作为完全相当于 1 + 2*epsilon 的双精度格式,即

0 + 01111111111 + 000...01

此外,2**-53 存储为:

0 + 01111001010 + 000...00

如果我们把它们写成

(mantissa)*(2**exponent)
的形式,
1 + 2**-52
就是
(1.0000...01)*(2**0)
2**-53
就是
(1.0000...00)*(2**-53)
,其中尾数(1.xxxx...xx)长度是53(包括隐含的1位) 。按位加法需要相同的指数,因此我移动较小的尾数(即 2**-53),使其具有指数 0:

(1.0000...00)*(2**-53) -> (0.|52 zeros here|10000...00)*(2**0)

所以添加就像:

1.0000...010 (51 zero digits after the radix, then a '1' and '0' making a total of 53 digits)
0.0000...001 (52 zero digits after the radix + '1' digit making a total of 53 digits)
+
------------
1.0000...011 (53 digits after the radix)

现在尾数总共应该是53,但是上面是54,所以应该四舍五入。

我的问题从这里开始:这两种编程语言在我执行

1.0000000000000004
时给出
1.0 + 2**-52 + 2**-53
输出的原因是因为实现了tie-to-even规则,以便
1.0000...011
四舍五入为
1.0000...10
,本质上
1.0000000000000004
高达 16 位精度?还是(完全)是别的东西&我在计算等中犯了错误?

抱歉,如果在这样一个简单的主题上看起来有点矫枉过正或想得太多,但是,它困扰了我好几天,我无法弄清楚原因或无法验证我的想法。任何答案和评论都值得赞赏。

python c++ floating-point ieee-754
2个回答
0
投票

是的,这完全是关于舍入到最接近/偶数。 Python 的

float.hex()
可以直接显示这些位:

>>> 1.0 + 2**-52 + 2**-53
1.0000000000000004
>>> _.hex()
'0x1.0000000000002p+0'

尽管 Python 和 C 与此关系不大:这几乎肯定是 CPU/FPU 如何实现浮点加法的结果(它们几乎肯定默认在硬件中实现 754 风格的最接近/偶数舍入)。 `` 请注意,在此特定示例中,关联性并不重要。

1.0 + 2**-52
2**-52 + 2**-53
本身都可以完全表示。


0
投票

是的,平局。这是默认的 IEEE 754 舍入模式。 IEEE 754 定义了 4 种可能的舍入模式:

  • 舍入到最接近的位置,打破平局。
  • 向零舍入。
  • 向正无穷大舍入。
  • 向负无穷大舍入。

选择除默认值之外的任何内容是一项极其专业的操作,大多数语言甚至都不支持。

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.