math.h ceil не работает как положено в C - PullRequest
6 голосов
/ 22 февраля 2010

Как получается, что ceil () округляет ровное число без дробных частей?
Когда я пытаюсь сделать это:

double x = 2.22;  
x *= 100; //which becomes 222.00...  
printf("%lf", ceil(x)); //prints 223.00... (?)  

Но когда я изменяю значение от 2,22 до 2,21

x *= 100; //which becomes 221.00...  
printf("%lf", ceil(x)); //prints 221.00... as expected  

Я попытался сделать это другим способом, используя modf (), и столкнулся с другой странной вещью:

double x = 2.22 * 100;  
double num, fraction;  
fraction = modf(x, &num);  
if(fraction > 0)  
    num += 1; //goes inside here even when fraction is 0.00...  

Так что же получается, 0,000 ... больше 0?
Кто-нибудь может объяснить, почему происходят обе эти ситуации? Также я собираю с использованием CC версии 4.1.2 в RedHat.

Ответы [ 6 ]

12 голосов
/ 22 февраля 2010

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

Вам следует прочитать доклад Голдберга под названием Что должен знать каждый учёный-компьютерщик об арифметике с плавающей точкой .

12 голосов
/ 22 февраля 2010

Основной ответ таков: число с плавающей запятой, которое вы получите:

double x = 2.22;

на самом деле немного больше, чем значение 2.22, и значение, которое вы получите с

double x = 2.21;

чуть меньше 2.21.

7 голосов
/ 22 февраля 2010

Не все значения с плавающей точкой могут быть правильно представлены типами float и double C - скорее всего, вы думаете, что 222 на самом деле что-то вроде 222.00000000000000001.

Существует стандартный, но малоизвестный способ обойти это - используйте функцию nextafter() C99:

printf("%lf", ceil(nextafter(x, 0)));

Подробнее см. man nextafter.

2 голосов
/ 22 февраля 2010

Это потому, что 2,22 не совсем 2,22 и, следовательно, 2,22 * 100 не 222, а 222,000000000x

double x = 2.22;  
printf("%.20lf\n", x); //prints 2.22000000000000019540
x *= 100; 
printf("%.20lf\n", x); //prints 222.00000000000002842171

Если вам нужна целочисленная точность (например, для вычисления денег), используйте интегральную арифметику (то есть вычисляйте центы вместо долларов).

1 голос
/ 22 февраля 2010

Если бы вы написали:

const int x = 1.2;

в программе на C, что произойдет? Литерал 1.2 не может быть представлен как целочисленное значение, поэтому компилятор будет преобразовывать в соответствии с обычными правилами в целое число, а x будет присвоено значение 1.

То же самое происходит и здесь.

Вы писали:

double x = 2.22;

2.22 не представляется в виде числа с двойной точностью, поэтому компилятор преобразует его в соответствии с правилами стандарта Си. Вы получаете ближайшее представимое число двойной точности, а именно:

2.220000000000000195399252334027551114559173583984375

Когда это значение умножается на 100 в double, результат будет:

222.000000000000028421709430404007434844970703125

и когда вы вызываете ceil( ) с этим значением в качестве аргумента, математическая библиотека корректно возвращает 223.0.

0 голосов
/ 22 февраля 2010

Вы можете рассмотреть возможность использования десятичных типов с плавающей запятой , чтобы получить результаты, которые вы ожидаете от десятичной арифметики. Аппаратное обеспечение на типичных процессорах для настольных ПК поддерживает только двоичные числа с плавающей запятой, поэтому нельзя ожидать, что они будут давать те же результаты, что и десятичное вычисление. Конечно, если не поддерживается аппаратно, десятичная с плавающей точкой медленнее.

Обратите внимание, что десятичная с плавающей запятой не обязательно является более точной (например, 1/3 не может быть точно представлена, как есть значения в двоичной FP, которые не могут быть представлены), она просто выдаст ожидаемый результат, как если бы вы выполнили расчет длинной рукой в ​​десятичном формате.

...