Почему моя целая математика с std:: pow дает неправильный ответ?
Рассмотрим следующий фрагмент кода:
#include <iostream>
#include <cmath>
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)?
Ответы
Ответ 1
std::pow()
работает с числами с плавающей запятой, которые не имеют бесконечной точности и, вероятно, используют стандартную библиотеку, которую вы используете реализует pow()
в (плохой) способ, который делает эту нехватку бесконечной точности актуальной.
Однако вы можете легко определить свою собственную версию, которая работает с целыми числами. В С++ 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);
}
Ответ 2
Все остальные ответы до сих пор пропускают или танцуют вокруг одной и единственной проблемы в вопросе:
pow
в вашей реализации на С++ плохое качество. Он возвращает неверный ответ, когда нет необходимости.
Получите лучшую реализацию на С++ или, по крайней мере, замените в ней математические функции. Хороший на который указывает Паскаль Куок.
Ответ 3
Не с моим хотя бы:
$ 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
.
Ответ 4
Ваша проблема не является ошибкой в gcc, что абсолютно точно. Это может быть ошибкой в реализации pow
, но я думаю, что ваша проблема на самом деле состоит в том, что вы используете pow
, который дает неточный результат с плавающей запятой (потому что он реализован как нечто вроде exp(power * log(base));
и log(base)
никогда не будет абсолютно точным [если base не является степенью e].
Ответ 5
Если вы просто пишете
#include <iostream>
#include <cmath>
int main() {
int i = 23;
int j = 1;
int base = 10;
int k = 2;
i += j * pow(base, k);
std::cout << i << std::endl;
}
Как вы думаете, к чему относится pow
? Стандарт С++ даже не гарантирует, что после включения cmath у вас будет функция pow в глобальной области.
Имейте в виду, что все перегрузки по крайней мере находятся в пространстве имен std
. Существуют функции pow
, которые принимают целочисленную экспоненту и существуют функции pow
, которые принимают показатели с плавающей запятой. Вполне возможно, что ваша реализация на С++ объявляет функцию 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-й степени.
Edit:
Как вы указали, попытка использовать перегрузку std:: pow (double, int) также, похоже, дает значение чуть меньше 100. Я потратил время, чтобы проверить стандарты ISO и реализацию libstdС++, чтобы увидеть, что начиная с С++ 11 перегрузки, принимающие целочисленные показатели, были отброшены в результате разрешения отчета о дефектах 550. Включение поддержки С++ 0x/С++ 11 фактически удаляет перегрузки в реализации libstdС++, что может объяснить, почему вы не заметили никаких улучшений.
Во всяком случае, вероятно, плохая идея полагаться на точность такой функции, особенно если речь идет о преобразовании в целое число. Небольшая ошибка к нулю, очевидно, будет иметь большое значение, если вы ожидаете, что значение с плавающей запятой будет целочисленным (например, 100), а затем преобразует его в значение типа int. Поэтому мое предложение будет писать вашу собственную функцию pow, которая берет все целые числа или проявляет особую осторожность в отношении преобразования double- > int с использованием вашей собственной функции раунда, так что небольшая ошибка торможения нуля не изменяет результат.