C和Java之间的双精度差异

时间:2015-10-20 10:06:41

标签: java c double rounding

我正在编写涉及双精度算术的不同语言的代码。理想情况下,程序需要产生完全相同的值。我知道不是所有的double / float算术都是确定性的(在这里很好地解释:https://randomascii.wordpress.com/2013/07/16/floating-point-determinism/),所以我需要谨慎。有人能解释一下这里发生了什么吗?:

C程序:

#include <stdio.h>
#include <stdlib.h>

int
main(void) {
    printf("%.52f\n", 1.66007664274403694e-03);
    return (EXIT_SUCCESS);
}

结果:0.0016600766427440369430584832244335302675608545541763

“等效”Java 8程序:

class A {
    public static void main(String args[]) {
        System.out.printf("%.52f\n", 1.66007664274403694e-03D);
    }
}

结果:0.0016600766427440370000000000000000000000000000000000

结果不同。我觉得这可能与浮点舍入模式有关,但是,据我所知,C和Java具有相同的默认值(?)。

如何确保两个程序具有相同的结果?

编辑:

FWIW,如果我将常量打印为BigDecimal:System.out.printf("%.52f\n", new BigDecimal(1.66007664274403694e-03));,我得到:0.0016600766427440369430584832244335302675608545541763。这个可能证明这不是显示问题,但是谁知道JVM在下面做了什么魔术。

EDIT2:

使用strictfp作为@ chris-k建议,我注释了该类,结果仍为0.0016600766427440370000000000000000000000000000000000

EDIT3:

另一个建议是尝试System.out.printf("%.52f\n", new BigDecimal("1.66007664274403694e-03"));,它会给出我们尚未看到的结果:0.0016600766427440369400000000000000000000000000000000

3 个答案:

答案 0 :(得分:2)

我认为这是一个显示问题。 System.out.printf中的精度说明符不允许精度增加到某个点以上(之后,它只打印0位数)。

如果您不太关心打印值,只是希望它们在两者之间相同,请提取符号/尾数/指数并打印它们,例如在Java中:

    long mantissa = Double.doubleToLongBits(f) & 0x000fffffffffffffL;
    long exponent = Double.doubleToLongBits(f) & 0x7ff0000000000000L;
    long sign = Double.doubleToLongBits(f)  & 0x8000000000000000L;

    // Exponent is encoded with a bias, so to see the actual exponent
    // value:
    exponent >>= (13 * 4);
    exponent -= 1023;

    // Leading 1 bit for mantissa isn't stored:
    mantissa |= 0x0010000000000000L;

    if (sign != 0) sign = 1;
    System.out.println("" + sign + "/" + mantissa + "/" + exponent);

对于Java,这会打印:

    0/7655752242860553/-10

等效C代码(适用于x86-64上的GCC):

#include <string.h>
#include <stdio.h>

int main(int argc, char ** argv)
{
    double f = 1.66007664274403694e-03;

    long long doubleBits;
    memcpy(&doubleBits, &f, sizeof(long long));

    long long mantissa = doubleBits & 0x000fffffffffffffLL;
    long long exponent = doubleBits & 0x7ff0000000000000LL;
    long long sign = doubleBits  & 0x8000000000000000LL;

    // Exponent is encoded with a bias, so to see the actual exponent
    // value:
    exponent >>= (13 * 4);
    exponent -= 1023;

    // Leading 1 bit for mantissa isn't stored:
    mantissa |= 0x0010000000000000L;

    if (sign != 0) sign = 1;
    printf("%lld/%lld/%lld\n", sign, mantissa, exponent);

    return 0;
}

在我的机器上,它产生相同的输出。这表明常量的内部表示在两个平台上都是相同的(当然,这在结构和实现中可能并不普遍)。原始程序输出中的差异是由于printf函数的实现不同造成的。 (Java版本显然在某个点之后停止计算数字并打印0&#39;而可能是为了避免打印浮点常量与在源代码中编写它们的方式不同。)

另一方面,如果这是计算的值而不是常量,那么您可能更难以获得C和Java之间的值来匹配:)

答案 1 :(得分:2)

你很可能只有这里的显示问题。但是在计算过程中,Java可以自由地与C的行为不同。

如果在平台上可用,Java可以在浮点计算中自由使用额外的精度。但是,Java确实提供了覆盖,将double声明为strictfp,这将通知JVM限制计算,使其更具可移植性。

您可能还想查看Java中的StrictMath类,它还有一些已在C中实现的实用程序,以实现兼容性。

答案 2 :(得分:0)

在Java中使用BigDecimal进行此类操作:

    BigDecimal x = new BigDecimal(1.66007664274403694e-03D);
    System.out.printf(x.toPlainString());

Java中的原始浮点只有4个字节宽。