Точность с плавающей запятой

Возможный дубликат:
Сравнение с плавающей точкой

У меня проблема с точностью поплавка в 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. Почему?

2

Решение

Вы можете представлять числа только в 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...,

11

Другие решения

Вы должны прочитать больше о как работают числа с плавающей точкой, особенно часть на представимые числа.

Вы не даете много объяснений, почему вы думаете, что «32 бита должно быть достаточно для 101,1», так что это сложно опровергнуть.

Двоичные числа с плавающей точкой не работают хорошо для всех десятичных чисел, так как они в основном хранят число в, ждите его, основание 2. Как в двоичном.

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

4

Твой номер 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,

4

Здесь вы видите сочетание двух факторов:

  • IEEE754 представление с плавающей запятой не способно точно представлять целый класс рациональных и всех иррациональных чисел
  • Эффекты округления (по умолчанию здесь до 6 знаков после запятой) в printf, То есть ошибка при использовании double происходит где-то справа от 6-го DP.
2

Если у вас было больше цифр на печать double вы увидите, что даже double не может быть представлено точно:

 printf ("b: %.16f\n", b);

b: 101.0999999999999943

Дело в том float а также double используют двоичный формат, и не все числа с плавающим указателем могут быть представлены точно в двоичном формате.

2

К сожалению, большинство десятичных чисел с плавающей запятой не могут быть точно представлены в (машинной) с плавающей запятой. Это просто, как все работает.

Например, число 101.1 в двоичном виде будет представлено как 1100101.0(0011) ( 0011 часть будет повторяться вечно), поэтому независимо от того, сколько байт вы должны хранить, она никогда не станет точной. Вот небольшая статья о двоичном представлении с плавающей точкой, и Вот Вы можете найти некоторые примеры преобразования чисел с плавающей запятой в двоичные.

Если вы хотите узнать больше на эту тему, я мог бы рекомендовать вам Эта статья, хотя это долго и не слишком легко читать.

1
По вопросам рекламы [email protected]