Что происходит? Почему целочисленный результат не равен 18
в обоих случаях?
Проблема в том, что результат выражения с плавающей запятой округляется до нуля при преобразовании в целочисленное значение (в обоих случаях).
0.1
не может быть представлено точно как значение с плавающей запятой (в обоих случаях). Компилятор выполняет преобразование в двоичное число с плавающей запятой IEEE754 и решает, следует ли округлить в большую или меньшую сторону до представляемого значения. Затем процессор умножает это значение во время выполнения, и результат округляется, чтобы получить целочисленное значение.
Хорошо, но поскольку и double
, и float
ведут себя так, почему я получаю 18
в одном из двух случаев, а 17
— в другом? Я в замешательстве.
Ваш код берет результат функции 0.1f
(число с плавающей запятой), а затем вычисляет 20 * (1.0 - 0.1f)
, которое является двойным выражением, а 20 * (1.0f - 0.1f)
— выражением с плавающей запятой. Теперь версия с плавающей запятой оказывается немного больше 18.0
и округляется до 18
, в то время как двойное выражение немного меньше 18.0
и округляется до 17
.
Если вы не знаете точно, как двоичные числа с плавающей запятой IEEE754 строятся из десятичных чисел, это будет в значительной степени случайным, если оно будет немного меньше или немного больше, чем десятичное число, которое вы ввели в свой код. Так что не стоит на это рассчитывать. Не пытайтесь исправить такую проблему, добавляя f
к одному из чисел и говоря: «Теперь это работает, поэтому я оставлю это f
там», потому что другое значение снова ведет себя по-другому.
Почему тип выражения зависит от наличия этого f
?
Это связано с тем, что литерал с плавающей запятой в C и C++ имеет тип double
по умолчанию. Если вы добавите f
, это будет число с плавающей запятой. Результат выражения с плавающей запятой имеет тип "больше". Результат двойного выражения и целого числа по-прежнему является двойным выражением, а int и float будут числом с плавающей запятой. Таким образом, результатом вашего выражения является либо число с плавающей точкой, либо двойное число.
Хорошо, но я не хочу округлять до нуля. Я хочу округлить до ближайшего числа.
Чтобы устранить эту проблему, добавьте половину к результату, прежде чем преобразовывать его в целое число:
hero->onBeingHit(ENEMY_ATTACK_POINT * (1.0 - hero->getDefensePercent()) + 0.5);
В C++11 для этого есть std::round()
. В предыдущих версиях стандарта не было такой функции для округления до ближайшего целого числа. (Подробности см. в комментариях.)
Если у вас нет std::round
, вы можете написать его самостоятельно. Будьте осторожны при работе с отрицательными числами. При преобразовании в целое число будет усечено (округлено до нуля), что означает, что отрицательные значения будут округлены вверх, а не вниз. Таким образом, мы должны вычесть половину, если число отрицательное:
int round(double x) {
return (x < 0.0) ? (x - .5) : (x + .5);
}
person
leemes
schedule
15.02.2013
.9
или.1
(с типами данных с плавающей запятой). Вероятно, меньшая точность в двойном числе приведет к чему-то вроде18.00x
вместо17.999xxx
. Обратите внимание, чтоfloating point --> int
всегда находится на нижнем уровне. - person Zeta   schedule 15.02.2013double
и, таким образом, заставляет вычисления выполняться вdouble
s, а неfloat
s. - person Damien_The_Unbeliever   schedule 15.02.2013ENEMY_ATTACK_POINT
— целое число. Вы получаете ошибки округления, так как используете целочисленную математику. - person Pubby   schedule 15.02.2013