#c
#c
Вопрос:
У меня есть плавающее значение, равное 0.1, вводимое из пользовательского интерфейса.
Но при преобразовании этой строки в float я получаю значение 0.10 … 01. Проблема заключается в добавлении ненулевой цифры. Как мне решить эту проблему.
Спасибо,
iSight
Ответ №1:
Вам нужно выполнить некоторое ознакомительное чтение с представлениями с плавающей запятой: http://docs.sun.com/source/806-3568/ncg_goldberg.html.
Учитывая, что компьютеры являются переключателями включения-выключения, они хранят округленный ответ, и они работают по второму основанию, а не по десятому основанию, которое нам, людям,, кажется, нравится.
Ваши варианты заключаются в том, чтобы:
- отобразите его обратно с меньшим количеством цифр, чтобы округлить обратно до базового значения 10 (извлеките
<iomanip>
заголовок стандартной библиотеки и установите точность) - сохраните число в каком-нибудь реальном объекте с поддержкой десятичной системы счисления — вы найдете множество классов C для этого в Google, но ни один из них не предусмотрен ни в стандарте, ни в boost, который я просматривал в последний раз
- преобразуйте входные данные из строки непосредственно в целое число, состоящее из некоторой меньшей единицы (например, тысячных долей), избегая округления.
Ответ №2:
0,1 (десятичный) = 0,00011001100110011 … (двоичный)
Итак, в общем случае число, которое вы можете представить конечным числом десятичных разрядов, может быть невозможно представить конечным числом битов. Но числа с плавающей запятой хранят только самые N значащих битов. Итак, преобразования между десятичной строкой и «двоичным» значением с плавающей запятой обычно включают округление.
Однако преобразование десятичной строки в обратном направлении без потерь -> двойная -> десятичная строка возможно, если вы ограничиваетесь десятичными строками, содержащими не более 15 значащих цифр (при условии, что IEEE 754 содержит 64-разрядные числа с плавающей запятой). Это включает в себя последнее преобразование. Вам нужно создать строку из double, в которой не более 15 значащих цифр.
Также возможно выполнить обратный переход double -> string -> double без потерь. Но здесь вам могут понадобиться десятичные строки с 17 десятичными цифрами, чтобы заставить его работать (опять же, при условии, что IEEE-754 использует 64-битные числа с плавающей запятой).
Ответ №3:
Лучший сайт, который я когда-либо видел, который объясняет, почему некоторые числа не могут быть представлены точно, — это сайт-конвертер IEEE754 Харальда Шмидта.
Это онлайн-инструмент для отображения представлений значений одинарной точности IEEE754, и мне это так понравилось, что я написал свое собственное Java-приложение для этого (а также с двойной точностью).
В итоге у вас может быть всего около четырех миллиардов различных 32-разрядных значений, но существует бесконечное количество реальных значений между любыми двумя разными значениями. Итак, у вас проблема с точностью. К этому вам придется привыкнуть.
Если вам нужна большая точность и / или лучший тип для десятичных значений, вы можете либо:
- переключитесь на большее количество битов.
- используйте десятичный тип
- используйте библиотеку с большими числами, такую как GMP (хотя я отказываюсь использовать это в производственном коде, поскольку обнаружил, что она не справляется с нехваткой памяти элегантно).
В качестве альтернативы вы можете использовать неточные значения (частота ошибок в них очень низкая, что-то вроде одной части на сто миллионов для чисел с плавающей запятой, из памяти) и просто распечатать их с меньшей точностью. Распечатка 0.10000000145
с точностью до двух знаков после запятой поможет вам 0.10
.
Вам пришлось бы выполнять миллионы и миллионы дополнений, чтобы ошибка заметно накапливалась. Конечно, меньше других операций, но все равно много.
Что касается того, почему вы получаете это значение, 0.1
оно хранится в формате одинарной точности IEEE754 следующим образом (знак, экспонента и мантисса):
s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm 1/n
0 01111011 10011001100110011001101
|||||||||||||||||||||| - 8388608
||||||||||||||||||||| -- 4194304
|||||||||||||||||||| --- 2097152
||||||||||||||||||| ---- 1048576
|||||||||||||||||| ----- 524288
||||||||||||||||| ------ 262144
|||||||||||||||| ------- 131072
||||||||||||||| -------- 65536
|||||||||||||| --------- 32768
||||||||||||| ---------- 16384
|||||||||||| ----------- 8192
||||||||||| ------------ 4096
|||||||||| ------------- 2048
||||||||| -------------- 1024
|||||||| --------------- 512
||||||| ---------------- 256
|||||| ----------------- 128
||||| ------------------ 64
|||| ------------------- 32
||| -------------------- 16
|| --------------------- 8
| ---------------------- 4
----------------------- 2
Знак положительный, это довольно просто.
Показатель степени равен 64 32 16 8 2 1 = 123 - 127 bias = -4
, поэтому множитель равен 2-4 или 1/16
.
Мантисса слишком объемная. Он состоит из 1
(неявной базы) плюс (для всех этих битов, каждый из которых равен 1 / (2n), поскольку n
начинается с 1
и увеличивается вправо), {1/2, 1/16, 1/32, 1/256, 1/512, 1/4096, 1/8192, 1/65536, 1/131072, 1/1048576, 1/2097152, 1/8388608}
.
Когда вы складываете все это, вы получаете 1.60000002384185791015625
.
Когда вы умножаете это на множитель, вы получаете 0.100000001490116119384765625
, совпадающее со значением двойной точности на сайте Харальда, насколько оно напечатано:
0.10000000149011612 (out by 0.00000000149011612)
И когда вы отключаете наименее значимый (самый правый) бит, который является наименьшим движением вниз, которое вы можете совершить, вы получаете:
0.09999999403953552 (out by 0.00000000596046448)
Объединение этих двух:
0.10000000149011612 (out by 0.00000000149011612)
|
0.09999999403953552 (out by 0.00000000596046448)
вы можете видеть, что первый вариант соответствует примерно в четыре раза (14.9: 59.6). Итак, это самое близкое значение, которое вы можете получить 0.1
.
Комментарии:
1. Однажды я отлаживал встроенную систему, которая работала непрерывно, считывая показания датчика и интегрируя результаты измерений, чтобы накапливать общее количество примерно раз в секунду. Первоначальный автор (не я) сохранил накопитель с одинарной точностью
float
. Когда процесс выполняется непрерывно в течение месяцев или лет, неудивительно, что он достигает точки, когда накопитель был достаточно большим по величине, чем типичное инкрементное изменение, чтобы заставить его больше не отслеживать истинное значение.
Ответ №4:
Поскольку числа с плавающей запятой хранятся в двоичном формате, дробная часть фактически находится в базе два … и одна десятая является повторяющимся десятичным числом в базе два, таким же, как одна девятая в базе десять.
Наиболее распространенные способы решения этой проблемы — хранить ваши значения в виде целых чисел соответствующего масштаба, как в типах валюты C # или SQL, или округлять числа с плавающей запятой при их отображении.