Почему std::abs(9484282305798401ull) = 9484282305798400?

В настоящее время я пишу шаблонный вспомогательный метод, который может преобразовывать числа C в целом (включая unsigned long long) в числа mpz_class в библиотеке GMP. Между ними есть вызов std::abs.

Однако оказывается, что для C++17 (g++ 6.3.1)

#include <iostream>
#include <cmath>

int main()
{
    std::cout << (unsigned long long)std::abs(9484282305798401ull);
}

дает неверный вывод 9484282305798400.

Как я понял из cmath, std::abs сначала приводит аргумент к типу double.

Согласно документам C++, double имеет 52 бита мантиссы, а это означает, что максимальное целочисленное значение I должно быть строго меньше 2^52 = 4503599627370496 до какой-либо потери точности.

Правильно ли я говорю, что, поскольку 9484282305798401 превышает этот предел, std::abs в конечном итоге отбрасывает точность, чтобы дать неверный ответ?

Чтобы уточнить, я абсолютно осознаю, что совершенно бессмысленно запрашивать абсолютное значение целого числа без знака; Однако я хотел бы, чтобы шаблонная функция работала для общих чисел C, вместо того, чтобы специально создавать специализацию для каждого подписанного и неподписанного типа отдельно.


person Yiyuan Lee    schedule 31.05.2017    source источник
comment
Если можно, какой смысл вызывать abs для беззнакового типа?   -  person stybl    schedule 31.05.2017
comment
Я пишу шаблонную функцию, которая выполняет некоторые арифметические действия перед вызовом класса mpz_import библиотеки GMP; Между ними есть вызов std::abs, который может потребоваться для подписанных номеров.   -  person Yiyuan Lee    schedule 31.05.2017
comment
Относится к Является ли std::abs(0u) неверным?   -  person Shafik Yaghmour    schedule 31.05.2017
comment
Обратите внимание, что хотя двойная точность IEEE 754 использует 52 бита для хранения мантиссы, фактически имеется 53 бита, поскольку значение нормализовано и существует «неявная 1» (см. этот пост SO:   -  person Paul Floyd    schedule 01.06.2017


Ответы (3)


Во-первых, то, что вы делаете, не имеет смысла вне контекста (получение абсолютного значения беззнакового типа). Но я отвлекся.

Код, который вы разместили, не компилируется. По крайней мере, не в компиляторе, который я использовал (в зависимости от того, какой использует repl.it). Вместо этого он жалуется на неоднозначную перегрузку. Даже если бы он скомпилировался, он привел бы unsigned long long к другому типу, который не может поддерживать его фактическое значение (в данном случае double).

Изменение abs на llabs следующим образом:

std::cout << (unsigned long long)std::llabs(9484282305798401ull);

..оба компилируют и дают точный результат. См. документацию по различным функциям abs для целочисленных типов здесь.

person stybl    schedule 31.05.2017
comment
Старые версии libstdc++ раньше принимали этот код. старые версии в основном уходят. - person Shafik Yaghmour; 31.05.2017
comment
Правда, однако ОП заявил, что они используют С++ 17, а ошибка присутствует с С++ 11. Но для полноты я включу это в свой ответ. - person stybl; 31.05.2017
comment
gcc 6.3 с использованием c++1z и -pedantic без диагностики. - person Shafik Yaghmour; 31.05.2017
comment
Небольшая проблема с использованием llabs заключается в том, что The behavior is undefined if the result cannot be represented by the return type - person Shafik Yaghmour; 31.05.2017

Ваша программа плохо сформирована. Из [c.math.abs]/29.9.2.3:

Если abs() вызывается с аргументом типа X, для которого is_­unsigned_­v<X> является true, и если X не может быть преобразовано в int путем интегрального расширения, программа построена неправильно.

Однако компиляторы должны предупредить вас об этом.

В любом случае также не имеет смысла вызывать std::abs для беззнакового типа.

person Rakete1111    schedule 31.05.2017
comment
Плохой формат требует диагностики, см. подробности в моем ответе здесь, однако это не обязательно должно быть ошибкой. С другой стороны, UB и неправильно сформированный NDR не требуют диагностики. - person Shafik Yaghmour; 31.05.2017
comment
Это одна из замечательных особенностей UB и constexpr: UB неправильно сформирован в constexpr и поэтому требует диагностики. - person Shafik Yaghmour; 31.05.2017
comment
@Shafik Да, ты совершенно прав. Я неправильно понял объяснение плохо сформированного :) - person Rakete1111; 31.05.2017

Вы можете создать свою собственную перегрузку abs, если хотите управлять неподписанными типами способом, отличным от того, что делает стандартная библиотечная функция:

#include <cmath>
#include <type_traits>

namespace my {

template <class S>
auto abs (S x) -> typename std::enable_if<std::is_signed<S>::value,
                                          decltype(std::abs(x))>::type
{
    return std::abs(x);
}

template <class U>
auto abs (U x) -> typename std::enable_if<std::is_unsigned<U>::value, U>::type
{
    return x;
}

} // namespace my

потом

std::cout << my::abs(9484282305798401ull) << '\n'              // -> 9484282305798401
          << my::abs(-3.14159) << '\n'                         // -> 3.14159
          << my::abs(std::numeric_limits<char>::min()) << '\n' // -> 128
          << my::abs(std::numeric_limits<int>::min()) << '\n'  // -> -2147483648

Обратите внимание, что std::abs продвигает chars (подписано в моей реализации), но из-за представления дополнения 2 ints не удается получить абсолютное значение INT_MIN.

person Bob__    schedule 31.05.2017
comment
Было бы еще лучше, если бы вы использовали SFINAE, чтобы типы, которые не могут преобразоваться в unsigned long long, также работали. - person Rakete1111; 31.05.2017