Search code examples
javascriptjavafractions

Consistent evaluation and match the fractional values


I have a web application and search is powered by ElasticSearch.

I calculate fractional values in Java using ScriptEngine :

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
System.out.println("12*0.0254 = " + engine.eval("12*0.0254"));//0.30479999999999996
System.out.println("12000*0.0000254 = " + engine.eval("12000*0.0000254"));//0.3048

As seen in the SOP output in the comments, for the same input, the output gets rounded off differently.

In UI, I use JavaScript's built-in eval() function and that doesn't match with the inconsistent Java output.

What is the way in Java to get consistent and correct expression results, i.e. 12 * 0.0254 should always evaluate to 0.3048 instead of 0.30479999999999996 ?


Solution

  • Just because two expressions are mathematically equivalent, does not mean they evaluate to the same value in a given machine arithmetic.

    The Java Virtual Machine incorporates a subset of the floating-point arithmetic specified in IEEE Standard for Binary Floating-Point Arithmetic (ANSI/IEEE Std. 754-1985, New York).

    2.8.1. Java Virtual Machine Floating-Point Arithmetic and IEEE 754

    The key differences between the floating-point arithmetic supported by the > Java Virtual Machine and the IEEE 754 standard are:

    The floating-point operations of the Java Virtual Machine do not throw >exceptions, trap, or otherwise signal the IEEE 754 exceptional conditions of >invalid operation, division by zero, overflow, underflow, or inexact. The >Java Virtual Machine has no signaling NaN value.

    The Java Virtual Machine does not support IEEE 754 signaling floating->point comparisons.

    The rounding operations of the Java Virtual Machine always use IEEE 754 >round to nearest mode. Inexact results are rounded to the nearest >representable value, with ties going to the value with a zero least-significant bit. This is the IEEE 754 default mode. But Java Virtual Machine >instructions that convert values of floating-point types to values of > integral types round toward zero. The Java Virtual Machine does not give any means to change the floating-point rounding mode.

    The Java Virtual Machine does not support either the IEEE 754 single extended or double extended format, except insofar as the double and double-extended-exponent value sets may be said to support the single extended format. The float-extended-exponent and double-extended-exponent value sets, which may optionally be supported, do not correspond to the values of the IEEE 754 extended formats: the IEEE 754 extended formats require extended precision as well as extended exponent range.

    Source: https://docs.oracle.com/javase/specs/jvms/se10/html/jvms-2.html#jvms-2.8.1.

    In JavaScript I get the same result as you (https://jsfiddle.net/, Firefox Developer Edition 97.0b5 (64 Bit):

    console.log(12*0.0254);
    console.log(12000*0.0000254);
    
    0.30479999999999996
    0.3048
    

    Maybe your GUI framework applies rounding?

    You can format the output to a certain number of decimal places in Java like that:

    DecimalFormat df = new DecimalFormat("#.#####");
    System.out.println(df.format(12*0.0254));
    System.out.println(df.format(12000*0.0000254));