84

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
markzzz
  • 47,390
  • 120
  • 299
  • 507
  • 20
    std::round moves halfway cases away from zero. That's not mathematically uniform, like floor(f + .5), where the halfway cases always go towards the upper side. The reason for using the floor method is that it's required for proper rounding in the engineering world. – Michaël Roy Nov 15 '17 at 10:46
  • 2
    @MichaëlRoy Could you give an example for that? "it's required for proper rounding in the engineering world" – Arne Vogel Nov 15 '17 at 12:41
  • 7
    As noted in [round() for float in C++](https://stackoverflow.com/a/24348037/1708801) pre-C++11 we did not have round. As I noted in my answer writing your own round correctly is a hard problem. – Shafik Yaghmour Nov 15 '17 at 13:48
  • 2
    Why std::nextafter(0.05, 0.0) / 0.1; instead of std::nextafter(0.5, 0.0);? Shouldn’t it produce the same result? Apparently it does: http://coliru.stacked-crooked.com/a/9bf4f15072e81cb9 Kind of a red herring. – Michael Nov 15 '17 at 14:13
  • 16
    @Arne Using std::round() the distance between the rounded values of -0.5 and +0.5 is 2. using floor, it is 1. Only happens when the two values have opposite signs. Very irritating when trying to draw straight lines, or makes you pick the wrong texture pixel. – Michaël Roy Nov 15 '17 at 14:31
  • 20
    Some programming languages and environments (including .NET) use a deceiving thing called Banker's Rounding, in which x.5 rounds to the nearest EVEN number. So 0.5 rounds to 0 while 1.5 rounds to 2. You can imagine the confusion this can cause when debugging. I think the solution to this evil 'feature' is to not have a .Round() function at all, and instead have .RoundBankers(), .RoundHalfUp(), .RoundHalfDown(), etc (or .BankersRound(), etc but intellisense would work better with .RoundBankers()). At least that way you would be forced to know what to expect. – user3685427 Nov 15 '17 at 19:33
  • 3
    @user368 Certainly one option to force people to be explicit. But if you don't do that using banker's rounding is vastly superior to any other option since everything else leads to unnecessary deviations from the true mean when rounding multiple numbers. And since clearly people didn't care enough to specify what they want, you might as well give them the best option. – Voo Nov 15 '17 at 20:33
  • 2
    @Voo: It's possible to have a rounding scheme that guarantees that round(x)==-round(-x), or one that guarantees that round(x+n)==round(x)+n in cases where x+n is exactly representable. If there weren't a predefined function for banker's rounding, it would be awkward to implement, so there's more value in having a predefined function for banker's rounding than one for half-rounds-to-higher, but for many common purposes the latter is actually better. – supercat Nov 15 '17 at 21:55
  • 2
    @user3685427 .NET's `Math.Round` function lets you pick between `ToEven` ("banker's rounding", the default) and `AwayFromZero`, which rounds positive numbers up and negative numbers down. – Michael Edenfield Nov 16 '17 at 02:38
  • Unless there is some hard evidence presented to support the assertion on which the question is based this question is off topic. – user207421 Nov 16 '17 at 09:44
  • 9
    @user3685427: *Banker's Rounding* is necessary in financial and statistical applications that require elimination of the subtle and systemic upward bias introduced by *away from zero* rounding. It is almost impossible to implement without actual knowledge of the hardware floating point implementation, hence it's selection as the default in C#. – Pieter Geerkens Nov 16 '17 at 10:54
  • 1
    @supercat Why is it "better"? In some situations where you show the original and the rounded value to the user it might lead to less confusion for your average user, but round to higher introduces a clear statistical bias without any other advantage apart from being marginally easier. – Voo Nov 16 '17 at 11:46
  • 1
    There exist many different modes of rounding floating point numbers. If you just take any function called "round" without checking what it does you won't know which mode of rounding you will get. – mathreadler Nov 16 '17 at 13:24
  • @Voo: In cases where one is rounding many values of the form `a+b*n` [with integer `n`], and `b` will be either a whole number or a fraction with a small power-of-two denominator, having groups of consecutive equally-spaced values appear as e.g. [-3, -2, -1, 0, 1, 2, 3] or [-2,-2,-1,-1,0,0,1,1,2,2] may be better than having them appear as [-4, -2, -2, 0, 0, 2, 2] or [-2,-2,-2,-1,0,0,0,1,2,2]. Banker's rounding is good as a floating-point rounding mode, and is appropriate in some number-to-string situations, but in some cases a more uniform approach would be better. – supercat Nov 16 '17 at 17:22
  • @MichaëlRoy: That's why you should use [`nearbyint()`](http://en.cppreference.com/w/cpp/numeric/math/nearbyint) (which uses the current rounding mode) instead of `round()` (which always uses away-from-zero for half-way cases.) – Peter Cordes Nov 17 '17 at 03:22
  • 1
    @Pater. And rely on an unknown rounding mode to do math? – Michaël Roy Nov 17 '17 at 14:11

5 Answers5

118

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

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
haccks
  • 104,019
  • 25
  • 176
  • 264
  • Nothing. But its older than C++11. I thought logical that C++ got it before 11. That's all ;) – markzzz Nov 15 '17 at 08:44
  • 12
    @markzzz - The C++ standard library doesn't automatically inherit C's standard library. There's careful picking and choosing going on. It took them 12 years to sync to C99. – StoryTeller - Unslander Monica Nov 15 '17 at 08:45
  • 2
    @haccks: Indeed. IMHO C was ahead of C++ in terms of mathematical functions up to C++11. – Bathsheba Nov 15 '17 at 08:55
  • 3
    Important to note that [the method being used breaks for some inputs](https://stackoverflow.com/a/24348037/1708801) and also that C++11 relies on C99 while C++03 relied on C90 which kind of goes to @markzzz point. – Shafik Yaghmour Nov 15 '17 at 14:03
  • 1
    @markzzz: Your remark is spot on (despite critics). The thing is, there was a long gap without C++ standards, the first C++ standard was C++98 and the first major revision was C++11. There was a minor update, C++03, but as the [Wikipedia page](https://en.wikipedia.org/wiki/C%2B%2B03) notes it was mostly a "bug fix" release. Therefore, C++11 was the first opportunity to catch up to the C standard library, after 13 years of iatus. – Matthieu M. Nov 16 '17 at 11:49
  • std::round is banker's rounding. This has nothing to do with std::floor. And std::round is not appropriate for roundng in any other domain than the financial domain... Since any seripus money-counting app uses some form of integer representation for monetary values, I'll say std::round is quite useless in real life. – Michaël Roy Jul 10 '21 at 05:56
22

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?

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 2
    Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackoverflow.com/rooms/159106/discussion-on-answer-by-bathsheba-why-do-lots-of-old-programs-use-floor0-5). – Andy Nov 16 '17 at 03:52
  • 2
    [`nearbyint()`](http://en.cppreference.com/w/cpp/numeric/math/nearbyint) is usually a better choice than [`round()`](http://en.cppreference.com/w/cpp/numeric/math/round), because `nearbyint` uses the current rounding mode instead of the funky tiebreak away from zero of `round()` (which x86 doesn't even have hardware support for, although ARM does). Both were added to C++ in C++11. – Peter Cordes Nov 17 '17 at 03:26
10

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.

Michaël Roy
  • 6,338
  • 1
  • 15
  • 19
  • "this increments the dynamic range" - looks like meaningless extra text; copy-pasted from somewhere by mistake? Might want to delete it. – anatolyg Nov 16 '17 at 22:46
  • No. Decreasing the sampling noise decreases the noise floor and that does indeed increase the dynamic range. – Michaël Roy Nov 17 '17 at 02:55
  • Could you provide a simple example or a reference to illustrate the increase of the dynamic range/decrease of noise? – Ruslan Oct 08 '19 at 14:41
  • 2
    With standard integer (lack of) rounding, all values between zero and minus one "disappear"', or rather change sign (same value and fo 0 to + 1 inputs), all negative values are offset by at least one bit. Here's one bit of noise there with distorsion added. – Michaël Roy Oct 08 '19 at 18:50
  • Saw things similar like specified with (int)(value + 0.5) instead of floor, found it in an audio programming book. So I can see that making sense for my application but can that be proved/is there a quick read on why that's the case? – SeedyROM Jun 20 '21 at 15:39
  • 1
    @Seedy using floor is needed to correctly round negative values. Example: floor(-1.1) == -2.0, while (int)-1.1 == -1. Not using floor will bias rounding negative values towards zero. – Michaël Roy Jun 21 '21 at 14:23
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.

MivaScott
  • 1,763
  • 1
  • 12
  • 30
  • +1 This answer's point is made by the very comments on the question, wherein there is disagreement about what `round()` does. – Loduwijk Nov 15 '17 at 20:34
  • @Aaron The answer is wrong about what `floor(x + 0.5)` does though. – EOF Nov 15 '17 at 20:36
  • Oh, hah! Good catch then. That is ironic. "Use the better known X since we are not in agreement or full knowledge about Y." So what do you do when the same applies to X? – Loduwijk Nov 15 '17 at 21:35
  • 1
    @Aaron Easy. You do the only sane thing and use `nearbyint(x)`, which uses sane (to nearest even) rounding, provided you haven't messed with the floating point environment. – EOF Nov 15 '17 at 23:08
  • @EOF : Your rounding choice is not sane. A rounding function that does not have period 1 in the nonlinear component is insane. – Eric Towers Nov 16 '17 at 17:51
  • @EricTowers Are you seriously going to claim that the default rounding for ieee754 floating point is insane? – EOF Nov 16 '17 at 17:58
  • @EOF : Yes. I am not a banker. I use "[common rounding](https://www.mathsisfun.com/rounding-numbers.html)". – Eric Towers Nov 16 '17 at 18:02
  • @EricTowers I hope you don't mind the systematic error - I know I do. – EOF Nov 16 '17 at 20:35
  • @EOF : There is no systematic error. Every preimage is congruent. – Eric Towers Nov 17 '17 at 12:59
  • @EricTowers and EOF: Both rounding methods (and others which exist besides those two, of which `floor` is one) are sane; they just do not apply to the same use cases. Sometimes normal rounding is appropriate, sometimes rounding to the nearest even is more appropriate so you do not have a bias in your statistics, and sometimes rounding to floor is correct. If you are switching away from `floor(x+0.5)` then I think it is evident that the normal method is desired, though a rant why "to even" avoids bias (because of 0.5 special treatment) could be in order (if it weren't off topic for OP). – Loduwijk Nov 17 '17 at 16:05
2

Many programmers adapt idioms that they learned when programming with other languages. Not all languages have a round() function, and in those languages it's normal to use floor(x + 0.5) as a substitute. When these programmers start using C++, they don't always realize that there's a built-in round(), they continue to use the style they're used to.

In other words, just because you see lots of code that does something, it doesn't mean there's a good reason to do it. You can find examples of this in every programming language. Remember Sturgeon's Law:

ninety percent of everything is crap

Barmar
  • 741,623
  • 53
  • 500
  • 612