java icon indicating copy to clipboard operation
java copied to clipboard

[Java track] Tests for exercise `rational-numbers` are flaky

Open ndlarsen opened this issue 1 year ago • 0 comments

Track: Java Exercise rational-numbers Problem: The one or more tests for the exercise are flaky/non-deterministic

The test case testRaiseARealNumberToAPositiveRationalNumber, fails when I run it locally but passes when submitted to the exercism build servers.

vscode ➜ /workspaces/java $ gradle rational-numbers:test

> Task :rational-numbers:test FAILED

RationalTest > testReduceARationalNumberWithANegativeDenominatorToLowestTerms() PASSED

RationalTest > testSubtractTwoNegativeRationalNumbers() PASSED

RationalTest > testRaiseANegativeRationalNumberToAPositiveIntegerPower() PASSED

RationalTest > testReducePlacesTheMinusSignOnTheNumerator() PASSED

RationalTest > testMultiplyARationalNumberByZero() PASSED

RationalTest > testAbsoluteValueOfAPositiveRationalNumberWithNegativeNumeratorAndDenominator() PASSED

RationalTest > testAbsoluteValueOfANegativeRationalNumber() PASSED

RationalTest > testAddARationalNumberToItsAdditiveInverse() PASSED

RationalTest > testRaiseZeroToAnIntegerPower() PASSED

RationalTest > testRaiseANegativeRationalNumberToAnEvenNegativeIntegerPower() PASSED

RationalTest > testReduceOneToLowestTerms() PASSED

RationalTest > testMultiplyARationalNumberByItsReciprocal() PASSED

RationalTest > testDivideAPositiveRationalNumberByANegativeRationalNumber() PASSED

RationalTest > testRaiseAPositiveRationalNumberToANegativeIntegerPower() PASSED

RationalTest > testReduceANegativeRationalNumberToLowestTerms() PASSED

RationalTest > testRaiseOneToAnIntegerPower() PASSED

RationalTest > testRaiseANegativeRationalNumberToAnOddNegativeIntegerPower() PASSED

RationalTest > testAddTwoPositiveRationalNumbers() PASSED

RationalTest > testDivideTwoPositiveRationalNumbers() PASSED

RationalTest > testAddAPositiveRationalNumberAndANegativeRationalNumber() PASSED

RationalTest > testRaiseARealNumberToAPositiveRationalNumber() FAILED
    java.lang.AssertionError: 
    Expecting actual:
      16.0
    to be close to:
      15.999999999999998
    by less than 1.0E-15 but difference was 2.0E-15.
    (a difference of exactly 1.0E-15 being considered valid)
        at RationalTest.assertDoublesEqual(RationalTest.java:13)
        at RationalTest.testRaiseARealNumberToAPositiveRationalNumber(RationalTest.java:246)

RationalTest > testDivideARationalNumberByOne() PASSED

RationalTest > testSubtractARationalNumberFromItself() PASSED

RationalTest > testMultiplyTwoPositiveRationalNumbers() PASSED

RationalTest > testAbsoluteValueOfZero() PASSED

RationalTest > testRaiseARealNumberToANegativeRationalNumber() PASSED

RationalTest > testReduceAPositiveRationalNumberToLowestTerms() PASSED

RationalTest > testMultiplyANegativeRationalNumberByAPositiveRationalNumber() PASSED

RationalTest > testSubtractTwoPositiveRationalNumbers() PASSED

RationalTest > testAbsoluteValueOfARationalNumberIsReducedToLowestTerms() PASSED

RationalTest > testMultiplyARationalNumberByOne() PASSED

RationalTest > testAbsoluteValueOfANegativeRationalNumberWithNegativeDenominator() PASSED

RationalTest > testAddTwoNegativeRationalNumbers() PASSED

RationalTest > testDivideTwoNegativeRationalNumbers() PASSED

RationalTest > testRaiseAPositiveRationalNumberToAPositiveIntegerPower() PASSED

RationalTest > testAbsoluteValueOfAPositiveRationalNumber() PASSED

RationalTest > testMultiplyTwoNegativeRationalNumbers() PASSED

RationalTest > testRaiseAPositiveRationalNumberToThePowerOfZero() PASSED

RationalTest > testReduceAnIntegerToLowestTerms() PASSED

RationalTest > testSubtractAPositiveRationalNumberAndANegativeRationalNumber() PASSED

RationalTest > testReduceZeroToLowestTerms() PASSED

41 tests completed, 1 failed

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':rational-numbers:test'.
> There were failing tests. See the report at: file:///workspaces/java/rational-numbers/build/reports/tests/test/index.html

* Try:
> Run with --scan to get full insights.

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.9/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD FAILED in 1s
3 actionable tasks: 2 executed, 1 up-to-date

The implementation is the same as pretty much everyone else have submitted

    double exp(double exponent) {
        return Math.pow(Math.pow(exponent, this.numerator), 1.0 / this.denominator);
    }

I have been banging my head against the table for quite at while due to this and needless to say, that is no fun. I do not have any solution for this but I suggest that the tests are rewritten to eliminate the current inherent unreliability. Given the current amount of submissions for the exercise on the track I would be quite surprised if no one but me has encountered the issue.

To be fair, a member already pointed this out while solving the exercise at https://exercism.org/tracks/java/exercises/rational-numbers/solutions/nunoguedelha

        /* exponent^(a/b) = (exponent^a)^(1/b)
           If we compute ^(1/b) using the Math.pow function, we cannot a priori fully control the accuracy.
           Typically, we might not reach the accuracy of 1e-15 as the exercise tests require. For instance:
           Math.pow(8.0, 1.0/3) returns the exact result 2.0, such that if we compute 8^(4/3) as
           Math.pow(Math.pow(8.0, 1.0/3), 4) we get the exact result 16.0, but if we compute it as
           Math.pow(Math.pow(8.0, 4), 1.0/3) we get 15.999999999999998 which has an accuracy of 2e-15 > 1e-15,
           and this causes the test to fail.
           But in general, Math.pow(Math.pow(x, 4), 1.0/3) should provide a better accuracy since only the
           last operation generates an approximation error. Instead, doing it the other way around amplifies
           the error of the first operation.
           The example Math.pow(Math.pow(8, 1.0/3), 4) is a lucky case, but the simple case Math.pow(4096, 1.0/3)
           illustrates well the problem.
           Computing ^(1/b) with the Heron's method allows us to better control the accuracy and reach the desired
           1e-15.
         */

ndlarsen avatar Jul 16 '24 15:07 ndlarsen