Floating point 为什么两个看似相同的浮点数乘以它们的倒数时会有不同的行为?

Floating point 为什么两个看似相同的浮点数乘以它们的倒数时会有不同的行为?,floating-point,numerical,floating-point-conversion,Floating Point,Numerical,Floating Point Conversion,在将我链接到之前,请先阅读问题。我知道这是怎么回事。这个问题是针对我发现的两个数字的(好吧,可能存在更多这样的对,但我想解释一下这种特殊的行为) 我有两个浮点常量:1.9和1.1。使用Python,我将这两个函数乘以它们的倒数,得到以下结果: >>> x = 1.1 >>> y = 1.9 >>> print("%.55f" % x) 1.1000000000000000888178419700125232338905334472656250

在将我链接到之前,请先阅读问题。我知道这是怎么回事。这个问题是针对我发现的两个数字的(好吧,可能存在更多这样的对,但我想解释一下这种特殊的行为)

我有两个浮点常量:
1.9
1.1
。使用Python,我将这两个函数乘以它们的倒数,得到以下结果:

>>> x = 1.1
>>> y = 1.9
>>> print("%.55f" % x)
1.1000000000000000888178419700125232338905334472656250000
>>> print("%.55f" % y)
1.8999999999999999111821580299874767661094665527343750000
>>> print("%.55f" % (1/x))
0.9090909090909090606302811465866398066282272338867187500
>>> print("%.55f" % (1/y))
0.5263157894736841813099204046011436730623245239257812500
>>> print("%.55f" % ((1/x) * x))
1.0000000000000000000000000000000000000000000000000000000
>>> print("%.55f" % ((1/y) * y))
0.9999999999999998889776975374843459576368331909179687500
我当然知道在硬件中实现的浮点算法的问题,但我无法理解为什么这些数字表现出如此不同的行为。考虑他们的倒数;其精确结果如下(使用WolframAlpha计算,为简洁起见,后面的数字省略):

  • 1/x
    0.9017505915727262383419481191148103102677263…
  • 1/y
    0.5263157894738423512959611357687739218560515259237553584…
很明显,我的硬件计算的倒数是正确的,直到两个数字的15-16位左右;没有区别。还有什么不同?怎么可能
(1/x)*x
恰好是
1
,末尾没有任何“垃圾”

为了完整起见,我可能应该提到我在x86 PC上,所以这是所有64位IEEE 754算法:

>>> sys.float_info
sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)

在这个答案的最后,我写了一个Java程序,它显示了正在发生的事情。我使用Java是因为BigDecimal提供了一种方便的方法来进行精确的打印输出和简单的计算

IEEE浮点运算的作用就像计算机首先计算出精确的结果,然后将其舍入到最接近的可表示值。使用BigDecimal,我可以显示精确的乘积,然后显示上下舍入的结果

我这样做是为了你的每一个输入,也是为了2.0来测试程序,当产品正好是1.0时

以下是
x
的输出:

x=1.100000000000000088817841970012523233890533447265625
1/x=0.90909090909090906063028114658663980662822723388671875
Exact product = 1.00000000000000004743680196125668585607481256497662217592534562686512611406897121923975646495819091796875
Round down = 1
Round down error = 4.743680196125668585607481256497662217592534562686512611406897121923975646495819091796875E-17
Round up = 1.0000000000000002220446049250313080847263336181640625
Round up error = 1.7460780296377462222865152105318744032407465437313487388593102878076024353504180908203125E-16
精确答案用1.0和稍大的值括起来,但1.0更接近,因此四舍五入到最接近的乘法结果

y=1.899999999999999911182158029987476766109466552734375
1/y=0.52631578947368418130992040460114367306232452392578125
Exact product = 0.99999999999999989774261615294611071381321879759485743989659632495470287238958917441777884960174560546875
Round down = 0.99999999999999988897769753748434595763683319091796875
Round down error = 8.76491861546176475617638560667688868989659632495470287238958917441777884960174560546875E-18
Round up = 1
Round up error = 1.0225738384705388928618678120240514256010340367504529712761041082558222115039825439453125E-16
在这种情况下,精确的乘积用1.0和稍小的值括起来。较小的值更接近精确结果

