6

我正在用 Java 和 C/C++ 编写几个参考算法。其中一些算法使用 π。我希望每个算法的两种实现产生相同的结果,而不会以不同的方式舍入。迄今为止一直有效的一种方法是使用pi在两种语言中完全相同的自定义常量,例如 3.14159。然而,当 Java 和 GCC 库中已经定义了高精度常量时,定义 pi 让我觉得很愚蠢。

我花了一些时间编写快速测试程序,查看每个库的文档,并阅读浮点类型。但我无法说服自己 java.lang.Math.PI(或 java.lang.StrictMath.PI)是否等于 math.h 中的 M_PI。

GCC 3.4.4 (cygwin) math.h 包含:

#define M_PI            3.14159265358979323846
                                         ^^^^^

但是这个

printf("%.20f", M_PI);

生产

3.14159265358979311600
                 ^^^^^

这表明最后 5 位数字不可信。

同时,Javadocs 说 java.lang.Math.PI 是:

double比任何其他值更接近pi的值,圆周长与其直径的比率。

public static final double PI  3.141592653589793d

它省略了常数中可疑的最后五位数字。

System.out.printf("%.20f\n", Math.PI);

生产

3.14159265358979300000
                 ^^^^^

如果你在浮点数据类型方面有一定的专业知识,你能说服我这些库常量是完全相等的吗?或者他们绝对不相等?

4

8 回答 8

11

请注意以下事项。

这两个数字相同到小数点后 16 位。这几乎是相同的 48 位。

在 IEEE 64 位浮点数中,这就是所有不是符号或指数的位。

#define M_PI21 位数字;这大约是 63 位的精度,这对于 IEEE 80 位浮点值很有用。

我认为您看到的是M_PI值中位的普通截断。

于 2009-05-05T16:12:20.320 回答
8

您要做的是打印出 PI 值的原始位模式并进行比较。

在 Java 中,使用http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Double.html#doubleToRawLongBits(double)方法获取应该打印为二进制的长值。

Java 5 给出:

  • PI 是 3.141592653589793
  • 原始位是 4614256656552045848
  • 二进制是 100000000001001001000011111101101010100010001000010110100011000

在 C 中,您double pi = M_PI; printf("%lld\n", pi);可以获得相同的 64 位整数:4614256656552045848(感谢 Bruno)。

于 2009-05-05T16:05:37.807 回答
3

即使起始值相同,也很难计算出相同的值。

浮点计算结果有时会因架构而异(例如 x86/PowerPC)、编译器与另一个(例如 GCC/MS C++)甚至使用相同的编译器,但编译选项不同。并非总是如此,但有时(通常是四舍五入时)。通常足以让问题被忽视,直到为时已晚(在许多次迭代和许多次舍入差异之后考虑)

这使得同步计算游戏状态的每次迭代的跨平台多人游戏变得相当困难(每个节点只接收输入,而不是实际的数据结构)。

因此,即使使用相同的语言 (C/C++),结果也可能不同,从 Java VM 到本机主机,结果也可能不同。

更新:

我找不到我阅读的来源,但我找到了Sun关于此事的一篇论文。

就像您自己回答的那样,java.lang.Math.PI 和 GCC 的 M_PI 可以管理为具有相同的值。魔鬼隐藏在这些价值观的使用中。IEEE 没有指定数学函数的输出(sin、cos、exp、...)。因此,计算的输出不一定相同。

于 2009-05-05T16:27:36.617 回答
2

是的,它们是平等的,并且使用它们将确保相同算法的 GCC 和 Java 实现处于相同的基础上——至少与使用手动定义的pi常量一样

S. Lott暗示的一个警告是,GCC 实现必须M_PI包含double数据类型,而不是long double,以确保等效性。Java 和 GCC 似乎都使用 IEEE-754 的 64 位十进制表示来表示它们各自的double数据类型。库值的字节表示(MSB 到 LSB),表示为 a double,可以如下获得(感谢JeeBee):

