mtommila/apfloat

JUnit test gives different results for Windows and other systems

Closed this issue · 3 comments

axkr commented

In the Symja project I have the following problem with Bitbucket Maven pipeline.
See this commit for the JUnit test case

This JUnit test gives different results for Windows and other systems:

	public void testApfloat() {
		// simulate Symja expr: N(Pi, 30) + E
		Apfloat f = ApfloatMath.pi(30).add(new Apfloat(Math.E, 30));

		assertEquals(f.toString(), "5.85987448204883829099102473027");
	}

Is this an apfloat bug or a JVM dependent problem?

testApfloat(org.matheclipse.core.system.NumberTest)  Time elapsed: 0 sec  <<< FAILURE!
junit.framework.ComparisonFailure: expected:<5.859874482048838[358462643383]27> but was:<5.859874482048838[290991024730]27>
	at junit.framework.Assert.assertEquals(Assert.java:100)
	at junit.framework.Assert.assertEquals(Assert.java:107)
	at junit.framework.TestCase.assertEquals(TestCase.java:269)
	at org.matheclipse.core.system.NumberTest.testApfloat(NumberTest.java:24)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)

The example code provided above uses the "double constructor caveat" described in the tutorial:
http://www.apfloat.org/apfloat_java/tutorial.html#caveats

Indeed the behavior can be platform dependent, but I don't think this is really a fault in the apfloat library. Different JVMs simply can behave in different ways. The apfloat methods have not been marked "strictfp" so this is possible.

For more predictable results you could change the code e.g. to:

new Apfloat(String.valueOf(Math.E), 30)
or
new Apfloat(new BigDecimal(Math.E), 30)

depending on how you want to calculate it.

The apfloat double constructor doesn't use either of the above mechanisms but it just repeatedly multiplies the double, as a double, resulting in platform-dependent round-off errors (in the absence of "strictfp"). This is done for performance reasons. However, I do kind of agree that the whole constructor is pretty useless. Then again there is no way to make it work in a greatly better way. E.g. you cannot expect to get E to a precision of 30 digits with "new Apfloat(Math.E, 30)" because Math.E is accurate to only 16 decimal digits anyway. The information does not exist.

As a double value, the exact decimal value of Math.E is some integer multiplied by a power of two. It can be retrieved e.g. with "new BigDecimal(Math.E)", which is 2.718281828459045090795598298427648842334747314453125, but obviously only the first 16 digits are correct.

Also, Apfloats are internally (by default) stored in radix-10 whereas doubles use radix-2.

It's a bit unclear to me why the expected result is exactly 5.85987448204883829099102473027? Is this just the result on some particular platform?

axkr commented

5.85987448204883829099102473027 is the result on the Wndows platform.

The failed JUnit test case comes from the Bitbucket Pipeline remote testing platform.

axkr commented

At least this first commit which uses your new Apfloat(new BigDecimal(), 30) hack seems to work:
https://bitbucket.org/axelclk/symja_android_library/commits/d651ef8c9670c9819adb086b3c3f6d0228100f66

Thanks for this suggestion