如何确定数字是否可以精确表示为浮点数?

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

由于浮点数是以 2 为基数的数字系统,因此不可能直接表示 0.24F 相同,如果没有 recurring decimal period,则不可能在十进制系统中表示 1/3,即 1/3=0.3333...0.(3).

因此,当打印回十进制表示法时,浮点数 0.24F 显示为 0.23,但由于四舍五入而发生变化:

println(0.24F) => 0.23999999463558197021484375

0.25F可以直接显示:

println(0.25F) => 0.25

但是我怎样才能确定一个数字是精确表示的呢?

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

也许 Java API 已经有一些功能可以做到这一点?

UPD 这是一个代码,它显示 [-4, 4] 范围内的浮点数及其内部表示:

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);
    }
}

当尾数为零时,浮点数绝对准确。但在其他情况下,如 -0.375-1.625 就不太清楚了。

从中创建一个 BigDecimal 并捕获 java.lang.ArithmeticException 如果存在不终止的十进制扩展,它将抛出。

一般来说,这是不可能的。 一旦将数字转换为浮点数或双精度数,它就只是数字的近似值。因此您对 isexactfloat() 的输入将不准确...

如果您有准确的浮点数版本,例如字符串格式,那么就可以设计一个函数来告诉您浮点数或双精度数是否准确表示字符串格式的数字。请参阅下面 Carlos Heurberger 关于如何实现此类功能的评论。

不清楚您的问题是否与精度(准确表示 0.24)或循环数字有关,例如 1 / 3.0。

一般来说,如果您使用传统的浮点表示法,精度问题总是会悄悄出现。

如果精度对您来说是一个真正的问题,您应该考虑使用 BigDecimal。虽然不如 double 灵活,但它具有其他优点,例如任意精度,并且您还可以控制非精确计算(例如循环十进制值)中的舍入行为。

如果您只追求精度控制,您可能需要查看 Apache Commons Math Precision class。

您可以只比较 doublefloat?

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

Demo

Java double 只能表示终止二进制分数。转换为 double 可能会隐藏问题,因此我认为最好从 String 表示开始工作。如果 String 表示数字,则转换为 BigDecimal 是准确的。从 floatdoubleBigDecimal 的转换也是如此。以下是精确表示为 floatdouble:

的测试函数
  public static boolean isExactDouble(String data) {
    BigDecimal rawBD = new BigDecimal(data);
    double d = rawBD.doubleValue();
    BigDecimal cookedBD = new BigDecimal(d);
    return cookedBD.compareTo(rawBD) == 0;
  }

  public static boolean isExactFloat(String data) {
    BigDecimal rawBD = new BigDecimal(data);
    float d = rawBD.floatValue();
    BigDecimal cookedBD = new BigDecimal(d);
    return cookedBD.compareTo(rawBD) == 0;
  }

我想在这里分享这个功能

// 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;
}

编辑:上面的函数可以更短

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