Почему pow(10,5) = 9999 в C++

Недавно я пишу блок кода:

const int sections = 10;

for(int t= 0; t < 5; t++){
   int i = pow(sections, 5- t -1);  
   cout << i << endl;
}

И результат неверный:

9999
1000
99
10
1

Если я использую только этот код:

for(int t = 0; t < 5; t++){
    cout << pow(sections,5-t-1) << endl; 
}

Проблема больше не возникает:

10000
1000
100
10
1

Кто-нибудь дает мне объяснение? большое спасибо!


person Kingfisher Phuoc    schedule 14.03.2012    source источник
comment
Что такое sections и как он инициализируется?   -  person pmdj    schedule 14.03.2012


Ответы (8)


Из-за представления значений с плавающей запятой pow(10.0, 5) может быть 9999,9999999 или что-то в этом роде. Когда вы присваиваете это целому числу, которое было усечено.

РЕДАКТИРОВАТЬ: В случае cout << pow(10.0, 5); похоже, что вывод округлен, но сейчас у меня нет подтверждающего документа, подтверждающего это.

РЕДАКТИРОВАТЬ 2: комментарий, сделанный BoBTFish и этот вопрос подтверждает, что когда pow(10.0, 5) используется непосредственно в cout, оно округляется.

person taskinoor    schedule 14.03.2012
comment
Это не объясняет, почему при прямой печати не печатается 9999,9999999. - person Luchian Grigore; 14.03.2012
comment
@ Лучиан Григоре, возможно ли, что в этом случае он округляется? Я бы тоже хотел услышать это объяснение. - person taskinoor; 14.03.2012
comment
Если точность по умолчанию меньше числа десятичных разрядов, она будет округлена. - person BoBTFish; 14.03.2012
comment
@BoBTFish, не могли бы вы объяснить немного больше. Это поведение cout? - person taskinoor; 14.03.2012
comment
Есть ли решение получить int i = pow(10,5) = 10,000? - person Kingfisher Phuoc; 14.03.2012
comment
@Kingfisher, на моей машине int i = pow(10, 5) результат 10000, а pow(10.0, 5) нет. Похоже, sections в вашем коде двойное. Если вам действительно нужно, чтобы sections было двойным, вместо того, чтобы назначать его i, вам нужно округлить мощность. - person taskinoor; 14.03.2012
comment
std::cout будет округлять числа с плавающей запятой до указанного количества значащих цифр, которое можно изменить с помощью манипулятора std::setprecision. Поэтому, если вы попытаетесь вывести 99.9999 (что, как вы помните, на самом деле является ближайшим приближением к тому, с чем он может справиться, а не точным числом) с точностью, скажем, до 4, округление будет нести 1 до конца и напечатать 100. - person BoBTFish; 14.03.2012
comment
@taskinoor Какой компилятор / библиотеку вы используете, которая не может точно вычислить pow(10.0, 5);? - person Mark B; 15.03.2012
comment
@Mark B, я использую gcc 4.4.1 с MinGW32 на машине с Windows 7. - person taskinoor; 15.03.2012

