Search code examples
javafloating-pointieee-754

How to determine whether number is exactly representable in floating-point?


Since float numbers are base-2 numeral system then it's not possible to represent 0.24F directly as the same it's not possible to represent 1/3 in decimal system without recurring decimal period i.e. 1/3=0.3333... or 0.(3).

So the float number 0.24F when printed back to decimal representation is shown as 0.23 with a change due to rounding:

println(0.24F) => 0.23999999463558197021484375

while 0.25F can be shown directly:

println(0.25F) => 0.25

But how can I determine that a number is exactly representable?

isExactFloat(0.25F) ==> true
isExactFloat(0.24F) ==> false

Maybe Java API has already some function to do that?

UPD Here is a code which shows float numbers in range [-4, 4] with their internal representation:

public class FloatDestructure {
    public static void main(String[] args) {
        BigDecimal dec = BigDecimal.valueOf(-4000L, 3);
        BigDecimal incr = BigDecimal.valueOf(1L, 3);
        for (int i = 0; i <= 8000; i++) {
            double dbl = dec.doubleValue();
            floatDestuct(dbl, dec);
            dec = dec.add(incr);
        }

    }
    static boolean isExactFloat(double d) { return d == (float) d; }

    static void floatDestuct(double val, BigDecimal dec) {
        float value = (float) val;
        int bits = Float.floatToIntBits(value);
        int sign = bits >>> 31;
        int exp = (bits >>> 23 & ((1 << 8) - 1)) - ((1 << 7) - 1);
        int mantissa = bits & ((1 << 23) - 1);
        float backToFloat = Float.intBitsToFloat((sign << 31) | (exp + ((1 << 7) - 1)) << 23 | mantissa);
        boolean exactFloat = isExactFloat(val);
        boolean exactFloatStr = Double.toString(value).length() <= 7;
        System.out.println(dec.toString() + " " + (double) val + " " + (double) value + " sign: " + sign + " exp: " + exp + " mantissa: " + mantissa + " " + Integer.toBinaryString(mantissa) + " " + (double) backToFloat + " " + exactFloat + " " + exactFloatStr);
    }
}

When mantissa is zero then the float is definitely exact. But in other cases like -0.375 or -1.625 it's not so clear.


Solution

  • I would like to share this function here.

    // Determine whether number is exactly representable in double.
    // i.e., No rounding to an approximation during the conversion.
    // Results are valid for numbers in the range [2^-24, 2^52].
    
    public static boolean isExactFloat(double val) {
    
        int exp2 = Math.getExponent(val);
        int exp10 = (int) Math.floor(Math.log10(Math.abs(val)));
    
        // check for any mismatch between the exact decimal and
        // the round-trip representation.
        int rightmost_bits = (52 - exp2) - (16 - exp10);
    
        // create bitmask for rightmost bits
        long mask = (1L << rightmost_bits) - 1;
    
        // test if all rightmost bits are 0's (i.e., no rounding)
        return (Double.doubleToLongBits(val) & mask) == 0;
    }
    

    Edit: the above function could be even shorter

    public static boolean isExactFloat(double val) {
    
        int exp2 = Math.getExponent(val);
        int exp10 = (int) Math.floor(Math.log10(Math.abs(val)));
    
        long bits = Double.doubleToLongBits(val);
    
        // test if at least n rightmost bits are 0's (i.e., no rounding)
        return Long.numberOfTrailingZeros(bits) >= 36 - exp2 + exp10;        
    }
    

    Demo