Question

I am coding several reference algorithms in both Java and C/C++. Some of these algorithms use π. I would like for the two implementations of each algorithm to produce identical results, without rounding differently. One way to do this that has worked consistently so far is to use a custom-defined pi constant which is exactly the same in both languages, such as 3.14159. However, it strikes me as silly to define pi when there are already high-precision constants defined in both the Java and GCC libraries.

I've spent some time writing quick test programs, looking at documentation for each library, and reading up on floating-point types. But I haven't been able to convince myself that java.lang.Math.PI (or java.lang.StrictMath.PI) is, or is not, equal to M_PI in math.h.

GCC 3.4.4 (cygwin) math.h contains:

#define M_PI            3.14159265358979323846
                                         ^^^^^

but this

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

produces

3.14159265358979311600
                 ^^^^^

which suggests that the last 5 digits cannot be trusted.

Meanwhile, Javadocs say that java.lang.Math.PI is:

The double value that is closer than any other to pi, the ratio of the circumference of a circle to its diameter.

and

public static final double PI  3.141592653589793d

which omits the questionable last five digits from the constant.

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

produces

3.14159265358979300000
                 ^^^^^

If you have some expertise in floating-point data types, can you convince me that these library constants are exactly equal? Or that they are definitely not equal?

Was it helpful?

Solution 4

Yes, they are equal, and using them will insure that GCC and Java implementations of the same algorithm are on the same footing – at least as much as using a hand-defined pi constant would.

One caveat, hinted by S. Lott, is that the GCC implementation must hold M_PI in a double data type, and not long double, to ensure equivalence. Both Java and GCC appear to use IEEE-754's 64-bit decimal representation for their respective double data types. The bytewise representation (MSB to LSB) of the library value, expressed as a double, can be obtained as follows (thanks to 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 ) );
   }
}

Running both:

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

$ javac pi_bytes.java && java pi_bytes
400921fb54442d18

The underlying representations of M_PI (as a double) and Math.PI are identical, down to their bits.

† – As noted by Steve Schnepp, the output of math functions such as sin, cos, exp, etc. is not guaranteed to be identical, even if the inputs to those computations are bitwise identical.

OTHER TIPS

Note the following.

The two numbers are the same to 16 decimal places. That's almost 48 bits which are the same.

In an IEEE 64-bit floating-point number, that's all the bits there are that aren't signs or exponents.

The #define M_PI has 21 digits; that's about 63 bits of precision, which is good for an IEEE 80-bit floating-point value.

What I think you're seeing is ordinary truncation of the bits in the M_PI value.

What you want to do is print out the raw bit pattern for the PI values and compare them.

In Java use the http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Double.html#doubleToRawLongBits(double) method to get the long value that you should print as binary.

Java 5 gives:

  • PI is 3.141592653589793
  • Raw bits is 4614256656552045848
  • Binary is 100000000001001001000011111101101010100010001000010110100011000

In C, you can do double pi = M_PI; printf("%lld\n", pi); to obtain the same 64bit integer: 4614256656552045848 (thanks Bruno).

It would be very difficult to compute the same value, even if the starting values are the same.

Floating point computation results are sometime different from an architecture to another (think x86/PowerPC for example), from a compiler to another (think GCC/MS C++) and even with the same compiler, but different compilation options. Not always, but sometimes (usually when rounding). Usually just enough for the problem to go unnoticed until too late (think after many many iterations, and many many rounding differences)

This make it quite difficult for cross-platform multi-player games that compute each iteration of the gamestate synchronously (each node only receives the input, not the actual data structures).

Therefore if even in the same language (C/C++) results can be different, from a Java VM to a native host it may also be different.

Update:

I cannot find the source I read, but I found a paper by Sun on the matter.

Like you answered yourself, java.lang.Math.PI and GCC’s M_PI can be managed to have the same value. The devil hides in the usage of these values. IEEE doesn't specify the output of math functions (sin, cos, exp, ...). Therefore it's the output of the computation that isn't necessarily the same.

a double only has like 52 bits of signficand so i think that only gives you around 15 base 10 digits which would explain why you have 5 zeroes when you ask for 20 digits.

You can use BigDecimal for more accuracy like:

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));
}

Brings back memories of having to get a value for pi in fortran.

Since there were no libraries of constants, I either used 4*atan(1.) or acos(-1.).

No, they aren't equal, they have different presentation in memory.

In general when you want to compare 2 floating point values you mustn't use == (and if so you can't operate with termin 'equals'). You should use comparison with epsilon.

double eps = 0.0000001;
if (Math.abs (Java_PI - Another_Pi) <= eps)
  System.out.println ("equals");
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top