Подтвердить что ты не робот

Почему моя целая математика с 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)?

4b9b3361

Ответ 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 с использованием вашей собственной функции раунда, так что небольшая ошибка торможения нуля не изменяет результат.