最后:

Exact case=2
1/Exact case=0.5
Exact product = 1.0
在这两个测试用例中,确切的乘积不能用Java double、IEEE 754 64位二进制浮点表示。结果与精确的产品最接近。不同之处在于精确乘积与1.0和其他括号内可表示数字的接近程度

节目如下:

import java.math.BigDecimal;

public class Test {
  public static void main(String[] args) {
    testit(1.1, "x");
    testit(1.9, "y");
    testit(2.0, "Exact case");
  }

  private static void testit(double val, String name) {
    BigDecimal valBD = new BigDecimal(val);
    System.out.println(name + "=" + valBD);
    double inv = 1 / val;
    BigDecimal invBD = new BigDecimal(inv);
    System.out.println("1/" + name + "=" + invBD);
    BigDecimal exactProduct = valBD.multiply(invBD);
    System.out.println("Exact product = " + exactProduct);
    double rawRound = exactProduct.doubleValue();
    BigDecimal rawRoundBD = new BigDecimal(rawRound);
    int comp = rawRoundBD.compareTo(exactProduct);
    double down = 0;
    BigDecimal downBD;
    double up = 0;
    BigDecimal upBD;
    if (comp == 0) {
      return;
    } else if (comp < 0) {
      down = rawRound;
      up = Math.nextUp(down);
    } else {
      up = rawRound;
      down = Math.nextDown(up);
    }
    downBD = new BigDecimal(down);
    upBD = new BigDecimal(up);
    BigDecimal downError = exactProduct.subtract(downBD);
    BigDecimal upError = upBD.subtract(exactProduct);
    System.out.println("Round down = " + downBD);
    System.out.println("Round down error = " + downError);
    System.out.println("Round up = " + upBD);
    System.out.println("Round up error = " + upError);
    System.out.println();

  }

}
import java.math.BigDecimal;
公开课考试{
公共静态void main(字符串[]args){
testit(1.1,“x”);
testit(1.9,“y”);
testit(2.0,“精确案例”);
}
私有静态void testit(双val,字符串名){
BigDecimal valBD=新的BigDecimal(val);
System.out.println(name+“=”+valBD);
双存货=1/val;
BigDecimal invBD=新的BigDecimal(inv);
System.out.println(“1/”+name+“=”+invBD);
BigDecimal exactProduct=valBD.multiply(invBD);
System.out.println(“精确产品=“+exactProduct”);
double rawRound=exactProduct.doubleValue();
BigDecimal rawRoundBD=新的BigDecimal(rawRound);
int comp=rawRoundBD.compareTo(exactProduct);
双重下降=0;
BigDecimalDownBD;
加倍=0;
大十进制upBD;
如果(comp==0){
返回;
}否则如果(组件<0){
向下=旋转;
向上=数学下一步(向下);
}否则{
向上=圆形;
向下=数学。下一次向下(向上);
}
downBD=新的大十进制(向下);
upBD=新的大十进制(向上);
BigDecimal downError=exactProduct.subtract(downBD);
BigDecimal SuperRor=向上减去(exactProduct);
System.out.println(“向下取整=“+downBD”);
System.out.println(“向下取整错误=“+downError”);
System.out.println(“Round up=“+uppd”);
System.out.println(“舍入错误=“+upError”);
System.out.println();
}
}

在这个答案的末尾,我编写了一个Java程序,它显示了正在发生的事情。我使用Java是因为BigDecimal提供了一种方便的方法来进行精确的打印输出和简单的计算

IEEE浮点运算的作用就像计算机首先计算出精确的结果,然后将其舍入到最接近的可表示值。使用BigDecimal,我可以显示精确的乘积,然后显示上下舍入的结果

