Как выполнить округление с плавающей запятой со смещением (всегда округлять вверх или вниз)?

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

Например, я хочу округлить до ближайшего кратного 1/10. Ближайшее число с плавающей запятой к 7/10 примерно равно 0,69999998807, а ближайшее число к 8/10 примерно равно 0,80000001192. Когда я округляю числа, я получаю два результата. Я бы предпочел округлить их таким же образом. 7/10 следует округлить до 0,70000004768, а 8/10 — до 0,80000001192.

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

Строка, которую я использую для округления, это floor(val * 100 + 0.5) / 100. Я программирую на С++.


person user4891    schedule 26.03.2009    source источник
comment
FWIW, вывод, который вы могли бы извлечь из моего удаленного ответа (который, как я понял, не касался правильной части вашего вопроса), заключается в том, что общее округление до произвольной точности «p» может быть выполнено следующим образом: пол (val / prec + 0,5 ) * пред. Более интуитивно, чем то, как вы это пишете.   -  person chaos    schedule 26.03.2009
comment
(Возможно, из-за неинтуитивности ваше выражение округляется до ближайшей 1/100, а не до ближайшей 1/10.)   -  person chaos    schedule 26.03.2009
comment
Почему кто-то изменил теги в этом посте? Ничего не добавилось, порядок изменился. Это кажется легкомысленным редактированием, поэтому я вернул его.   -  person user4891    schedule 08.04.2009


Ответы (1)


Я думаю, что лучший способ добиться этого — полагаться на тот факт, что в соответствии со стандартом IEEE 754 с плавающей запятой целочисленное представление битов с плавающей запятой лексикографически упорядочено как целое число с дополнением до 2.

т.е. вы можете просто добавить один ulp (единицы на последнем месте), чтобы получить следующее представление с плавающей запятой (которое всегда будет немного больше вашего порога, если оно было меньше, поскольку ошибка округления составляет не более 1/2 ulp)

e.g.

 float floatValue = 7.f/10;
 std::cout << std::setprecision(20) << floatValue << std::endl;
 int asInt = *(int*)&floatValue;
 asInt += 1;
 floatValue = *(float*)&asInt;
 std::cout << floatValue << std::endl;

отпечатки (в моей системе)

 0.69999998807907104492
 0.70000004768371582031

Чтобы узнать, когда вам нужно добавить одну ulp, вам придется полагаться на разницу между floor и округленным floor.

 if (std::floor(floatValue * 100.) != std::floor(floatValue * 100. + 0.5)) {
    int asInt = *(int*)&floatValue;
    asInt += 1;
    floatValue = *(float*)&asInt;
 }

Было бы правильно преобразовать 0,69 .. в 0,70 .. но оставить 0,80 .. в покое.

Обратите внимание, что число с плавающей запятой повышается до двойного путем умножения на 100. до применения floor.

Если вы этого не сделаете, вы рискуете оказаться в ситуации,

 7.f/10.f * 100.f

Представление с плавающей запятой (ограниченное по точности) будет 70,00...

person Pieter    schedule 26.03.2009
comment
+1 за потрясающую игру в покер. Я чувствую себя умнее после прочтения этого. - person J. Polfer; 26.03.2009
comment
круто :) проще было бы использовать псевдоним напрямую: (int&)floatValue += 1; - person ; 26.03.2009
comment
Я только что обнаружил в C99 функцию nexttoward, которую можно использовать для увеличения значения ulp, что хорошо скрывает манипуляции с битами. penguin-soft.com/penguin/man/3/nextafter.html< /а> - person user4891; 17.04.2009