Как определить, является ли число точно представимым в плавающей точке? - PullRequest
0 голосов
/ 08 октября 2018

Поскольку числа с плавающей запятой представляют собой систему счисления с основанием-2, то невозможно представить 0.24F непосредственно как то же самое, что невозможно представить 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, это не так ясно.

Ответы [ 5 ]

0 голосов
/ 09 октября 2018

Java double может представлять только конечные двоичные дроби.Выполнение преобразования в double может скрыть проблемы, поэтому я думаю, что лучше работать с представлением String.Преобразование в BigDecimal является точным, если строка представляет число.То же самое происходит и с float или double до BigDecimal.Вот тестовые функции для точного представления в виде float или double:

  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;
  }
0 голосов
/ 08 октября 2018

Вы можете просто сравнить double и float?

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

Демо

0 голосов
/ 08 октября 2018

В общем, это невозможно.Как только число конвертируется в число с плавающей запятой или двойное число, это всего лишь приблизительное число.Таким образом, ваш ввод для isexactfloat () не будет точным ...

Если у вас есть точная версия числа с плавающей запятой, например, в строковом формате, то можно было бы разработать функцию, которая могла бы сообщить вам, еслиfloat или double точно представляет форматированный номер строки или нет.См. Комментарий Карлоса Хюрбергера ниже о том, как реализовать такую ​​функцию.

0 голосов
/ 08 октября 2018

Непонятно, связана ли ваша проблема с точностью (с точностью до 0,24) или с повторяющимися числами, например 1 / 3,0.

В общем, проблемы точности всегда будут возникать, если вы используете обычные представления с плавающей запятой.

Если точность является реальной проблемой для вас, вы должны рассмотреть использование BigDecimal .Хотя он и не такой гибкий, как double, он обладает и другими преимуществами, такими как произвольная точность, и вы также можете контролировать поведение округления в неточных вычислениях (например, повторяющиеся десятичные значения).

Если все, что вам нужно, это контроль точности, вы можете обратиться к классу Apache Commons Math Precision .

0 голосов
/ 08 октября 2018

Создайте BigDecimal из него и поймайте java.lang.ArithmeticException, который он сгенерирует, если есть бесконечное десятичное расширение.

...