Почему арифметика с плавающей запятой в 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.)

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

  • Как временные среды Java, нацеленные на процессоры pre-SSE2, реализуют базовые операции с плавающей запятой?
  • Преобразование String для плавания в Apple Swift
  • Составляют ли какие-либо JIT-компиляторы JVM код, который использует векторизованные инструкции с плавающей запятой?
  • Приведение float в int (побитовое) в C
  • Почему значение с плавающей запятой, такое как 3.14, по умолчанию считается в MSVC?
  • Каков наиболее эффективный способ для плавающего и двойного сравнения?
  • Как обнаружить переполнение и переполнение двойной точки с двойной точностью?
  • Могут ли какие-либо процессоры реального мира не использовать IEEE 754?
  • Почему моя целая математика с std :: pow дает неправильный ответ?
  • Как изменить поплавок на наименьший приращение (или близко к нему)?
  • Сделать поплавок показывать только два десятичных знака
  • Interesting Posts

    Возможно ли как-то скрыть Growl и Herald, когда VLC работает в полноэкранном режиме?

    Dell Inspiron 6400 указывает, что неправильный адаптер питания подключен и не заряжает аккумулятор

    В противном случае на StateProvider

    Как распознать салфетки во всех четырех направлениях?

    Как реализовать stream активности в социальной сети

    Почему мой экран черный после подключения rdp?

    Android: пользовательский разделитель (или даже элемент) в ListView, зависящий от содержимого элемента

    Использование map / reduce для отображения свойств в коллекции

    Является ли HashMap streamобезопасным для разных ключей?

    LESS: непризнанная ошибка ввода при использовании Bootstrap

    Как работает GET / tags Instagram / / media / Recent Pagination?

    Потенциальное загрязнение кучи по параметру varargs

    Нечувствительная к регистру строка в виде ключа HashMap

    Что означает свойство страницы AutoEventWireUp?

    AngularJS: Предотrotation ошибки $ digest уже выполняется при вызове $ scope. $ Apply ()

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