Java 浮点数乘法给出错误的结果

Java Float multiplication giving wrong result

提问人:jagadesh 提问时间:12/28/2022 更新时间:12/30/2022 访问量:399

问:

使用 FLOAT 进行乘法会产生明显的差异。


public static void main(String[] args) {
    // using string and parsing instead of actual data type is part of use case, that is why representing the same here

    double v1 = parseDouble("590.0");
    double v2 = parseDouble("490.0");
    double v3 = parseDouble("391.0");

    float v4 = parseFloat("590.0");
    float v5 = parseFloat("490.0");
    float v6 = parseFloat("391.0");

    System.out.println(new BigDecimal(v1 * v2 * v3));
    System.out.println(new BigDecimal(v4 * v5 * v6));

    System.out.println(BigDecimal.valueOf(Float.parseFloat("289100.0") * Float.parseFloat("391.0")));
    System.out.println(BigDecimal.valueOf(Double.parseDouble("289100.0") * Double.parseDouble("391.0")));

}

输出:

113038100 // double multiplication
113038096 // float multiplication
113038096
113038100

对于上面的代码,

(590.0 * 490.0 * 391.0) 给出 113038100 使用 double

(590.0 * 490.0 * 391.0) 给出113038096使用浮点数 (113038100 - 113038096 = 4 // 差值)

我已经通读了此链接 https://floating-point-gui.de/basic/ 并且能够理解浮点计算是如何发生的,但是所有 4 个计数都不同是出乎意料的。

请帮助我理解以下事项

  • 这是正确的吗 首先
  • 总是浮点给出错误的数字吗?
  • 正如我所看到的,double 也使用相同的技术,所以如果我们使用 double 来保证我们得到正确的结果
Java 浮点 精度

评论

0赞 jagadesh 12/28/2022
@user16320675 - 老实说,我不明白......它给出 8.0 最后一个浮点数是多少??如果 4 的差异不是意外的,为什么要使用 float??就好像我一辈子都被愚弄了......如果是 1.000004,这是可以接受的,但 100 对 104 (例如)完全不可接受
1赞 user16320675 12/28/2022
这是可以使用 表示的下一个数字的区别,如文档中所述。“不可接受”?那就不要使用它 - 计算机是有限的,只有有限数量的可用位,但无限数量的实数(像这样的数字需要更多的位 - 需要更多的信息,即使没有小数)float12345678901234567890123
0赞 jagadesh 12/28/2022
@user16320675 - 我绝对不会使用它......让我重新表述我的问题......(590.0 * 490.0 * 391.0) 给出113038096 = 这是正确的吗??
0赞 Steve Summit 12/29/2022
@jagadesh 这是浮点数令人惊讶的事情之一:对于大数,小数点左侧会有“舍入误差”。一直都在发生,完全正常,没什么好担心的。当然,单精度的情况更糟——这是从不使用单精度(又名“浮点数”)的另一个原因。
0赞 user16320675 1/10/2023
顺便说一句,java(以及使用的标准IEEE 754)更像是或(590.0 * 490.0 * 391.0)1.130381 * 10⁸1.130381E8

答:

0赞 user20716279 12/28/2022 #1

总是浮点给出错误的数字吗?

这取决于数字,如果数字可以使用浮点精度表示,那就没问题了

“正如我所看到的,double也使用相同的技术,所以我们有多少保证 如果我们使用double,则必须得到正确的结果”

double 也有同样的问题,但由于 double 具有更高的精度,因此可能性会降低,但它仍然会发生

因此,当您需要非常精确的结果(例如在科学或金融应用程序中)时,您将需要使用 BigDecimal

观看此视频,它解释了浮点数的工作原理 https://www.youtube.com/watch?v=ajaHQ9S4uTA

评论

0赞 jagadesh 12/28/2022
是的。。。我知道为了获得准确的结果,我们需要使用大小数,但是,即使使用的数字没有小数点,也给出 4 个数字的差异,这并不奇怪,或者这是预期的行为吗?
0赞 12/28/2022
@jagadesh浮点数大约是整数,即使没有小数部分,仍然有可能有问题表示为二进制,视频的 8:22 显示了如何表示 3 的示例,当然在视频中他们没有使用双精度浮点数
0赞 Eric Postpischil 12/30/2022 #2

这是正确的吗 首先

Java 格式为 IEEE-754 binary32。在这种格式中,每个有限数都表示为一个符号、一个 24 位整数和一个从 2−149 到 2104 的 2 次幂缩放。整数部分称为有效部分。(该格式通常被描述为符号,一个 24 位数字,第一位后有一个二进制点,因此它的值为 [0, 2),缩放范围为 2−126 到 2127。这些格式在数学上是等效的,此处使用的格式在 IEEE-754 标准中注明为一个选项。在正常形式中,24 位整数为 223 或更大。(小于 2−126 的可表示数字不能以正常形式表示,并且必然是次正态的。float

在这种格式中,590 可以表示为 +590•20 或 +8,339,456•2−14。490 是 +490•20 或 +16,056,320•2−15

它们的乘积是 +289,100•20 或 +9,251,200•2−5

391 是 +391•20 或 +12,812,288−15

+289,100•2 0 和 +391•20 的普通算术乘积为 +113,038,100•20但是,113,038,100 不是 24 位数字;它是一个 27 位数字。为了使其低于 224,我们可以调整缩放比例,将有效数乘以 1/8,然后将缩放比例乘以 8 = 23

这给了我们 +14,129,762.5•23.但是,现在有效数不是整数。此结果无法以格式表示。为了产生结果,定义了格式加法的运算,以将普通算术四舍五入到最接近的可表示值。在这种情况下,存在平局,我们可以向上或向下舍入 .5。平局通过四舍五入来解决,使低位数均匀,因此我们四舍五入到 +14,129,762•23.floatfloat

+14,129,762•23 是 113,038,096。这就是你得到的结果,所以它是正确的。

总是浮点给出错误的数字吗?

这没有错;计算机按照其规范运行。

Observe 是 32 位格式,但实数是无限多的。甚至还有无限多的有理数。32 位格式不可能产生与理论实数算术或有理数算术相同的结果。可能的结果比可表示的值多。float

64 位格式也是如此。整数格式、固定精度格式以及具有固定位数的所有数字格式也是如此。固定位数的位数不能表示无限多的值。double

您的评论表明您认为浮点数会为小数值(小于 1 的数字)生成近似结果。但是,可以表示多少个值的限制适用于所有比例。在每个刻度(每个 2 的幂)下,只有 224 个值是可表示的(正常形式为 223)。对于尺度 20,所有低于 224 的非负整数都是可表示的。但是,在此之上,只有一些整数是可表示的。首先,我们必须跳过每两个整数,然后是每四个,然后是每八个,依此类推。

浮点运算旨在近似于实数算术。当您想要近似实数算术时,应该使用它。除极少数例外情况外,当您需要精确算术时,不应使用它。