Возможный дубликат:
Сравнение с плавающей точкой
У меня проблема с точностью поплавка в C / C ++. Когда я выполняю программу ниже:
#include <stdio.h>
int main (void) {
float a = 101.1;
double b = 101.1;
printf ("a: %f\n", a);
printf ("b: %lf\n", b);
return 0;
}
Результат:
a: 101.099998
b: 101.100000
Я считаю, что float должен иметь 32-битный код, поэтому его должно быть достаточно для хранения 101.1. Почему?
Вы можете представлять числа только в IEEE754 (по крайней мере для двоичных форматов одинарной и двойной точности), если они могут быть построены из сложения инвертированных степеней двух (т.е. 2-n
лайк 1
, 1/2
, 1/4
, 1/65536
и т. д.) в зависимости от количества битов, доступных для точности.
Не существует комбинации инвертированных степеней двух, которые позволят вам получить точное значение 101,1 в пределах масштабирования, обеспечиваемого числами с плавающей запятой (23 бита точности) или же удваивается (52 бита точности).
Если вы хотите получить краткий учебник о том, как работает этот перевернутый источник энергии, см. этот ответ.
Применяя знания из этого ответа к вашему 101.1
число (как поплавок одинарной точности):
s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm 1/n
0 10000101 10010100011001100110011
| | | || || || |+- 8388608
| | | || || || +-- 4194304
| | | || || |+----- 524288
| | | || || +------ 262144
| | | || |+--------- 32768
| | | || +---------- 16384
| | | |+------------- 2048
| | | +-------------- 1024
| | +------------------ 64
| +-------------------- 16
+----------------------- 2
Мантисса часть этого на самом деле продолжается вечно 101.1
:
mmmmmmmmm mmmm mmmm mmmm mm
100101000 1100 1100 1100 11|00 1100 (and so on).
следовательно, это не вопрос точности, никакое количество конечных битов не будет представлять это число точно в формате IEEE754.
Использование битов для вычисления фактический число (ближайшее приближение), знак положительный. Показатель степени равен 128 + 4 + 1 = 133 — 127 смещения = 6, поэтому множитель равен 26 или 64
Мантисса состоит из 1 (неявного основания) плюс (для всех этих битов, каждый из которых стоит 1 / (2)N), поскольку n начинается с 1 и увеличивается вправо), {1/2, 1/16, 1/64, 1/1024, 1/2048, 1/16384, 1/32768, 1/262144, 1/524288, 1/4194304, 1/8388608}
,
Когда вы добавляете все это, вы получаете 1.57968747615814208984375
,
Когда вы умножаете это на ранее рассчитанный множитель, 64
, ты получаешь 101.09999847412109375
,
Все числа были рассчитаны с bc
используя шкалу из 100 десятичных цифр, что приводит к большому количеству конечных нулей, поэтому числа должен быть очень точным Вдвойне, так как я проверил результат с:
#include <stdio.h>
int main (void) {
float f = 101.1f;
printf ("%.50f\n", f);
return 0;
}
который также дал мне 101.09999847412109375000...
,
Вы должны прочитать больше о как работают числа с плавающей точкой, особенно часть на представимые числа.
Вы не даете много объяснений, почему вы думаете, что «32 бита должно быть достаточно для 101,1», так что это сложно опровергнуть.
Двоичные числа с плавающей точкой не работают хорошо для всех десятичных чисел, так как они в основном хранят число в, ждите его, основание 2. Как в двоичном.
Это общеизвестный факт, и именно поэтому, например, деньги никогда не должны обрабатываться с плавающей точкой.
Твой номер 101.1
в базе 10
является 1100101.0(0011)
в базе 2
, 0011
часть повторяется. Таким образом, независимо от того, сколько цифр у вас будет, номер не может быть точно представлен на компьютере.
Глядя на IEE754 стандарт для чисел с плавающей запятой, вы можете узнать, почему double
Версия, казалось, показала это полностью.
PS: вывод 101.1
в базе 10
является 1100101.0(0011)
в базе 2
:
101 = 64 + 32 + 4 + 1
101 -> 1100101
.1 * 2 = .2 -> 0
.2 * 2 = .4 -> 0
.4 * 2 = .8 -> 0
.8 * 2 = 1.6 -> 1
.6 * 2 = 1.2 -> 1
.2 * 2 = .4 -> 0
.4 * 2 = .8 -> 0
.8 * 2 = 1.6 -> 1
.6 * 2 = 1.2 -> 1
.2 * 2 = .4 -> 0
.4 * 2 = .8 -> 0
.8 * 2 = 1.6 -> 1
.6 * 2 = 1.2 -> 1
.2 * 2 = .4 -> 0
.4 * 2 = .8 -> 0
.8 * 2 = 1.6 -> 1
.6 * 2 = 1.2 -> 1
.2 * 2 = .4 -> 0
.4 * 2 = .8 -> 0
.8 * 2 = 1.6 -> 1
.6 * 2 = 1.2 -> 1
.2 * 2....
PPS: То же самое, если бы вы хотели сохранить именно результат 1/3
в базе 10
,
Здесь вы видите сочетание двух факторов:
printf
, То есть ошибка при использовании double
происходит где-то справа от 6-го DP.Если у вас было больше цифр на печать double
вы увидите, что даже double
не может быть представлено точно:
printf ("b: %.16f\n", b);
b: 101.0999999999999943
Дело в том float
а также double
используют двоичный формат, и не все числа с плавающим указателем могут быть представлены точно в двоичном формате.
К сожалению, большинство десятичных чисел с плавающей запятой не могут быть точно представлены в (машинной) с плавающей запятой. Это просто, как все работает.
Например, число 101.1 в двоичном виде будет представлено как 1100101.0(0011)
( 0011
часть будет повторяться вечно), поэтому независимо от того, сколько байт вы должны хранить, она никогда не станет точной. Вот небольшая статья о двоичном представлении с плавающей точкой, и Вот Вы можете найти некоторые примеры преобразования чисел с плавающей запятой в двоичные.
Если вы хотите узнать больше на эту тему, я мог бы рекомендовать вам Эта статья, хотя это долго и не слишком легко читать.