Долгое значение в Visual Studio

Мы знаем, что -2 * 4 ^ 31 + 1 = -9.223.372.036.854.775.807, самое низкое значение, которое вы можете хранить долгое время, как сказано здесь: Что диапазон значений может хранить целые типы в С++. Поэтому у меня есть эта операция:

#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.

Ответ 1

Это очень неприятная проблема, которая имеет три (!) причины.

Во-первых, существует проблема, когда арифметика с плавающей запятой является приблизительной. Если компилятор выбирает функцию 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 не имеет.

В-третьих, функция OP pow не выбрана, потому что он передает аргументы 4 и 31, которые являются типами int, а объявленная функция имеет аргументы типа unsigned. Начиная с С++ 11, существует много перегрузок (или шаблон функции) std::pow. Все они возвращают float или double (если только один из аргументов имеет тип long double - который здесь не применяется).

Таким образом, перегрузка std::pow будет лучшим совпадением... с двойными значениями возврата, и мы получим округление с плавающей запятой.

Мораль истории: не записывайте функции с тем же именем, что и стандартные библиотечные функции, если вы действительно не знаете, что делаете!

Ответ 2

Visual Studio определила pow(double, int), для которой требуется только преобразование одного аргумента, тогда как ваш pow(unsigned, unsigned) требует преобразования обоих аргументов, если вы не используете pow(4U, 31U). Перегрузочное разрешение в С++ основано на входах, а не на типе результата.

Ответ 3

Наименьшее длинное длинное значение можно получить с помощью numeric_limits. Долгое время это:

auto lowest_ll = std::numeric_limits<long long>::lowest();

что приводит к:

-9223372036854775808

Функция pow(), которая вызывается, не соответствует вашим наблюдениям. Измените имя функции.

Ответ 4

Единственным возможным объяснением результата -9.223.372.036.854.775.808 является использование функции pow из стандартной библиотеки, возвращающей двойное значение. В этом случае 5 будет ниже точности двойного вычисления, и результат будет точно -2 63 а преобразованный в длинный длинный будет давать 0x8000000000000000 или -9.223.372.036.854.775.808.

Если вы используете функцию, возвращающую unsigned long long, вы получите предупреждение о том, что вы применяете унарный минус к неподписанному типу и все еще получаете ULL. Таким образом, вся операция должна выполняться как unsigned long long и должна давать без переполнения 0x8000000000000005 как значение unsigned. Когда вы передаете его значению со знаком, результатом будет undefined, но все компиляторы, которые я знаю, просто используют целое число со знаком с таким же представлением, которое -9.223.372.036.854.775.803.

Но было бы просто сделать вычисление долгое долгое знаком без предупреждения, просто используя:

long long nr =  -1 * pow(4, 31) + 5 - pow(4,31);

В качестве дополнения у вас нет ни undefined листинга, ни переполнения, чтобы результат был полностью определен в стандарте, если unsigned long long имеет не менее 64 бит.

Ответ 5

В первом вызове 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-значным числам!). Чтобы получить ответ, который вы ищете, измените подпись на:

long long my_pow(unsigned a, unsigned b);

Это сработало для меня в MSVС++ 2013. Как указано в других ответах, вы получаете плавучую точку pow, потому что ваша функция ожидает unsigned и получает подписанные целочисленные константы. Добавление U в ваши целые числа вызывает вашу версию pow.