Why do lots of (old) programs use floor(0.5 + input) instead of round(input)?

The differences reside in the returned value giving inputs around tie-breaking I believe, such as this code:

int main()
{
    std::cout.precision(100);

    double input = std::nextafter(0.05, 0.0) / 0.1;
    double x1 = floor(0.5 + input);
    double x2 = round(input);

    std::cout << x1 << std::endl;
    std::cout << x2 << std::endl;
}

which outputs:

1
0

But they are just different results in the end, one chooses its preferred one. I see lots of "old" C/C++ programs using floor(0.5 + input) instead of round(input).

Is there any historic reason? Cheapest on the CPU?


Solution 1:

std::round is introduced in C++11. Before that, only std::floor was available so programmers were using it.

Solution 2:

There is no historic reason whatsoever. This kind of deviance has been around since year dot. It's an abuse of floating point arithmetic, and many experienced professional programmers fall for it. Even the Java bods did up to version 1.7. Funny guys.

My conjecture is that a decent out-of-the-box rounding function was not formally available until C++11 (despite C getting theirs in C99), but that really is no excuse for adopting the so-called alternative.

Here's the thing: floor(0.5 + input) does not always recover the same result as the corresponding std::round call!

The reason is quite subtle: the cutoff point for rounding, a.5 for an integer a is a dyadic rational. As this can be represented exactly in an IEEE754 floating point up to the 52nd power of 2, and thereafter rounding is a no-op anyway, std::round always works properly. For other floating point schemes, consult the documentation.

But adding 0.5 to a double can introduce imprecision causing a slight under or overshoot for some values. If you think about it, adding two double values together - that are the inception of unwitting denary conversions - and applying a function that is a very strong function of the input (such as a rounding function), is bound to end in tears.

Don't do it.

Reference: Why does Math.round(0.49999999999999994) return 1?

Solution 3:

I think this is where you err:

But they are just different results in the end, one chooses its preferred one. I see lots of "old" C/C++ programs using floor(0.5 + input) instead of round(input).

That is not the case. You must select the right rounding scheme for the domain. In a financial application, you'll round using banker's rules (not using float by the way). When sampling, however, rounding up using static_cast<int>(floor(f + .5)) yields less sampling noise, this increments the dynamic range. When aligning pixels, i.e. converting a position to screen coordinates, using any other rounding method will yield holes, gaps, and other artifacts.

Solution 4:

A simple reason could be that there are different methods of rounding numbers so unless you knew the method used, you could different results.

With floor(), you can be consistent with the results. If the float is .5 or greater, adding it will bump up to the next int. But .49999 will just drop the decimal.