длинное длинное значение в 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.