Почему арифметика с плавающей запятой в C # неточна?
Почему следующая программа печатает то, что она печатает?
class Program { static void Main(string[] args) { float f1 = 0.09f*100f; float f2 = 0.09f*99.999999f; Console.WriteLine(f1 > f2); } }
Выход
false
- Алгоритм преобразования двоичного кода IEEE 754 в строку?
- Двойной против BigDecimal?
- Сравнение числа с плавающей точкой с нолем
- Построение 32-битного поплавка из 4 составных байтов
- Библиотека высокой точности с плавающей запятой Java
- Когда следует использовать ключевое слово "strictfp" в java?
- Как мне сделать сравнение с плавающей запятой?
- Java: Почему мы должны использовать BigDecimal вместо Double в реальном мире?
- Как может примитивное значение float быть -0.0? Что это значит?
- Сравнение поплавковых и двойных типов данных в объекте C
- double или float, что быстрее?
- Разница между десятичной, плавающей и двойной в .NET?
- проблемы в сравнении с плавающей запятой
Плавающая точка имеет только столько цифр точности. Если вы видите 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.)
* В плавающей точке представлены как знак числа, так и знак экспоненты.