длинное длинное значение в Visual Studio
мы знаем, что -2*4^31 + 1 = -9.223.372.036.854.775.807, самое низкое значение, которое вы можете хранить в long long, как говорится здесь:какой диапазон значений могут хранить целочисленные типы В C++. Итак, у меня есть эта операция:
#include <iostream>
unsigned long long pow(unsigned a, unsigned b) {
unsigned long long p = 1;
for (unsigned i = 0; i < b; i++)
p *= a;
return p;
}
int main()
{
long long nr = -pow(4, 31) + 5 -pow(4,31);
std::cout << nr << std::endl;
}
почему он показывает -9.223.372.036.854.775.808 вместо -9.223.372.036.854.775.803? Я использую Visual Studio 2015.
5 ответов
это действительно неприятная маленькая проблема, которая имеет три(!) причины.
во-первых, существует проблема, что арифметика с плавающей точкой является приблизительной. Если компилятор выбирает pow
функция, возвращающая float или double, тогда 4* * 31 настолько велика, что 5 меньше 1ULP( единица наименьшей точности), поэтому добавление ее ничего не сделает (другими словами, 4.0**31+5 == 4.0**31). Умножение на -2 можно сделать без потерь, а результат можно сохранить в long long
без потери как неправильный ответ: -9.223.372.036.854.775.808.
во-вторых, стандартный заголовок мая включить другие стандартные заголовки, но не обязан. Очевидно, версия Visual Studio <iostream>
включает в себя <math.h>
(который декларирует pow
в глобальном пространстве имен), но версия Code:: Blocks этого не делает.
в-третьих, ОП pow
функция Не выбрана, потому что он передает Аргументы 4
и 31
, которые оба типа int
, и заявленные функции аргументы типа unsigned
. Начиная с C++11, существует множество перегрузок (или шаблон функции)std::pow
. Эти все возвращаются float
или double
(если только один из аргументов не имеет типа long double
- который здесь не применим).
std::pow
будет лучше соответствовать ... с двойным возвращением значений, и мы получаем округление с плавающей запятой.
мораль истории: не пишите функции с тем же именем, что и стандартные библиотечные функции, если только ты ... --20-->действительно знаете, что вы делаете!
Visual Studio определила pow(double, int)
, что требует преобразования только одного аргумента, тогда как ваш pow(unsigned, unsigned)
требует преобразования обоих аргументов, если вы не используете pow(4U, 31U)
. Разрешение перегрузки в C++ основано на входных данных, а не на типе результата.
самое низкое длинное длинное значение можно получить через numeric_limits. Долго долго это:
auto lowest_ll = std::numeric_limits<long long>::lowest();
что приводит к:
-9223372036854775808
на pow()
функция, которая вызывается не ваше, следовательно, наблюдаемые результаты. Измените имя функции.
единственным возможным объяснением для результата -9.223.372.036.854.775.808 является использование pow
функция из стандартной библиотеки возвращает двойное значение. В таком случае,5
будет ниже точности двойного вычисления, и результат будет точно -263 и преобразованный в длинный длинный даст 0x8000000000000000
или -9.223.372.036.854.775.808
.
если вы используете функцию, возвращающую unsigned long long, вы получаете предупреждение о том, что вы применяете унарный минус к тип unsigned и до сих пор Улла. Таким образом, вся операция должна выполняться как unsigned long long и должна давать без переполнения 0x8000000000000005
как значение без знака. Когда вы приводите его к знаковому значению, результат не определен, но все компиляторы, которые я знаю, просто используют знаковое целое число с тем же представлением, которое -9.223.372.036.854.775.803
.
но было бы просто сделать вычисление как подписанное long long без какого-либо предупреждения, просто используя:
long long nr = -1 * pow(4, 31) + 5 - pow(4,31);
в качестве дополнения у вас есть ни undefined cast, ни overflow здесь, поэтому результат идеально определен для стандарта при условии, что unsigned long long составляет не менее 64 бит.
ваш первый звонок в pow
использует функцию стандартной библиотеки C, которая работает с плавающими точками. Попробуйте дать свой pow
функции уникальное имя:
unsigned long long my_pow(unsigned a, unsigned b) {
unsigned long long p = 1;
for (unsigned i = 0; i < b; i++)
p *= a;
return p;
}
int main()
{
long long nr = -my_pow(4, 31) + 5 - my_pow(4, 31);
std::cout << nr << std::endl;
}
этот код сообщает об ошибке:"унарный оператор минус применяется к типу без знака, результат по-прежнему без знака". Таким образом, по сути, ваш исходный код вызвал функцию с плавающей запятой, отрицал значение, применил к нему некоторую целочисленную арифметику, для которой у него не было достаточной точности, чтобы дать ответ, который вы были ищем (на 19 цифрах presicion!). Чтобы получить ответ, который вы ищете, измените подпись на:
long long my_pow(unsigned a, unsigned b);
это сработало для меня в MSVC++ 2013. Как указано в других ответах, вы получаете плавающую точку pow
потому что ваша функция ожидает unsigned
, и получает целочисленные константы со знаком. Добавление U
для ваших целых чисел вызывает вашу версию pow
.