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