4

I have the following line of code.

hero->onBeingHit(ENEMY_ATTACK_POINT * (1.0 - hero->getDefensePercent()));
  • void onBeingHit(int decHP) method accepts integer number and updates health points.
  • float getDefensePercent() method is a getter method returning the defense percent of a hero.
  • ENEMY_ATTACK_POINT is a macro constant factor defined as #define ENEMY_ATTACK_POINT 20.

Let's say hero->getDefensePercent() returns 0.1. So the calculation is

20 * (1.0 - 0.1)  =  20 * (0.9)  =  18

Whenever I tried it with the following code (no f appending 1.0)

hero->onBeingHit(ENEMY_ATTACK_POINT * (1.0 - hero->getDefensePercent()));

I got 17.

But for the following code (f appended after 1.0)

hero->onBeingHit(ENEMY_ATTACK_POINT * (1.0f - hero->getDefensePercent()));

I got 18.

What's going on? Is f significant to have at all although hero->getDefensePercent() is already in float?

leemes
  • 44,967
  • 21
  • 135
  • 183
haxpor
  • 2,431
  • 3
  • 27
  • 46
  • 1
    You cannot save `.9` or `.1` exactly (with floating point data types). Probably the lesser precision in the double will result in something like `18.00x` instead of `17.999xxx`. Note that `floating point --> int` is always floored down. – Zeta Feb 15 '13 at 07:55
  • Don't know C++ too well, but I'd expect that your literal is interpreted as `double` and thus forcing the calculation to be done in `double`s rather than `float`s. – Damien_The_Unbeliever Feb 15 '13 at 07:57
  • `ENEMY_ATTACK_POINT` is an integer. You're getting rounding errors since you're using integer math. – Pubby Feb 15 '13 at 07:58
  • 1
    http://liveworkspace.org/code/1KmxNy$0 – BoBTFish Feb 15 '13 at 07:58
  • 2
    http://liveworkspace.org/code/uNqnq$7 – BoBTFish Feb 15 '13 at 08:04
  • Zeta and Damien Makes sense, and accompanied with leemes 's answer below. Thanks for fast respond. – haxpor Feb 15 '13 at 08:08
  • @Pubby But another variable is higher precision, so this should be no problem. – haxpor Feb 15 '13 at 08:09
  • @BoBTFish Thanks for your effort in putting out sample codes there. +1 for this. – haxpor Feb 15 '13 at 08:11

3 Answers3

11

What's going on? Why isn't the integer result 18 in both cases?

The problem is that the result of the floating point expression is rounded towards zero when being converted to an integer value (in both cases).

0.1 can't be represented exactly as a floating point value (in both cases). The compiler does the conversion to a binary IEEE754 floating point number and decides whether to round up or down to a representable value. The processor then multiplies this value during runtime and the result is rounded to get an integer value.

Ok, but since both double and float behave like that, why do I get 18 in one of the two cases, but 17 in the other case? I'm confused.

Your code takes the result of the function, 0.1f (a float), and then calculates 20 * (1.0 - 0.1f) which is a double expression, while 20 * (1.0f - 0.1f) is a float expression. Now the float version happens to be slightly larger than 18.0 and gets rounded down to 18, while the double expression is slightly less than 18.0 and gets rounded down to 17.

If you don't know exactly how IEEE754 binary floating point numbers are constructed from decimal numbers, it's pretty much random if it will be slightly less or slightly greater than the decimal number you've entered in your code. So you shouldn't count on this. Don't try to fix such an issue by appending f to one of the numbers and say "now it works, so I leave this f there", because another value behaves differently again.

Why depends the type of the expression on the precence of this f?

This is because a floating point literal in C and C++ is of type double per default. If you add the f, it's a float. The result of a floating point epxression is of the "greater" type. The result of a double expression and an integer is still a double expression as well as int and float will be a float. So the result of your expression is either a float or a double.

Ok, but I don't want to round to zero. I want to round to the nearest number.

