有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

Java/C中浮点计算的显著精度差异#

我知道以前也有人问过类似的问题,但没有一个答案能解决我的问题

我有两个功能:

爪哇

public static void main(String[] args) {
    double h, z, lat0, n0, eSq;
    z    = 4488055.516;
    lat0 = 0.7853981634671384;
    n0   = 6388838.290122733;
    eSq  = 0.0066943799901975545;
    h    = z / Math.sin(lat0) - n0 * (1 - eSq);
    System.out.println(h);
}

C#

public static void Main (string[] args)
{
    double h, z, lat0, n0, eSq;
    z    = 4488055.516;
    lat0 = 0.7853981634671384;
    n0   = 6388838.290122733;
    eSq  = 0.0066943799901975545;
    h    = z / Math.Sin(lat0) - n0 * (1 - eSq);
    Console.WriteLine(h);
}

4488055,516/sin(0,7853981634671384)-6388838,290122733*(1-0,0066943799901975545)

对于SpeedCrunch、Maxima和LibreOffice Calc

结果如下:

Java:             1000.0000555226579 (same with and without strictfp)
C#:               1000,00005552359
SpeedCrunch (15): 1000,000055524055155
SpeedCrunch (50): 1000,00005552405515548724762598846216107366705932894830
LibreOffice Calc: 1000,000055523590000
Maxima:           1000.000055523589
Maxima:           1.00000005552391142b3 (bfloat - fpprec:20)

正如您所看到的,Java和C在小数点后第9位是不同的。其他的也不是那么统一。这是在相同的操作系统和相同的CPU上测试的。测试也在32位和64位系统上进行

如何解决这类问题?我认为精度应该等于15位小数


共 (2) 个答案

  1. # 1 楼答案

    没有得到15位数的原因是两个相似的数字相减z / Math.sin(lat0)大约是6347068.978968251n0 * (1 - eSq)大约是6346068.978912728。小数点前有7位,减法结果中第9位小数的变化对应于其中一个输入中10^15中少于一部分的变化

    这类问题最简单的解决方案通常是只显示输入中可靠数字支持的数字。对于10^12中的一个部分,可以进行的测量非常少,因此在这种情况下,几乎可以肯定,由于浮点舍入误差而不同的数字将被删除

    例如,看起来您的数据与位置相关。珠穆朗玛峰的高度是这些数据中最仔细测量的部分之一。根据高精度全球定位系统测量,目前的最佳估计数为“29035英尺,误差幅度为正负6.5英尺”。第13位有效数字的误差对应于测量地球周长时小于千分之一英寸的误差

    如果舍入误差相对于结果要求以及考虑到输入的准确性而实际可达到的程度来说确实很重要,那么您可能需要寻找更聪明的方法来安排计算或使用更高精度的算法

  2. # 2 楼答案

    您观察到的精度差异不在浮点支持本身中。Java和C#将使用相同的(IEE 768)浮点表示,并且很可能使用相同的指令

    你所观察到的可能是计算超越函数的算法的不同;e、 g.正弦函数。理论计算包括对一个无限序列求和。为了得到尽可能准确的答案,你需要不断地对序列求和。为了得到某一精度的答案,您需要不断求和,直到“增量”小于所需精度

    在实践中,这种方法是不切实际的缓慢。在可能的情况下,使用表格和插值实现实用算法。这要快得多,尽管你不能获得最大的精度


    Java Math.sin的精度由用于计算它的算法决定。这些算法是特定于平台的。以下是javadocs for Math关于精确性的内容

    Unlike some of the numeric methods of class StrictMath, all implementations of the equivalent functions of class Math are not defined to return the bit-for-bit same results. This relaxation permits better-performing implementations where strict reproducibility is not required.

    Math.sin的精度保证是:

    The computed result must be within 1 ulp of the exact result. Results must be semi-monotonic.

    其中javadoc中指定了“ulp”和“半单调”

    相比之下javadocsfor StrictMath声明使用特定开源库的特定版本进行计算。目标是再现性;i、 e.在所有Java平台上都有相同的答案


    问:那么为什么他们不让Math.sin更精确呢?有可能达到0.5 ulp以内

    这是速度和精度之间的工程权衡。阅读this wikipedia article了解问题所在

    我的建议是,如果您想要获得最大的精度,请寻找一个第三方开源Java库来实现超越函数。并且要准备好让你的代码慢很多