Двойки с округлением - 0,5 - спринтф

Я использую следующий код для округления до 2dp:

sprintf(temp,"%.2f",coef[i]); //coef[i] returns a double

Он успешно округляет 6,666 до 6,67, но неправильно работает при округлении 5,555. Он возвращает 5,55, тогда как должен (по крайней мере, на мой взгляд) возвращать 5,56.

Как я могу округлить его, когда следующая цифра равна 5? то есть возврат 5.56.

редактировать: теперь я понимаю, что это происходит, потому что, когда я ввожу 5.555 с cin, он сохраняется как 5.554999997.

Я собираюсь попробовать округлить в два этапа - сначала до 3dp, а затем до 2dp. любые другие (более элегантные) идеи?


person Meir    schedule 15.06.2009    source источник


Ответы (5)


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

printf("%.2f %.2f\n", 5.555, round(5.555 * 100.)/100.);

Это дает следующий вывод на моей машине:

5.55 5.56
person ypnos    schedule 15.06.2009
comment
Это кажется немного более надежным, чем идея добавления 0,0005, но я думаю, что оба варианта будут работать. Спасибо за вашу помощь! - person Meir; 15.06.2009
comment
Это не глючит, @ypnos, вы просто еще не разобрались с ограничениями с плавающей запятой. 5,5551 представляется как 5,555100000000000370... с округлением в большую сторону, 5,555 представляется как 5,5549999999... с округлением в меньшую сторону. Вы можете увидеть, что это проблема представления, попробовав свой код с 5.55000000000000000001 - в этом случае оба вывода являются неправильными 5.55, потому что число НЕ 5.55000... 1, когда оно сохранено. Это 5,549999... - person paxdiablo; 15.06.2009
comment
Я довольно хорошо разбираюсь в ограничениях с плавающей запятой. Ошибка, которую я вижу здесь, это несоответствие, хотя вы можете возразить, что это ожидаемо (из-за ограничений формата). И спасибо, что понизили ПРАВИЛЬНЫЙ ответ! - person ypnos; 15.06.2009
comment
Я не минусовал вам @ypnos, это не мой стиль - делать это с конкурирующим ответом - я предпочел бы, чтобы решение приняло более широкое сообщество. В любом случае, в вашем ответе нет ничего плохого, просто в комментарии, который вы сделали позже, - это не ошибка. Ограничения IEE754 хорошо понятны и задокументированы (всеми, кроме толпы людей, которые, кажется, все еще задают вопросы об этом на SO :-). Это ошибка только в том случае, если фактическое поведение не соответствует задокументированному поведению; это НЕ так здесь. - person paxdiablo; 15.06.2009
comment
На самом деле, вот голос за то, что вы так любезны (и, поскольку я не могу придраться к вашему методу, с целыми числами гораздо легче играть, чем с плавающей запятой). - person paxdiablo; 16.06.2009

Число 5.555 не может быть представлено как точное число в IEEE754. Распечатка константы 5.555 с "%.50f" приводит к:

5.55499999999999971578290569595992565155029300000000

поэтому оно будет округлено в меньшую сторону. Попробуйте использовать это вместо этого:

printf ("%.2f\n",x+0.0005);

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

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

person paxdiablo    schedule 15.06.2009
comment
Это не влияет на ваш ответ, но мне просто любопытно: какой язык вы использовали для печати 5.55499999999999971578290569595992565155029300000000? На самом деле должно быть 5.55499999999999971578290569595992565155029296875000 - person Rick Regan; 18.07.2010
comment
@Rick, скорее всего, gcc под CygWin, но трудно вспомнить, что было год назад :-) Я получаю тот же результат, что и вы, под Ubuntu10. Но я думаю, что двойники в любом случае гарантируют точность только 15 десятичных цифр, поэтому он может использовать более широкий внутренний формат (у меня есть смутное припоминание, что вычисления по крайней мере в одной системе использовали 80 бит внутри, но это было бы давно - чтобы получить 48 десятичных цифр, как с вашим числом, вам потребуется около 160 бит точности). - person paxdiablo; 18.07.2010
comment
Хорошо спасибо. Я знал, что это не может быть Visual C++ (он не может точно напечатать столько цифр), и мне показалось, что в нем слишком мало цифр для gcc/glibc в Linux (что позволяет вам печатать их все). В любом случае, вы можете получить такое количество цифр со стандартным двойным значением (53 бита) - это точное десятичное представление того, что содержит двойное число (даже если это может быть только 15-значное приближение того, что ему было присвоено). - person Rick Regan; 18.07.2010

Как насчет другого возможного решения:

printf("%.2f", _nextafter(n, n*2));

Идея состоит в том, чтобы увеличить число от нуля (n*2 соответствует правильному знаку) на наименьшую возможную величину, которую можно представить с помощью математики с плавающей запятой.

Eg:

double n=5.555;
printf("%.2f\n", n);
printf("%.2f\n", _nextafter(n, n*2));
printf("%.20f\n", n);
printf("%.20f\n", _nextafter(n, n*2));

С выходом MSVC:

5.55
5.56
5.55499999999999970000
5.55500000000000060000
person Brad Robinson    schedule 24.06.2009

Этот вопрос помечен как C++, поэтому я буду исходить из этого предположения. Обратите внимание, что потоки C++ будут округляться, в отличие от семейства C printf. Все, что вам нужно сделать, это обеспечить желаемую точность, и библиотека потоков сделает это за вас. Я просто добавляю это на тот случай, если у вас еще нет причин не использовать потоки.

person Brian Neal    schedule 15.06.2009

Вы также можете сделать это (сохраняет умножение/деление):

printf("%.2f\n", coef[i] + 0.00049999);
person Brad Robinson    schedule 15.06.2009
comment
Это округлит 5,549999 до 5,56 ;-) - person ypnos; 15.06.2009
comment
@ypnos, вам действительно нужно проверять вещи перед публикацией - это вообще НЕ дает вам 5,56. - person paxdiablo; 15.06.2009
comment
Извините, заблуждение. Вы все еще должны понять мою точку зрения. 5,544999 + 0,0005 округляется до 5,55. Его следует явно округлить до 5,54. Не требуется ракетостроения, чтобы понять, что добавление смещения не решит таинственным образом проблему округления без введения новых. - person ypnos; 15.06.2009
comment
Один из немногих ответов, которые я поставлю ниже. Это совершенно неправильно. - person John Dibling; 15.06.2009