C++ 在浮点运算中,返回0.0减去两个不同的值是否可能?

C++ 在浮点运算中,返回0.0减去两个不同的值是否可能?,c++,floating-point,C++,Floating Point,由于浮点“近似”性质,两组不同的值可能返回相同的值 : #包括 int main(){ 标准:计算精度(100); 双a=0.5; 双b=0.5; 双c=0.4999999994; 排除像NAN这样的有趣数字,我认为这是不可能的 假设a和b是正规有限的IEEE 754浮点,并且| a-b |小于或等于| a |和| b |(否则它显然不是零) 这意味着指数是IEEE-754标准是特意设计的,当且仅当两个值相等时,减去两个值产生零,除非从其自身减去无穷大产生NaN和/或异常 遗憾的是,C++不需要

由于浮点“近似”性质,两组不同的值可能返回相同的值

:

#包括
int main(){
标准:计算精度(100);
双a=0.5;
双b=0.5;
双c=0.4999999994;

排除像NAN这样的有趣数字,我认为这是不可能的

假设a和b是正规有限的IEEE 754浮点,并且| a-b |小于或等于| a |和| b |(否则它显然不是零)


这意味着指数是IEEE-754标准是特意设计的,当且仅当两个值相等时,减去两个值产生零,除非从其自身减去无穷大产生NaN和/或异常

遗憾的是,C++不需要符合IEEE-75,并且许多C++实现使用IEEE-74的一些特性,但不完全一致。 一种常见的行为是将次正常结果“刷新”为零。这是硬件设计的一部分,以避免正确处理次正常结果的负担。如果此行为有效,则减去两个非常小但不同的数字可以得到零。(数字必须接近正常范围的底部,在低于正常范围内有一些有效位。)

有时,具有这种行为的系统可能会提供一种禁用它的方法

另一个需要注意的行为是C++不要求浮点运算精确地按写方式执行,它允许在中间操作和“收缩”中使用“过精度”。例如,

a*b-c*d
可以通过使用一个将
a
b
相乘的运算来计算,然后使用另一个将
c
d
相乘的运算来计算,并从先前计算的
a*b
中减去结果。后一个运算的作用就好像计算了
c*d
具有无限精度,而不是四舍五入到标称浮点格式。在这种情况下,
a*b-c*d
可能会产生非零结果,即使
a*b==c*d
的计算结果为true

<>一些C++实现提供了禁用或限制这种行为的方法。

IEEE浮点标准的渐增下溢特性阻止了这一点。逐步下溢是由次正规(非正规)数实现的,它是均匀分布的(相对于对数,就像正常浮点)。在最小的正数和正的正数之间,中间有零点。由于它们是均匀间隔的,所以增加两个不同的正数(即减去零)是精确的,因此不会再现你所要求的。最小的次正规是(多)。小于正常数之间的最小距离,因此不相等的正常数之间的任何减法都将更接近于小于零的次正常值

如果您使用CPU的特殊非规范化归零(DAZ)或齐平归零(FTZ)模式禁用IEEE一致性,那么实际上您可以减去两个小的、接近的数字,否则会导致一个低于正常值的数字,由于CPU的模式,该数字将被视为零。a(Linux):


第一个1表示减法结果是零,而第二个0表示操作数不相等。

< P>不幸的是,答案取决于实现和配置方式。C和C++不要求任何特定浮点表示或行为。大多数实现使用IEEE 754表示。但它们并不总是精确地实现IEEE 754算术行为

要理解这个问题的答案,我们必须首先了解浮点数是如何工作的

一个简单的浮点表示应该有一个指数、一个符号和一个尾数

(-1)s2(e-e0)(m/2M)

其中:

  • s是符号位,值为0或1
  • e是指数域
  • e0是指数偏差。它本质上设置了浮点数的总体范围
  • M是尾数位数
  • m是尾数,其值介于0和2M-1之间
这在概念上类似于你在学校教的科学记数法

然而,这种格式有许多相同数字的不同表示形式,几乎浪费了整整一位的编码空间。要解决这个问题,我们可以在尾数中添加一个“隐式1”

(-1)s2(e-e0)(1+(m/2M))

此格式每个数字只有一个表示形式。但是它有一个问题,它不能表示零或接近零的数字

要修复此问题,IEEE浮点会为特殊情况保留两个指数值。保留一个0的指数值用于表示称为次正常值的小数字。可能的最高指数值用于NaN和无穷大(我将在本文中忽略,因为它们在此处不相关).因此,定义现在变为

e=0时的(-1)s2(1–e0)(m/2M)
e>0和e<2E-1时的(-1)s2(e-e0)(1+(m/2M))

使用这种表示法,较小的数的步长总是小于或等于较大数的步长。因此,如果减法结果的大小小于两个操作数,则可以精确表示。特别是接近零但不完全为零的结果可以精确表示

如果结果的大小大于一个或两个操作数,例如从一个大值中减去一个小值或减去两个相反符号的值,则此项不适用。在这些情况下,结果可能不精确,但显然不能为零

不幸的是,FPU设计人员偷工减料。他们没有包括快速、正确处理次正常数字的逻辑,而是根本不支持(非零)次正常数字
#include <iostream>

int main() {
    std::cout.precision(100);

    double a = 0.5;
    double b = 0.5;
    double c = 0.49999999999999994;

    std::cout << a + b << std::endl; // output "exact" 1.0
    std::cout << a + c << std::endl; // output "exact" 1.0
}
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);    // system specific
double d = std::numeric_limits<double>::min(); // smallest normal
double n = std::nextafter(d, 10.0);     // second smallest normal
double z = d - n;       // a negative subnormal (flushed to zero)
std::cout << (z == 0) << '\n' << (d == n);
1
0