Почему `2.0 - 1.1` и` 2.0F - 1.1F` дают разные результаты? - PullRequest
2 голосов
/ 04 ноября 2019

Я работаю над кодом, в котором сравниваю Double и float значения:

class Demo {
    public static void main(String[] args) {
        System.out.println(2.0 - 1.1);            // 0.8999999999999999
        System.out.println(2.0 - 1.1 == 0.9);     // false

        System.out.println(2.0F - 1.1F);          // 0.9
        System.out.println(2.0F - 1.1F == 0.9F);  // true
        System.out.println(2.0F - 1.1F == 0.9);   // false
    }
}

Вывод приведен ниже:

0.8999999999999999
false
0.9
true
false

Я считаю,значение Double может сохранить больше точности, чем float.

Пожалуйста, объясните это, похоже, что значение float не теряет точность, а значение double одно теряет?


Редактировать: @goodvibration Я знаю, что 0,9 не может быть точно сохранен на любом языке компьютера, я просто запутался, как Java работает с этим подробно, почему 2.0F - 1.1F == 0.9F, но 2.0 - 1.1 != 0.9, еще одна интересная находка может помочь:

class Demo {
    public static void main(String[] args) {
        System.out.println(2.0 - 0.9);            // 1.1
        System.out.println(2.0 - 0.9 == 1.1);     // true

        System.out.println(2.0F - 0.9F);          // 1.1
        System.out.println(2.0F - 0.9F == 1.1F);  // true
        System.out.println(2.0F - 0.9F == 1.1);   // false
    }
}

Я знаю, что не могу рассчитывать на поплавок или двойную точность, просто ... не могу понять, сводит меня с ума, в чем реальная причинаэто? Почему 2.0 - 0.9 == 1.1, но 2.0 - 1.1 != 0.9 ??

Ответы [ 2 ]

1 голос
/ 04 ноября 2019

Разница между float и double:

Давайте запустим ваши числа в простой программе на C, чтобы получить их двоичные представления:

#include <stdio.h>

typedef union {
    float val;
    struct {
        unsigned int fraction : 23;
        unsigned int exponent :  8;
        unsigned int sign     :  1;
    } bits;
} F;

typedef union {
    double val;
    struct {
        unsigned long long fraction : 52;
        unsigned long long exponent : 11;
        unsigned long long sign     :  1;
    } bits;
} D;

int main() {
    F f = {(float )(2.0 - 1.1)};
    D d = {(double)(2.0 - 1.1)};
    printf("%d %d %d\n"      , f.bits.sign, f.bits.exponent, f.bits.fraction);
    printf("%lld %lld %lld\n", d.bits.sign, d.bits.exponent, d.bits.fraction);
    return 0;
}

Распечаткаэтот код:

0 126 6710886
0 1022 3602879701896396

Основываясь на двух приведенных выше спецификациях формата, давайте преобразуем эти числа в рациональные значения.

Чтобы добиться высокой точности, давайте сделаем это в простой программе на Python:

from decimal import Decimal
from decimal import getcontext

getcontext().prec = 100

TWO = Decimal(2)

def convert(sign, exponent, fraction, e_len, f_len):
    return (-1) ** sign * TWO ** (exponent - 2 ** (e_len - 1) + 1) * (1 + fraction / TWO ** f_len)

def toFloat(sign, exponent, fraction):
    return convert(sign, exponent, fraction, 8, 23)

def toDouble(sign, exponent, fraction):
    return convert(sign, exponent, fraction, 11, 52)

f = toFloat(0, 126, 6710886)
d = toDouble(0, 1022, 3602879701896396)

print('{:.40f}'.format(f))
print('{:.40f}'.format(d))

Распечатка этого кода:

0.8999999761581420898437500000000000000000
0.8999999999999999111821580299874767661094

