Почему моя целая математика с std :: pow дает неправильный ответ?

Рассмотрим следующий fragment кода:

#include  #include  int main() { int i = 23; int j = 1; int base = 10; int k = 2; i += j * pow(base, k); std::cout << i << std::endl; } 

Он выводит «122» вместо «123». Это ошибка в g ++ 4.7.2 (MinGW, Windows XP)?

std::pow() работает с числами с плавающей запятой, которые не имеют бесконечной точности, и, вероятно, реализация стандартной библиотеки, которую вы используете, реализует pow() в (плохой) способ, который делает эту нехватку бесконечной точности актуальной.

Однако вы можете легко определить свою собственную версию, которая работает с целыми числами. В C ++ 11 вы даже можете сделать его constexpr (чтобы результат мог быть вычислен во время компиляции, когда это было возможно):

 constexpr int int_pow(int b, int e) { return (e == 0) ? 1 : b * int_pow(b, e - 1); } 

Вот живой пример .


Хвост-рекурсивная форма (кредиты Дэну Ниссенбауму ):

 constexpr int int_pow(int b, int e, int res = 1) { return (e == 0) ? res : int_pow(b, e - 1, b * res); } 

Все остальные ответы до сих пор пропускают или танцуют вокруг одной и единственной проблемы в вопросе:

pow в вашей реализации на C ++ низкое. Он возвращает неверный ответ, когда нет необходимости.

Получите лучшую реализацию на C ++ или, по крайней мере, замените в ней математические функции. Тот, на что указывает Паскаль Куок , хорош.

Не с моим хотя бы:

 $ g++ --version | head -1 g++ (GCC) 4.7.2 20120921 (Red Hat 4.7.2-2) $ ./a.out 123 

IDEone также работает версия 4.7.2 и дает 123 .


Подписи pow() от http://www.cplusplus.com/reference/cmath/pow/

  double pow ( double base, double exponent ); long double pow ( long double base, long double exponent ); float pow ( float base, float exponent ); double pow ( double base, int exponent ); long double pow ( long double base, int exponent ); 

Вы должны установить double base = 10.0; и double i = 23.0 .

Если вы просто напишете

 #include  #include  int main() { int i = 23; int j = 1; int base = 10; int k = 2; i += j * pow(base, k); std::cout << i << std::endl; } 

как вы думаете, на что должно ссылаться? Стандарт C ++ даже не гарантирует, что после включения cmath у вас будет функция pow в глобальной области.

Имейте в виду, что все перегрузки по крайней мере находятся в пространстве имен std . Существуют функции pow которые принимают целочисленный показатель, и существуют функции pow которые принимают показатели с плавающей запятой. Вполне возможно, что ваша реализация на C ++ объявляет функцию C pow в глобальном масштабе. Эта функция принимает показатель с плавающей запятой. Дело в том, что эта функция, вероятно, будет иметь несколько ошибок приближения и округления. Например, одним из возможных способов реализации этой функции является:

 double pow(double base, double power) { return exp(log(base)*power); } 

Вполне возможно, что pow (10.0,2.0) дает что-то вроде 99.99999999992543453265 из-за ошибок округления и аппроксимации. В сочетании с тем фактом, что преобразование с плавающей точкой в ​​целое число дает число до десятичной точки, это объясняет ваш результат 122, потому что 99 + 3 = 122.

Попробуйте использовать перегрузку pow, которая принимает целочисленный показатель и / или делает правильное округление от float до int. Перегрузка с целочисленным показателем может дать вам точный результат для 10-й степени.

Редактировать:

Как вы указали, попытка использовать перегрузку std :: pow (double, int) также, похоже, дает значение чуть меньше 100. Я потратил время, чтобы проверить стандарты ISO и реализацию libstdc ++, чтобы увидеть, что начиная с C ++ 11 перегрузки с целыми показателями были опущены в результате разрешения отчета 550 о дефектах . Включение поддержки C ++ 0x / C ++ 11 фактически удаляет перегрузки в реализации libstdc ++, что может объяснить, почему вы не заметили никаких улучшений.

Во всяком случае, вероятно, плохая идея полагаться на точность такой функции, особенно если речь идет о преобразовании в целое. Небольшая ошибка к нулю, очевидно, будет иметь большое значение, если вы ожидаете, что значение с плавающей запятой будет целочисленным (например, 100), а затем преобразует его в значение типа int. Таким образом, мое предложение будет писать вашу собственную функцию pow, которая берет все целые числа или проявляет особую осторожность в отношении преобразования double-> int, используя вашу собственную круглую функцию, так что небольшая ошибка торможения нуля не изменяет результат.

Ваша проблема не является ошибкой в ​​gcc, это абсолютно точно. Это может быть ошибкой в ​​реализации pow , но я думаю, что ваша проблема – это просто факт, что вы используете pow который дает неточный результат с плавающей запятой (потому что он реализован как нечто вроде exp(power * log(base)); а log(base) никогда не будет абсолютно точной [если база не является степенью e].

  • Почему Java Double.compare (double, double) реализован так, как есть?
  • Какие типы чисел представляются в двоичной с плавающей запятой?
  • Каков наиболее эффективный способ для плавающего и двойного сравнения?
  • Каким детерминированным является неточность с плавающей запятой?
  • Решение проблем точности чисел с плавающей запятой
  • Получение фактической ширины элемента с плавающей запятой
  • Преобразование float в double без потери точности
  • Как обнаружить переполнение и переполнение двойной точки с двойной точностью?
  • Является ли наиболее значительная десятичная цифра точностью, которая может быть преобразована в двоичную и обратно в десятичную без потери значимости 6 или 7.225?
  • Java: Почему мы должны использовать BigDecimal вместо Double в реальном мире?
  • Как получить десятичную часть поплавка?
  • Давайте будем гением компьютера.