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

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

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

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

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

1 Ответ

11 голосов
/ 26 марта 2009

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

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

, например

 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

Чтобы узнать, когда вам нужно добавить один ульп, вам нужно полагаться на разницу 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 ...

...