我这样做是为了你的每一个输入,也是为了2.0来测试程序,当产品正好是1.0时

以下是
x
的输出:

x=1.100000000000000088817841970012523233890533447265625
1/x=0.90909090909090906063028114658663980662822723388671875
Exact product = 1.00000000000000004743680196125668585607481256497662217592534562686512611406897121923975646495819091796875
Round down = 1
Round down error = 4.743680196125668585607481256497662217592534562686512611406897121923975646495819091796875E-17
Round up = 1.0000000000000002220446049250313080847263336181640625
Round up error = 1.7460780296377462222865152105318744032407465437313487388593102878076024353504180908203125E-16
精确答案用1.0和稍大的值括起来,但1.0更接近,因此四舍五入到最接近的乘法结果

y=1.899999999999999911182158029987476766109466552734375
1/y=0.52631578947368418130992040460114367306232452392578125
Exact product = 0.99999999999999989774261615294611071381321879759485743989659632495470287238958917441777884960174560546875
Round down = 0.99999999999999988897769753748434595763683319091796875
Round down error = 8.76491861546176475617638560667688868989659632495470287238958917441777884960174560546875E-18
Round up = 1
Round up error = 1.0225738384705388928618678120240514256010340367504529712761041082558222115039825439453125E-16
在这种情况下,精确的乘积用1.0和稍小的值括起来。较小的值更接近精确结果

最后:

Exact case=2
1/Exact case=0.5
Exact product = 1.0
在这两个测试用例中,确切的乘积不能用Java double、IEEE 754 64位二进制浮点表示。结果与精确的产品最接近。不同之处在于精确乘积与1.0和其他括号内可表示数字的接近程度

节目如下:

import java.math.BigDecimal;

public class Test {
  public static void main(String[] args) {
    testit(1.1, "x");
    testit(1.9, "y");
    testit(2.0, "Exact case");
  }

  private static void testit(double val, String name) {
    BigDecimal valBD = new BigDecimal(val);
    System.out.println(name + "=" + valBD);
    double inv = 1 / val;
    BigDecimal invBD = new BigDecimal(inv);
    System.out.println("1/" + name + "=" + invBD);
    BigDecimal exactProduct = valBD.multiply(invBD);
    System.out.println("Exact product = " + exactProduct);
    double rawRound = exactProduct.doubleValue();
    BigDecimal rawRoundBD = new BigDecimal(rawRound);
    int comp = rawRoundBD.compareTo(exactProduct);
    double down = 0;
    BigDecimal downBD;
    double up = 0;
    BigDecimal upBD;
    if (comp == 0) {
      return;
    } else if (comp < 0) {
      down = rawRound;
      up = Math.nextUp(down);
    } else {
      up = rawRound;
      down = Math.nextDown(up);
    }
    downBD = new BigDecimal(down);
    upBD = new BigDecimal(up);
    BigDecimal downError = exactProduct.subtract(downBD);
    BigDecimal upError = upBD.subtract(exactProduct);
    System.out.println("Round down = " + downBD);
    System.out.println("Round down error = " + downError);
    System.out.println("Round up = " + upBD);
    System.out.println("Round up error = " + upError);
    System.out.println();

  }

}
import java.math.BigDecimal;
公开课考试{
公共静态void main(字符串[]args){
testit(1.1,“x”);
testit(1.9,“y”);
testit(2.0,“精确案例”);
}
私有静态void testit(双val,字符串名){
BigDecimal valBD=新的BigDecimal(val);
System.out.println(name+“=”+valBD);
双存货=1/val;
BigDecimal invBD=新的BigDecimal(inv);
System.out.println(“1/”+name+“=”+invBD);
BigDecimal exactProduct=valBD.multiply(invBD);
System.out.println(“精确产品=“+exactProduct”);
double rawRound=exactProduct.doubleVal