Если мы распечатаем эти два значения, указав от 8 до 15 десятичных цифр, мы получим то же самое, что вызаметил (значение double напечатано как 0,9, а значение float напечатано как близко к 0,9):

Другими словами, этот код:

for n in range(8, 15 + 1):
    string = '{:.' + str(n) + 'f}';
    print(string.format(f))
    print(string.format(d))

Дает эту распечатку:

0.89999998
0.90000000
0.899999976
0.900000000
0.8999999762
0.9000000000
0.89999997616
0.90000000000
0.899999976158
0.900000000000
0.8999999761581
0.9000000000000
0.89999997615814
0.90000000000000
0.899999976158142
0.900000000000000

Поэтому мы пришли к выводу, что JПо умолчанию ava печатает десятичные числа с точностью от 8 до 15 цифр.

Хороший вопрос Кстати ...

1 голос
/ 04 ноября 2019

Pop quiz: представляет 1/3 в десятичном формате.

Ответ: Вы не можете;не точно.

Компьютеры считаются в двоичном формате. Есть еще много чисел, которые «не могут быть полностью представлены». Точно так же, как в десятичном вопросе, если у вас есть только маленький листок бумаги, на котором вы можете написать его, вы можете просто пойти с 0.3333333 и назвать его день, и тогда у вас будет число, которое очень близко к,но не совсем то же самое, что и 1 / 3, поэтому компьютеры представляют дроби.

Или, подумайте об этом следующим образом: число с плавающей запятой занимает 32-битное число;double занимает 64. Существует только 2 ^ 32 (около 4 миллиардов) различных чисел, которые может представлять 32-битное значение. И все же, даже между 0 и 1 существует бесконечное количество чисел. Итак, учитывая, что существует не более 2 ^ 32 конкретных, конкретных чисел, которые «точно представлены» в виде числа с плавающей запятой, любое число, не входящее в этот благословенный набор из примерно 4 миллиардов значений, не представимо. Вместо того, чтобы просто делать ошибку, вы просто получаете в этом пуле 4 миллиарда значений, которые являются представимыми, и это самое близкое число к тому, которое вы хотели.

Кроме того, поскольку компьютеры учитываются в двоичном, а не в десятичном видеВаше чувство того, что является «представимым», а что нет, отключено. Вы можете подумать, что 1/3 - это большая проблема, но наверняка 1/10 - это легко, верно? Это просто 0.1, и это точное представление. Ах, но десятая работает хорошо в десятичном формате. В конце концов, десятичная дробь основана на числе 10, что неудивительно. А в двоичном? половина, четвертый, восьмой, шестнадцатый: легко в двоичном. Десятый? Это так же сложно, как и третье: НЕ ПРЕДСТАВЛЯЕМЫЙ .

0,9 само по себе не является представимым числом. И все же, когда вы печатали свой поплавок, это то, что вы получили.

Причина в том, что печать поплавков / двойников - это искусство, а не наука. Учитывая, что только несколько чисел представимы, и учитывая, что эти числа не кажутся людям «естественными» из-за двоичной и десятичной дроби, вам действительно нужно добавить к числу стратегию «округления», иначе она будет выглядетьсумасшедший (никто не хочет читать 0,899999999999999999765). И это именно то, что делают System.out.println и co.

Но вы действительно должны взять под контроль функцию округления: никогда не используйте System.out.println для печати двойных и плавающих чисел. Вместо этого используйте System.out.printf("%.6f", yourDouble);, и в этом случае ОБА будет печатать 0.9. Поскольку в действительности ни один из них не может точно представлять 0,9, число, которое ближе всего к нему в числах с плавающей запятой (или, скорее, число, которое вы получаете, когда берете число, ближайшее к 2,0 (которое составляет 2,0), и число, ближайшее к 1,1 (что1.1), вычтите их, а затем найдите число, наиболее близкое к этому результату) - печатается как 0,9, хотя это не для чисел с плавающей запятой, и не печатает как 0,9 в двойном размере.

...