C 和 Java 之间的双精度差异
Double precision discrepancy between C and Java
我正在用不同的语言编写涉及双精度算术的代码。理想情况下,程序需要产生完全相同的值。我知道并非所有 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
"Equivalent" 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 在下面做了什么魔法。
编辑2:
按照@chris-k 的建议使用strictfp
,我注释了class,结果仍然是0.0016600766427440370000000000000000000000000000000000
。
编辑3:
另一个建议是尝试 System.out.printf("%.52f\n", new BigDecimal("1.66007664274403694e-03"));
,它给出了我们尚未看到的结果:0.0016600766427440369400000000000000000000000000000000
.
使用 Java 中的 BigDecimal 进行此类操作:
BigDecimal x = new BigDecimal(1.66007664274403694e-03D);
System.out.printf(x.toPlainString());
Java 中的原始浮点数只有 4 个字节宽。
我认为这是一个显示问题。 System.out.printf
中的精度说明符不允许将精度提高到超过某个点(之后,它只打印尾随 0 位数字)。
如果您不太关心打印的值,只想让两者相同,请提取 sign/mantissa/exponent 并打印它们,例如 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,可能是为了避免打印浮点常量与它们在源代码中的书写方式不同)。
另一方面,如果这是一个 计算得出的 值而不是一个常量,您可能很难获得 C 和 Java 之间的值匹配 :)
您很可能只在此处遇到显示问题。但是在计算过程中 Java 可以自由地与 C 的行为不同。
如果平台上可用,Java 可以在浮点计算中自由使用额外精度。然而,Java 确实提供了一个覆盖,声明你的替身是 strictfp,这将通知 JVM 限制计算,使它们更便携。
您可能还想查看 Java 中的 class StrictMath,它还有一些已在 C 中实现的用于兼容性目的的实用程序。
我正在用不同的语言编写涉及双精度算术的代码。理想情况下,程序需要产生完全相同的值。我知道并非所有 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
"Equivalent" 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 在下面做了什么魔法。
编辑2:
按照@chris-k 的建议使用strictfp
,我注释了class,结果仍然是0.0016600766427440370000000000000000000000000000000000
。
编辑3:
另一个建议是尝试 System.out.printf("%.52f\n", new BigDecimal("1.66007664274403694e-03"));
,它给出了我们尚未看到的结果:0.0016600766427440369400000000000000000000000000000000
.
使用 Java 中的 BigDecimal 进行此类操作:
BigDecimal x = new BigDecimal(1.66007664274403694e-03D);
System.out.printf(x.toPlainString());
Java 中的原始浮点数只有 4 个字节宽。
我认为这是一个显示问题。 System.out.printf
中的精度说明符不允许将精度提高到超过某个点(之后,它只打印尾随 0 位数字)。
如果您不太关心打印的值,只想让两者相同,请提取 sign/mantissa/exponent 并打印它们,例如 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,可能是为了避免打印浮点常量与它们在源代码中的书写方式不同)。
另一方面,如果这是一个 计算得出的 值而不是一个常量,您可能很难获得 C 和 Java 之间的值匹配 :)
您很可能只在此处遇到显示问题。但是在计算过程中 Java 可以自由地与 C 的行为不同。
如果平台上可用,Java 可以在浮点计算中自由使用额外精度。然而,Java 确实提供了一个覆盖,声明你的替身是 strictfp,这将通知 JVM 限制计算,使它们更便携。
您可能还想查看 Java 中的 class StrictMath,它还有一些已在 C 中实现的用于兼容性目的的实用程序。