При использовании с дробными показателями pow(x,y) обычно оценивается как exp(log(x)*y); такая формула была бы математически правильной, если бы оценивалась с бесконечной точностью, но на практике может привести к ошибкам округления. Как уже отмечалось, значение 9999,999999999 при приведении к целому числу даст 9999. Некоторые языки и библиотеки постоянно используют такую ​​формулировку при использовании оператора возведения в степень с показателем с плавающей запятой; другие пытаются определить, когда показатель степени является целым числом, и при необходимости используют итерированное умножение. Просматривая документацию по функции pow, оказалось, что она должна работать, когда x отрицательное, а y не имеет дробной части (когда x отрицательное, а `y четное, результат должен быть pow(-x,y); когда y нечетный, результат должен быть -pow(-x,y) Казалось бы логичным, что, когда y не имеет дробной части, библиотека, которая собирается столкнуться с проблемой работы с отрицательным значением x, должна использовать итерированное умножение, но я не знаю какой-либо спецификации, диктующей это.

В любом случае, если вы пытаетесь возвести целое число в степень, почти наверняка лучше использовать математику целых чисел для вычисления или, если целое число, которое нужно возвести, является константой или всегда будет маленьким, просто используйте таблицу поиска. (возведение чисел от 0 до 15 в любую степень, которая уместилась бы в 64-битном целом числе, потребовало бы только таблицы из 4096 элементов).

person supercat    schedule 14.03.2012

Из здесь

Глядя на функцию pow(): double pow (double base, double exponent); мы знаем, что все параметры и возвращаемое значение относятся к типу double. Но все переменные num, i и res имеют тип int в приведенном выше коде, при преобразовании int в double или double в int это может привести к потере точности. Например (возможно, не строго), модуль с плавающей запятой (FPU) вычисляет pow(10, 4)=9999.99999999, а затем int(9999.9999999)=9999 путем преобразования типа в C++.

Как это решить?

Решение1

Изменить код:

    const int num = 10;

    for(int i = 0; i < 5; ++i){
       double res = pow(num, i);
       cout << res << endl;
    }

Решение2

Замените блок с плавающей запятой (FPU) с более высокой точностью вычислений в типе double. Например, мы используем SSE в ЦП Windows. В Code::Block 13.12 мы можем сделать следующие шаги для достижения цели: Настройка -> Настройка компилятора -> Компиляция GNU GCC -> Другие параметры, добавьте

-mfpmath=sse -msse3

Картина выглядит следующим образом:

добавить ‹code›-mfpmath=sse -msse3‹/code›
(источник: qiniudn.com)

person Aleeee    schedule 24.04.2015

Что происходит, так это то, что функция pow возвращает двойное значение, поэтому, когда вы это делаете

int i = pow(sections, 5- t -1);  

десятичные дроби 0,99999, и вы получите 9999.

а распечатать напрямую или сравнить с 10000 не проблема, потому что в каком-то смысле обкатывается.

person Community    schedule 14.03.2012

Если код в вашем первом примере является именно тем кодом, который вы используете, то у вас есть ошибочная библиотека. Независимо от того, выбираете ли вы std::pow или pow C, который принимает двойные числа, даже если выбрана двойная версия, 10 точно представимо как double. Таким образом, возведение в степень точно можно представить как double. Не должно происходить округления или усечения или чего-либо подобного.

С g++ 4.5 я не мог воспроизвести ваше (странное) поведение, даже используя -ffast-math и -O3.

Теперь я подозреваю, что происходит то, что sections не присваивается литерал 10 напрямую, а вместо этого считывается или вычисляется внутренне, так что его значение является чем-то вроде 9.9999999999999, что при возведении в четвертую степень генерирует номер типа 9999.9999999. Затем оно усекается до отображаемого целого числа 9999.

В зависимости от ваших потребностей вы можете захотеть округлить либо исходный номер, либо окончательный номер перед присвоением в int. Например: int i = pow(sections, 5- t -1) + 0.5; // Add 0.5 and truncate to round to nearest.

person Mark B    schedule 14.03.2012

В глобальном пространстве имен должна быть какая-то сломанная функция pow. Тогда std::pow "автоматически" используется вместо этого во втором примере из-за ADL.

Либо так, либо t на самом деле является величиной с плавающей запятой в вашем первом примере, и вы сталкиваетесь с ошибками округления.

person Lightness Races in Orbit    schedule 14.03.2012
comment
Разве это не проблема округления, как я изначально думал? - person user541686; 14.03.2012

Вы присваиваете результат int. Это принуждает его, усекая число.

Это должно работать нормально:

for(int t= 0; t < 5; t++){
   double i = pow(sections, 5- t -1);  
   cout << i << endl;
}
person Charlie Martin    schedule 14.03.2012

Что происходит, так это то, что ваш ответ на самом деле 99,9999, а не ровно 100. Это потому, что pow двойное. Итак, вы можете исправить это, используя i = ceil(pow()).

Ваш код должен быть:

const int sections = 10;
for(int t= 0; t < 5; t++){
   int i = ceil(pow(sections, 5- t -1));  
   cout << i << endl;
}
person samarth singhal    schedule 05.07.2018
comment
Вы не можете использовать ceil(pow()), потому что он слишком может быть отключен. - person Antti Haapala; 13.08.2020