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?
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:
In C, you can do
double pi = M_PI; printf("%lld\n", pi);
to obtain the same 64bit integer: 4614256656552045848 (thanks Bruno).You can use BigDecimal for more accuracy like:
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 adouble
data type, and notlong double
, to ensure equivalence. Both Java and GCC appear to use IEEE-754's 64-bit decimal representation for their respectivedouble
data types. The bytewise representation (MSB to LSB) of the library value, expressed as adouble
, can be obtained as follows (thanks to JeeBee):pi_bytes.c:
pi_bytes.java:
Running both:
The underlying representations of
M_PI
(as adouble
) andMath.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.
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.
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.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.