【问题标题】:Are results from math operations on Double exactly repeatable?Double 上的数学运算结果是否完全可重复?
【发布时间】:2017-02-23 22:53:03
【问题描述】:

我有一个使用 Java 8 实现的复杂数据处理算法,并使用 Double 作为数据类型。给定相同的输入(数十万数据库行),该算法输出不同的 Double 值。有时返回值 a,有时返回值 b。这两个值交替执行每次执行。 ab 之间的差异约为 0.0001。我知道 Double 数据类型不会像 Decimal 那样授予确切的值。但是,我不确定它是否会授予 repeatable 结果,假设输入完全相同。 IE。是否有可能以不一致的方式应用舍入策略?我在这里的目的是解释为什么我用相同的输入得到不同的值。

其他细节:我使用的是 Tomcat 8 运行时环境,部署在 SAP HCP 上。数据库级别的数据类型是 Decimal,由于历史原因,我们需要 Java 级别的 Double。

【问题讨论】:

  • 我无法回答,但如果您想要绝对的可重复性,请尝试谷歌搜索“fpstrict”
  • 在同一个系统上应用完全相同的操作序列应该总是得到相同的答案;使用fpstrict 将保证所有系统上的结果相同。您确定输入中没有不确定性吗?
  • 没有fpstrict 不应该让它在给定的平台上变得不确定,但是,让它跨平台移植,对吧? @MarcoBolis
  • 绝对如此,fpstrict 是关于不同平台的可重复性。抱歉,如果评论听起来有误导性...

标签: java floating-point java-8 double


【解决方案1】:

给定相同的输入,浮点数计算(在同一平台上)将产生相同的输出。

但是,如果您以不同的顺序输入输入数字,您可能会得到不同的结果(即使这在数学上不会产生影响)。也许这就是这里发生的事情(看到您从关系数据库中提取数字,除非您明确排序,否则该数据库没有定义的顺序)。如果您使用多个 CPU 进行并行计算并且每次都以不同的方式划分和组合数据,也会发生同样的情况。

【讨论】:

    【解决方案2】:

    订单很重要。如果每次处理的数据库行的顺序不一样,就会产生不同的结果。这是一个简单的例子:

    double d1 = 0.1;
    double d2 = 0.2;
    System.out.println(d1 + d1 + d2 + d2); 
    System.out.println(d1 + d2 + d2 + d1); 
    

    【讨论】:

      【解决方案3】:

      对于 strictfp,Java 语言规范完全定义了双精度数上所有简单算术的结果,无论平台如何。

      允许某些java.lang.Math 方法对某些操作产生略微不同的结果。如果您需要可重现的结果,请改用java.lang.StrictMath

      正如下面的评论中所指出的,java.lang.Math.cos 是一个结果可以改变的函数的例子。 API documentation 表示“计算结果必须在精确结果的 1 ulp 范围内。结果必须是半单调的。”

      【讨论】:

      • StrictMath 不是关于架构之间的可移植性(但只要您坚持使用相同的 CPU/OS/JVM,非严格数学仍然应该是确定性和可重复的)?
      • @Thilo 我通常期望可重复性,但如果没有 strictfp,则不能绝对保证。至少在理论上,热点编译器可以根据观察到的性能改变其优化,从而导致运行间的变化。也就是说,OP 描述的结果更有可能是由于程序中的多线程错误。
      • 我刚刚经历了 Math.cos 返回的不同结果,具体取决于应用程序运行的是 Iava 8 还是 Java 11 - 很可能是 Improve Aarch64 Intrinsics 的结果
      【解决方案4】:

      这是Collectors.averagingDouble文档中的一个例子:

      The average returned can vary depending upon the order in which values are recorded, due to accumulated rounding error in addition of values of differing magnitudes.

      这里的关键词是depending upon the order。这是因为平均 double 在后台使用 Kahan 求和(以获得更好的结果和更少的精度损失)。

      任何other 双重操作的计数相同。应用这些操作的顺序可能会对最终结果产生影响(因为四舍五入)。

      这是一个例子:

        Double d1 = Stream.of(10000.0D, 3.14159D, 2.71828D).collect(Collectors.averagingDouble(x -> x)); 
        Double d2 = Stream.of(3.14159D, 10000.0D, 2.71828D).collect(Collectors.averagingDouble(x -> x));
      
      
        System.out.println(d1); // 3335.2866233333334
        System.out.println(d2); // 3335.286623333333
      

      以不同的顺序应用相同的数字会产生不同的结果(现在精度不同)

      【讨论】:

        猜你喜欢
        • 2019-10-16
        • 1970-01-01
        • 2023-01-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多