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

时间:2015-01-31 11:27:04

标签: java c# floating-point-precision

我知道之前已经提出了类似的问题,但没有一个答案解决了我的问题。

我有两个功能:

爪哇

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 个答案:

答案 0 :(得分:1)

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

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

例如,看起来您的数据与位置有关。这些数据中最精心测量的部分之一是珠穆朗玛峰的高度。基于高精度GPS测量的当前最佳估计是“29,035英尺,误差范围为正负6.5英尺”Encyclopedia Britannica。第13位有效数字中的误差对应于测量地球周长时小于千分之一英寸的误差。

如果舍入误差相对于结果要求确实很重要,并且考虑到输入的准确性,实际上可以实现的是,那么您可能需要考虑更灵巧的方式来安排计算或更高精度的算术。 / p>

答案 1 :(得分:1)

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

您所观察到的可能是计算超越函数的算法的差异;例如正弦函数。理论计算涉及对无限系列求和。为了获得最准确的答案,你要永远总结系列。要获得一定精度的答案,请保持求和,直到“增量”小于所需的精度。

实际上,这种方法实际上很慢。在可能的情况下,使用表格和插值来实现实用算法。虽然没有达到最大精度,但速度要快得多。


Java Math.sin的精度由用于计算它的算法决定。这些算法是特定于平台的。以下是Math的javadocs关于精度问题的说法。

  

与类StrictMath的某些数值方法不同,类Math的等效函数的所有实现都未定义为返回逐位相同的结果。这种放松允许在不需要严格再现性的情况下实现更好的实施。

Math.sin的精确保证是:

  

计算结果必须在精确结果的1 ulp范围内。结果必须是半单调的。

javadoc中指定了“ulp”和“semi-monotonic”。

相比之下,StrictMath表示Math.sin表示特定版本的特定开源库用于进行计算。目标是可重复性;即所有Java平台上的相同答案。


问:那么他们为什么不让{{1}}更准确?有可能达到0.5 ulp ......

这是速度和精度之间的工程权衡。阅读javadocs以了解问题。

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