【问题标题】:Is java.lang.Math.PI equal to GCC's M_PI?java.lang.Math.PI 是否等于 GCC 的 M_PI?
【发布时间】:2009-05-05 15:53:21
【问题描述】:

我正在用 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
                 ^^^^^

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

【问题讨论】:

    标签: java gcc floating-point language-features pi


    【解决方案1】:

    注意以下几点。

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

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

    #define M_PI 有 21 位数字;这大约是 63 位精度,这对于 IEEE 80 位浮点值来说是很好的。

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

    【讨论】:

    • +1 在很长一段时间内进行极其精确的时间增量测量时恰好遇到了这个问题。使用 64 位双精度可以得到 15 位数字。
    【解决方案2】:

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

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

    Java 5 给出:

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

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

    【讨论】:

    • 在 C 中,你可以做 double pi = M_PI; printf("%lld\n", pi);获取相同的 64 位整数:4614256656552045848
    • 谢谢布鲁诺,这几乎回答了最初的问题。
    【解决方案3】:

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

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

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

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

    更新:

    我找不到我阅读的来源,但我找到了关于此事的paper by Sun

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

    【讨论】:

    • 也许这在一般情况下是一个真正的问题,但不是在这里。正如问题中所述,“迄今为止一直有效的一种方法是使用自定义定义的 pi 常量,这在两种语言中都完全相同,例如 3.14159。”
    【解决方案4】:

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

    S. Lott 暗示的一个警告是,GCC 实现必须将M_PI 保存在double 数据类型中,而不是long double,以确保等效。 Java 和 GCC 似乎都使用 IEEE-754 的 64 位十进制表示来表示它们各自的 double 数据类型。库值的字节表示(MSB 到 LSB),表示为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(作为double)和Math.PI 的底层表示是相同的,就其位而言。

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

    【讨论】:

    • 严格来说,C printf 测试是不确定的,因为它由于违反了严格的别名规则而具有未定义的行为。
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      您可以使用 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));
      }
      

      【讨论】:

        【解决方案7】:

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

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

        【讨论】:

          【解决方案8】:

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

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

          double eps = 0.0000001;
          if (Math.abs (Java_PI - Another_Pi) <= eps)
            System.out.println ("equals");
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-07-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-02-07
            • 1970-01-01
            相关资源
            最近更新 更多