pi_bytes.c:

#include <math.h>
#include <stdio.h>
int main()
{
   double pi = M_PI;
   printf("%016llx\n", *((uint64_t*)&pi));
}

pi_bytes.java:

class pi_bytes
{
   public static void main(String[] a)
   {
      System.out.printf("%016x\n", Double.doubleToRawLongBits( Math.PI ) );
   }
}

同时运行:

$ gcc -lm -o pi_bytes pi_bytes.c && ./pi_bytes
400921fb54442d18

$ javac pi_bytes.java && java pi_bytes
400921fb54442d18

M_PI(as a double) 和的底层表示Math.PI是相同的,直到它们的位。

† – 正如Steve Schnepp所指出的,数学函数(如 sin、cos、exp 等)的输出不能保证相同,即使这些计算的输入是按位相同的。

于 2009-05-05T20:07:14.980 回答
1

一个双精度只有 52 位符号,所以我认为这只会给你大约 15 个基数 10 位数字,这可以解释为什么当你要求 20 位数字时你有 5 个零。

于 2009-05-05T16:11:35.983 回答
1

您可以使用 BigDecimal 获得更高的准确性,例如:

private static final BigDecimal PI = new BigDecimal(
"3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679" +
    "8214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196" +
    "4428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273" +
    "7245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094" +
    "3305727036575959195309218611738193261179310511854807446237996274956735188575272489122793818301194912" +
    "9833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132" +
    "0005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235" +
    "4201995611212902196086403441815981362977477130996051870721134999999837297804995105973173281609631859" +
    "5024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303" +
    "5982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989" +
    "3809525720106548586327886593615338182796823030195203530185296899577362259941389124972177528347913151" +
    "5574857242454150695950829533116861727855889075098381754637464939319255060400927701671139009848824012" +
    "8583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912" +
    "9331367702898915210475216205696602405803815019351125338243003558764024749647326391419927260426992279" +
    "6782354781636009341721641219924586315030286182974555706749838505494588586926995690927210797509302955" +
    "3211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000" +
    "8164706001614524919217321721477235014144197356854816136115735255213347574184946843852332390739414333" +
    "4547762416862518983569485562099219222184272550254256887671790494601653466804988627232791786085784383" +
    "8279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863" +
    "0674427862203919494504712371378696095636437191728746776465757396241389086583264599581339047802759009" +
    "9465764078951269468398352595709825822620522489407726719478268482601476990902640136394437455305068203" +
    "4962524517493996514314298091906592509372216964615157098583874105978859597729754989301617539284681382" +
    "6868386894277415599185592524595395943104997252468084598727364469584865383673622262609912460805124388" +
    "4390451244136549762780797715691435997700129616089441694868555848406353422072225828488648158456028506" +
    "0168427394522674676788952521385225499546667278239864565961163548862305774564980355936345681743241125"
);

public static void main(String... args) throws InterruptedException {
    System.out.println("PI to " + PI.scale() + " digits is " + PI);
    System.out.println("PI^2 to " + PI.scale() + " digits is " + 
            PI.multiply(PI).setScale(PI.scale(), BigDecimal.ROUND_HALF_UP));
}
于 2009-05-05T20:07:24.327 回答
-1

带回必须在 fortran 中获取 pi 值的记忆。

由于没有常量库,我要么使用 4*atan(1.) 要么使用 acos(-1.)。

于 2009-05-07T23:59:18.480 回答
-3

不,它们不相等,它们在记忆中的表现不同。

一般来说,当你想比较 2 个浮点值时,你不能使用 == (如果是这样,你就不能使用 termin 'equals' 进行操作)。您应该使用与 epsilon 的比较。

double eps = 0.0000001;
if (Math.abs (Java_PI - Another_Pi) <= eps)
  System.out.println ("equals");
于 2009-05-05T16:06:22.783 回答