Возможно ли, что число, точно представленное как число с плавающей запятой, НЕ может быть точно представлено как двойное?

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

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

Мой вопрос на самом деле о разнице в представлении float и double.

Откуда возникает этот вопрос?

Предположим, я делаю:

System.out.println(.475d+.075d);

тогда вывод будет не 0.55, а 0.549999 (на моей машине)

Однако, когда я делаю:

System.out.println(.475f+.075f);

Я получаю правильный ответ, т.е. 0.55 (немного неожиданно для меня)

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

Однако результаты, которые я получил, немного тревожат меня. Я смущен, если:

  1. Я неправильно понимаю, что означает precision?
  2. float и double представлены по-разному, кроме того факта, что у double больше бит?

person Ankit    schedule 08.06.2012    source источник


Ответы (3)


Точность просто означает больше битов. Число, которое не может быть представлено в виде float, может иметь точное представление в виде double, но число таких случаев бесконечно мало по сравнению с общим числом возможных случаев.

Для простых случаев, таких как 0.1, это не может быть представлено как число фиксированной длины с плавающей запятой, независимо от того, какое количество доступных битов. Это то же самое, что сказать, что такая дробь, как 1/7, не может быть представлена ​​точно в десятичном виде, независимо от количества цифр, которые вам разрешено использовать (пока количество цифр конечно). Вы можете аппроксимировать это как 0,142857142857142857... повторяя снова и снова, но вы никогда не сможете написать это ТОЧНО, независимо от того, как долго вы продолжаете.

И наоборот, если число можно представить точно как float, оно также будет представлено точно как double. Двойной имеет больший диапазон экспоненты и больше битов мантиссы.

В вашем примере причина очевидного несоответствия заключается в том, что в float разница между 0,475 и его представлением с плавающей запятой была в «правильном» направлении, поэтому, когда произошло усечение, оно произошло так, как вы ожидали. При увеличении доступной точности представление было «ближе» к 0,475, но теперь с противоположной стороны. В качестве грубого примера предположим, что ближайшее возможное значение с плавающей запятой было 0,475006, но в двойном ближайшем возможном значении было 0,474999. Это даст вам результаты, которые вы видите.

Изменить: Вот результаты быстрого эксперимента:

public class Test {

    public static void main(String[] args)
    {
        float  f = 0.475f;
        double d = 0.475d;

        System.out.printf("%20.16f", f);
        System.out.printf("%20.16f", d);
    }
}

Вывод:

  0.4749999940395355  0.4750000000000000

Это означает, что представление числа 0,475 с плавающей запятой, если бы у вас было огромное количество битов, было бы лишь чуть-чуть меньше 0,475. Это видно в двойном представлении. Однако первый «неправильный» бит находится настолько далеко вправо, что при усечении, чтобы поместиться в float, получается 0,475. Это чисто случайность.

person Jim Garrison    schedule 08.06.2012
comment
Я спросил совершенно противоположное. Может ли число, представленное точно как float, не быть правильно представленным как double - person Ankit; 09.06.2012
comment
На самом деле этот ответ также правильный. Поменять местами double на float. В любом случае речь идет о том, какие числа могут быть представлены как двоичные, а какие нет. Итак, да, число, точное как число с плавающей запятой, может потерять точность как двойное. - person Jim Barrows; 09.06.2012
comment
@JimGarrison Я думал о том же if a number is representable exactly as a float, it will also be representable exactly as a double. Однако, как видно из вопроса, это не так. float представлен, но не двойной - person Ankit; 09.06.2012
comment
Если число, представленное float, не окажется точно в центре диапазона чисел, которые может представлять float, преобразование в double часто будет давать значение, которое, по сравнению с подразумеваемой точностью, просто неверно. Напротив, преобразования из double в float почти всегда будут давать результаты с точностью до подразумеваемой точности, и даже если они не совсем точны, они будут отличаться на долю дополнительной части на миллион. - person supercat; 09.06.2012

Число, которое может быть представлено как float, также может быть представлено как double.

То, что вы читаете, является просто форматированным выводом, вы не читаете фактическое двоичное представление.

System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(.475d + .075d)));
// 11111111100001100110011001100110011001100110011001100110011001
System.out.println(Integer.toBinaryString(Float.floatToRawIntBits(.475f + .075f)));
// 111111000011001100110011001101

double d = .475d + .075d;
System.out.println(d);
// 0.5499999999999999
System.out.println((float)d);
// 0.55 (as expected)
System.out.println((double)(float)d);
// 0.550000011920929

System.out.println( .475f + .075f == 0.550000011920929d);
// true
person Piotr Findeisen    schedule 08.06.2012

Если учесть, что типы с плавающей запятой фактически представляют диапазоны значений, а не дискретные значения (например, 0.1f представляет не 13421773/134217728, а скорее «что-то между 13421772,5/134217728 и 13421773,5/134217728»), преобразования из double в float будут обычно точны, а преобразования из float в double обычно нет. К сожалению, Java позволяет неявно выполнять обычно неточные преобразования, требуя приведения типов в обычно точном направлении.

Для каждого значения типа float существует значение типа double, диапазон которого находится в центре диапазона float. Это не означает, что double является точным представлением значения с плавающей запятой. Например, преобразование 0.1f в double дает значение, означающее «что-то между 13421772,9999999/134217728 и 13421773,0000001/134217728», значение, которое более чем в миллион раз превышает подразумеваемый допуск.

Почти для каждого значения типа double существует значение типа float, диапазон которого полностью включает диапазон, подразумеваемый double. Единственным исключением являются значения, диапазон которых находится точно на границе между двумя значениями float. Преобразование таких значений в float потребует, чтобы система выбрала тот или иной диапазон; если система округляет в большую сторону, когда double фактически представляет число ниже центра своего диапазона, или наоборот, диапазон float не будет полностью охватывать диапазон double. С практической точки зрения, это не проблема, так как это означает, что вместо float от double, представляющего диапазон, подобный (13421772,5/134217728 до 13421773,5/134217728), он будет представлять диапазон, подобный (13421772,4999999/134217728 до 13421773. /134217728). По сравнению с ужасающей неточностью, возникающей в результате приведения от float к double, эта крошечная неточность — ничто.

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

0.075f = 20132660±½ / 268435456
0.475f = 31876710±½ /  67108864
Sum    = 18454938±½ /  33554432

Другими словами, сумма представляет собой число примерно между 0,54999999701 и 0,55000002682. Наиболее естественным представлением является 0,55 (поскольку фактическое значение может быть больше или меньше этого значения, дополнительные цифры не имеют смысла).

person supercat    schedule 08.06.2012