Почему арифметика с плавающей запятой в C # неточна?

Почему следующая программа печатает то, что она печатает?

class Program { static void Main(string[] args) { float f1 = 0.09f*100f; float f2 = 0.09f*99.999999f; Console.WriteLine(f1 > f2); } } 

Выход

 false 

Плавающая точка имеет только столько цифр точности. Если вы видите f1 == f2, это связано с тем, что любая разница требует большей точности, чем 32-битный float.

Я рекомендую прочитать, что каждый компьютерный ученый должен прочитать о плавающей точке

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

Вы также можете немного повеселиться с относительно простыми цифрами, если учесть, что это даже не базовая десятка. 0,1, например, является повторяющимся десятичным числом, представленным в двоичном формате.

В этом конкретном случае это потому, что .09 и .999999 не могут быть представлены с точной точностью в двоичном формате (аналогично, 1/3 не может быть представлена ​​с точной точностью в десятичной форме). Например, 0.11111111111111111111101111 base 2 является 0.999998986721038818359375 базой 10. Добавление 1 к предыдущему двоичному значению, 0.11111111111111111111 base 2 – 0.99999904632568359375 base 10. Для точно 0,999999 нет двоичного значения. Точность плавающей точки также ограничена пространством, выделенным для хранения экспоненты и дробной части мантиссы. Кроме того, как и целые типы, плавающая точка может переполнять свой диапазон, хотя его диапазон больше целых.

Запустив этот бит кода C ++ в отладчике Xcode,

float myFloat = 0,1;

показывает, что myFloat получает значение 0.100000001. Он отключен на 0.000000001. Не много, но если вычисление имеет несколько арифметических операций, неточность может быть усугублена.

imho очень хорошее объяснение с плавающей точкой в ​​главе 14 « Введение в компьютерную организацию» на языке сборки x86-64 и GNU / Linux Боба Планца из Калифорнийского государственного университета в Сономе (на пенсии) http://bob.cs.sonoma.edu /getting_book.html . В основе этой главы лежит следующее.

Плавающая точка похожа на научную нотацию, где значение хранится как смешанное число, большее или равное 1,0 и менее 2,0 (мантисса), разное число до некоторой степени (экспонента). Плавающая точка использует базу 2, а не базу 10, но в простой модели Plantz дает, он использует базу 10 для ясности. Представьте себе систему, в которой для мантиссы используются два положения хранения, одна позиция используется для знака показателя * (0, представляющего + и 1, представляющего -), а для экспонента используется одна позиция. Теперь добавьте 0.93 и 0.91. Ответ 1,8, а не 1,84.

9311 представляет собой 0,93 или 9,3 раза 10 до -1.

9111 представляет собой 0,91 или 9,1 раза 10 до -1.

Точный ответ составляет 1,84, или 1,84 раза 10 до 0, что было бы 18400, если бы у нас было 5 позиций, но, имея только четыре позиции, ответ 1800, или 1,8 раза 10 до нуля, или 1,8. Конечно, типы данных с плавающей запятой могут использовать более четырех позиций хранения, но количество позиций по-прежнему ограничено.

Точность ограничена пространством, но «точное представление дробных значений в двоичном выражении ограничено суммами обратных степеней двух» (Plantz, op. Cit.).

0,11100110 (двоичный) = 0,89843750 (десятичный)

0,11100111 (двоичный) = 0,90234375 (десятичный)

Точного представления десятичного десятичного числа в двоичном выражении нет. Даже перенос фракции из большего количества мест не работает, так как вы повторяете 1100 навсегда справа.

Начальные программисты часто видят арифметику с плавающей запятой как более точную, чем целую. Верно, что даже добавление двух очень больших целых чисел может привести к переполнению. Умножение делает еще более вероятным, что результат будет очень большим и, следовательно, переполнением. И когда используется с двумя целыми числами, оператор / в C / C ++ приводит к потере дробной части. Однако … представления с плавающей точкой имеют свой собственный набор неточностей. (Plantz, op. Cit.)

* В плавающей точке представлены как знак числа, так и знак экспоненты.

  • Почему значение с плавающей запятой, такое как 3.14, по умолчанию считается в MSVC?
  • Как компьютер выполняет арифметику с плавающей запятой?
  • Могут ли какие-либо процессоры реального мира не использовать IEEE 754?
  • Деление с плавающей запятой против умножения с плавающей запятой
  • Почему мы не можем использовать '==' для сравнения двух чисел с плавающей запятой или двойных чисел
  • Ассемблер x86: сравнение с плавающей запятой
  • Приведение float в int (побитовое) в C
  • Преобразовать float в строку с заданной точностью и количеством десятичных цифр?
  • Как обнаружить переполнение и переполнение двойной точки с двойной точностью?
  • Типы с плавающей запятой с фиксированным размером
  • поплавковые биты и строгий псевдоним
  • Interesting Posts

    Извлечь / сохранить вложение электронной почты с помощью bash

    Как настроить отношение многих ко многим, используя API-интерфейс сущностей

    Динамическое обновление содержимого TabControl во время выполнения

    Вызов функции PyGame, нажатие кнопки PyGame с ориентацией на объект

    Динамическое добавление содержимого в линейную компоновку?

    Что такое банковский конфликт? (Выполнение программирования Cuda / OpenCL)

    Как избавиться от `устаревшего преобразования из строковой константы в ‘char *’ ‘предупреждения в GCC?

    Сохранить значение в переменной после HTTPREAD

    Перемещение больших файлов между компьютерами Windows

    Синглтон: как его использовать

    Включите учетную запись omniauth facebook во всплывающем окне

    «Не объявлена ​​в этой области» ошибка с шаблонами и наследованием

    Как напечатать строку с нулевым завершением с помощью printf?

    Java BigDecimal тригонометрические методы

    Фильтр ListView с массивом

    Давайте будем гением компьютера.