Разница между double и float в точности с плавающей точкой - PullRequest
3 голосов
/ 25 июня 2019

Прочитав этот вопрос и этот msdn блог , я попробовал несколько примеров, чтобы проверить это:

Console.WriteLine(0.8-0.7 == 0.1);

И да, ожидаемый результат - False. Поэтому я пытаюсь привести выражение в обе стороны к double и float, чтобы посмотреть, смогу ли я получить другой результат:

Console.WriteLine((float)(0.8-0.7) == (float)(0.1));
Console.WriteLine((double)(0.8-0.7) == (double)(0.1));

Вывод первой строки True, а вывод второй строки False, почему это происходит?

Кроме того,

Console.WriteLine(8-0.7 == 7.3);
Console.WriteLine(8.0-0.7 == 7.3);

Обе строки выше дают True даже без приведения. И ...

Console.WriteLine(18.01-0.7 == 17.31);

Эта строка выводит False. Как вычитание 8 отличается от вычитания 18.01, если они оба вычитаются числом с плавающей запятой?

Я пытался прочитать блог и задать вопрос, я не могу найти ответ где-нибудь еще. Может кто-нибудь объяснить мне, почему все это происходит на языке Леймана? Заранее спасибо.

EDIT:

Console.WriteLine(8.001-0.001 == 8); //this return false
Console.WriteLine(8.01-0.01 == 8); //this return true

Примечание: я использую .NET fiddle онлайн c # компилятор.

1 Ответ

5 голосов
/ 25 июня 2019

Случаи 0,8-0,7

В 0.8-0.7 == 0.1 ни один из литералов не может быть точно представлен в double.Ближайшие представимые значения 0,8000000000000000444089209850062616169452667236328125 для .8, 0,6999999999999999555910790149937383830547332763671875 для .7 и 0,10000000000000000555111512312578270201561540451.Когда вычтены первые два, результат равен 0,100000000000000088817841970012523233890533447265625.Поскольку это не равно третьему, 0.8-0.7 == 0.1 оценивается как ложное.

В (float)(0.8-0.7) == (float)(0.1) результат 0.8-0.7 и 0.1 каждый преобразуется в float.Ближайшее к первому значение float, 0,1000000000000000055511151231257827021181583404541015625, равно 0,100000001490116119384765625.Ближайшее к последнему значение float, 0,100000000000000088817841970012523233890533447265625, равно 0,100000001490116119384765625.Поскольку они одинаковы, (float)(0.8-0.7) == (float)(0.1) оценивается как true.

В (double)(0.8-0.7) == (double)(0.1) результат 0.8-0.7 и 0.1 каждый преобразуется в double.Поскольку они уже double, эффект отсутствует, и результат такой же, как для 0.8-0.7 == 0.1.

Примечания

Спецификация C #, версия 5.0 указывает, что float и double являются 32-разрядными и 64-разрядными типами IEEE-754 с плавающей точкой.Я не вижу явного утверждения, что они являются двоичными форматами с плавающей запятой, а не десятичными, но описанные характеристики делают это очевидным.В спецификации также указывается, что, как правило, используется арифметика IEEE-754 с округлением до ближайшего (предположительно, округлением до ближайшего связывания с четностью), за исключением нижеприведенного.

Спецификация C # позволяетАрифметика с плавающей точкой должна выполняться с большей точностью, чем номинальный тип.В п. 4.1.6 говорится: «… операции с плавающей точкой могут выполняться с большей точностью, чем тип результата операции…». Это может усложнить анализ выражений с плавающей точкой в ​​целом, но это не касается нас в случае 0.8-0.7 == 0.1 поскольку единственной применимой операцией является вычитание 0.7 из 0.8, и эти числа находятся в одном и том же бинарном выражении (имеют одинаковую степень двойки в представлении с плавающей запятой), поэтому результат вычитания является точно представимыми дополнительная точность не изменит результат.Пока преобразование исходных текстов 0.8, 0.7 и 0.1 в double не использует дополнительную точность, а приведение к float дает float без дополнительной точности, результаты будутбыть как указано выше.(Стандарт C # говорит в п. 6.2.1, что преобразование из double в float дает значение float, хотя в нем прямо не говорится, что в этой точке нельзя использовать дополнительную точность.)

Дополнительные кейсы

В 8-0.7 == 7.3 у нас есть 8 для 8, 7.29999999999999982236431605997495353221893310546875 для 7.3, 0,6999999999999999555910790149937383830547332763671875 для 0.7, и 7,299999999999999822364316059974954 560 5997495 545 529, 5275

Обратите внимание, что дополнительная точность, допускаемая спецификацией C #, может повлиять на результат 8-0.7.Реализация AC #, которая использовала дополнительную точность для этой операции, могла бы привести к ложному результату для этого случая, поскольку она получала бы другой результат для 8-0.7.

В 18.01-0.7 == 17.31, у нас есть 18.010000000000001563194018672220408916473388671875 для 18.01, 0.6999999999999999555910790149937383830547332763671875 для 0.7, 17.309999999999998721023075631819665431976318359375 для 17.31 и 17.31000000000000227373675443232059478759765625 для 18.01-0.7, поэтому результат равен false.

Как вычитать 8 отличий от вычитания 18,01, если они обавычитается числом с плавающей запятой?

18,01 больше 8 и требует большей степени двойки в его представлении с плавающей точкой.Аналогично, результат 18.01-0.7 больше, чем 8-0.7.Это означает, что биты в их значениях (часть дроби представления с плавающей запятой, которая масштабируется степенью двойки) представляют большие значения, в результате чего ошибки округления в операциях с плавающей запятой, как правило, больше.В общем, формат с плавающей запятой имеет фиксированный диапазон - существует фиксированное расстояние от сохраненного старшего бита до сохраненного младшего бита.Когда вы меняете число с большим количеством битов слева (старшие биты), некоторые биты справа (младшие биты) выталкиваются, и результаты меняются.

...