Manipulating and comparing floating points in java

28,396

Solution 1

It's a general rule that floating point number should never be compared like (a==b), but rather like (Math.abs(a-b) < delta) where delta is a small number.

A floating point value having fixed number of digits in decimal form does not necessary have fixed number of digits in binary form.

Addition for clarity:

Though strict == comparison of floating point numbers has very little practical sense, the strict < and > comparison, on the contrary, is a valid use case (example - logic triggering when certain value exceeds threshold: (val > threshold) && panic();)

Solution 2

If you are interested in fixed precision numbers, you should be using a fixed precision type like BigDecimal, not an inherently approximate (though high precision) type like float. There are numerous similar questions on Stack Overflow that go into this in more detail, across many languages.

Solution 3

I think it has nothing to do with Java, it happens on any IEEE 754 floating point number. It is because of the nature of floating point representation. Any languages that use the IEEE 754 format will encounter the same problem.

As suggested by David above, you should use the method abs of java.lang.Math class to get the absolute value (drop the positive/negative sign).

You can read this: http://en.wikipedia.org/wiki/IEEE_754_revision and also a good numerical methods text book will address the problem sufficiently.

public static void main(String[] args) {
    float a = 1.2f;
    float b = 3.0f;
    float c = a * b;
        final float PRECISION_LEVEL = 0.001f;
    if(Math.abs(c - 3.6f) < PRECISION_LEVEL) {
        System.out.println("c is 3.6");
    } else {
        System.out.println("c is not 3.6");
    }
}

Solution 4

I’m using this bit of code in unit tests to compare if the outcome of 2 different calculations are the same, barring floating point math errors.

It works by looking at the binary representation of the floating point number. Most of the complication is due to the fact that the sign of floating point numbers is not two’s complement. After compensating for that it basically comes down to just a simple subtraction to get the difference in ULPs (explained in the comment below).

/**
 * Compare two floating points for equality within a margin of error.
 * 
 * This can be used to compensate for inequality caused by accumulated
 * floating point math errors.
 * 
 * The error margin is specified in ULPs (units of least precision).
 * A one-ULP difference means there are no representable floats in between.
 * E.g. 0f and 1.4e-45f are one ULP apart. So are -6.1340704f and -6.13407f.
 * Depending on the number of calculations involved, typically a margin of
 * 1-5 ULPs should be enough.
 * 
 * @param expected The expected value.
 * @param actual The actual value.
 * @param maxUlps The maximum difference in ULPs.
 * @return Whether they are equal or not.
 */
public static boolean compareFloatEquals(float expected, float actual, int maxUlps) {
    int expectedBits = Float.floatToIntBits(expected) < 0 ? 0x80000000 - Float.floatToIntBits(expected) : Float.floatToIntBits(expected);
    int actualBits = Float.floatToIntBits(actual) < 0 ? 0x80000000 - Float.floatToIntBits(actual) : Float.floatToIntBits(actual);
    int difference = expectedBits > actualBits ? expectedBits - actualBits : actualBits - expectedBits;

    return !Float.isNaN(expected) && !Float.isNaN(actual) && difference <= maxUlps;
}

Here is a version for double precision floats:

/**
 * Compare two double precision floats for equality within a margin of error.
 * 
 * @param expected The expected value.
 * @param actual The actual value.
 * @param maxUlps The maximum difference in ULPs.
 * @return Whether they are equal or not.
 * @see Utils#compareFloatEquals(float, float, int)
 */
public static boolean compareDoubleEquals(double expected, double actual, long maxUlps) {
    long expectedBits = Double.doubleToLongBits(expected) < 0 ? 0x8000000000000000L - Double.doubleToLongBits(expected) : Double.doubleToLongBits(expected);
    long actualBits = Double.doubleToLongBits(actual) < 0 ? 0x8000000000000000L - Double.doubleToLongBits(actual) : Double.doubleToLongBits(actual);
    long difference = expectedBits > actualBits ? expectedBits - actualBits : actualBits - expectedBits;

    return !Double.isNaN(expected) && !Double.isNaN(actual) && difference <= maxUlps;
}

Solution 5

This is a weakness of all floating point representations, and it happens because some numbers that appear to have a fixed number of decimals in the decimal system, actually have an infinite number of decimals in the binary system. And so what you think is 1.2 is actually something like 1.199999999997 because when representing it in binary it has to chop off the decimals after a certain number, and you lose some precision. Then multiplying it by 3 actually gives 3.5999999...

http://docs.python.org/py3k/tutorial/floatingpoint.html <- this might explain it better (even if it's for python, it's a common problem of the floating point representation)

Share:
28,396
Praneeth
Author by

Praneeth

Updated on November 21, 2020

Comments

  • Praneeth
    Praneeth over 3 years

    In Java the floating point arithmetic is not represented precisely. For example this java code:

    float a = 1.2; 
    float b= 3.0;
    float c = a * b; 
    if(c == 3.6){
        System.out.println("c is 3.6");
    } 
    else {
        System.out.println("c is not 3.6");
    } 
    

    Prints "c is not 3.6".

    I'm not interested in precision beyond 3 decimals (#.###). How can I deal with this problem to multiply floats and compare them reliably?