To fix this issue, add one half to the result before converting it to an integer:

hero->onBeingHit(ENEMY_ATTACK_POINT * (1.0 - hero->getDefensePercent()) + 0.5);

In C++11, there is std::round() for that. In previous versions of the standard, there was no such function to round to the nearest integer. (Please see comments for details.)

If you don't have std::round, you can write it yourself. Take care when dealing with negative numbers. When converting to an integer, the number will be truncated (rounded towards zero), which means that negative values will be rounded up, not down. So we have to subtract one half if the number is negative:

int round(double x) {
    return (x < 0.0) ? (x - .5) : (x + .5);
}
Community
  • 1
  • 1
leemes
  • 44,967
  • 21
  • 135
  • 183
  • Thanks for insight explanation. – haxpor Feb 15 '13 at 08:10
  • 3
    You don't have to add `0.5`, or write your own function. Just use `std::round()` (in ``). In general, I'd recommend always using one of the standard functions (`round`, `floor`, `ciel` or `trunc`); this makes your intent clear (and makes you think about what you actually want to do). – James Kanze Feb 15 '13 at 08:50
  • @JamesKanze It's C++11 only. I edited the answer accoringly, thanks. – leemes Feb 15 '13 at 08:53
  • @leemes It's also C99 (and Posix), so present in most C++03 or even C++98 implementations. Microsoft might be the exception, since they explicitly don't support modern C. But Microsoft has more or less led in support for C++11, so any recent Microsoft compiler should support them. And of course, since they're also Posix, any Unix compiler should have them. – James Kanze Feb 15 '13 at 08:59
  • @JamesKanze Fixed it. I didn't know that. But in C++03 it isn't `std::round` but just `round`. In the answer, I now refer to the comments. – leemes Feb 15 '13 at 09:11
  • @leemes Yes. You might have to include ``, and use just `round`. (And what would happen if a header you include includes ``, and you use your definition? As a general rule, you should never define a function that has the same name as anything in the C standard library---or in any other C API you're using, like Posix. But you can possibly know them all?) – James Kanze Feb 15 '13 at 09:26
4

1.0 is interpreted as a double, as opposed to 1.0f which is seen by the compiler as a float.

The f suffix simply tells the compiler which is a float and which is a double.

As the name implies, a double has 2x the precision of float. In general a double has 15 to 16 decimal digits of precision, while float only has 7.

This precision loss could lead to truncation errors much easier to float up

See MSDN (C++)

  • 3
    `double` has at least as much precision as `float`. Whether it has the same, less than twice, twice or more than twice is implementation defined. In the usual IEEE formats, `double` has 52 bits precision, `float` has 24 bits, `double` 53, so `double` has more than twice the precision. But there are processors (not that most C++ programmers will ever see them) where `double` and `float` are identical. (Note too that on most mainframes, machine floating point is not binary, but base 16 or base 8.) – James Kanze Feb 15 '13 at 08:55
  • Also (again just picking nits---there's really nothing wrong with your answer): what is meant by decimal precision varies. For round trip (convert to decimal and back, guaranteed to get the same value), with IEEE, you need 9 (`float`) or 17 (`double`) digits. Alternatively, you can consider the round trip in the other direction, which gives 6 and 15. (These correspond to `digits10` and `max_digits10` in `numeric_limits`.) – James Kanze Feb 15 '13 at 09:08
  • Thats ok :) I like new infos :) –  Feb 15 '13 at 10:40
4

The reason why is this happening is more precise result when using double, i.e. 1.0.

Try to round your result, which will lead to more precise integral result after conversion:

hero->onBeingHit(ENEMY_ATTACK_POINT * (1.0 - hero->getDefensePercent()) + 0.5);

Note that adding 0.5 and truncating to int right after it will cause rounding of the result, so by the time your result would be 17.999..., it will become 18.499..., which will be truncated to 18

LihO
  • 41,190
  • 11
  • 99
  • 167