Неправильный результат при использовании _addcarry_u64 на MSV C 2017 - PullRequest
0 голосов
/ 28 мая 2020

Я пытаюсь написать простую библиотеку для работы с длинными целыми числами. Использование специальной оптимизации на основе встроенного c метода _addcarry_u64 64-битного компилятора дает неожиданный результат в следующем коде, если используется оптимизация. Версия отладки работает должным образом.

inline uint64_t addc(const uint64_t& value1, const uint64_t& value2, uint64_t& carry) noexcept
{
    uint64_t result;
    carry = _addcarry_u64(static_cast<uint8_t>(carry), value1, value2, &result);

    return result;
}

template<typename native_t = uintmax_t>
class long_t
{
public:
    static_assert(std::is_unsigned_v<native_t>, "unsigned long integer native type must be unsigned");

    using native_array_t = std::array<native_t, 2>;

    long_t() noexcept;
    constexpr long_t(const long_t& that) noexcept = default;
    constexpr long_t(long_t&& that) noexcept = default;
    constexpr long_t(native_array_t digits) noexcept;
    constexpr long_t(const native_t& value) noexcept;
    template<typename type_t, std::enable_if_t<std::is_unsigned_v<type_t>, int> = 0>
    constexpr long_t(type_t value) noexcept;
    template<typename type_t, std::enable_if_t<std::is_signed_v<type_t>, int> = 0>
    constexpr long_t(type_t value) noexcept;

    constexpr bool operator==(const long_t& that) const noexcept;
    constexpr long_t& operator+=(const long_t& that) noexcept;
    constexpr long_t operator+(const long_t& that) const noexcept;

    native_array_t digits;
};
template<typename native_t>
inline long_t<native_t>::long_t() noexcept
{
}

template<typename native_t>
inline constexpr long_t<native_t>::long_t(native_array_t digits) noexcept
: digits(digits)
{
}

template<typename native_t>
inline constexpr long_t<native_t>::long_t(const native_t& value) noexcept
: long_t({ value, 0})
{
}

template<typename native_t>
template<typename type_t, std::enable_if_t<std::is_unsigned_v<type_t>, int>>
inline constexpr long_t<native_t>::long_t(type_t value) noexcept
: long_t({ native_t(value), 0 })
{
}

template<typename native_t>
template<typename type_t, std::enable_if_t<std::is_signed_v<type_t>, int>>
inline constexpr long_t<native_t>::long_t(type_t value) noexcept
: long_t({ static_cast<native_t>(value), (value >= 0 ? 0 : native_t(~0)) })
{
}

template<typename native_t>
inline constexpr bool long_t<native_t>::operator==(const long_t& that) const noexcept
{
    if (digits[1] != that.digits[1])
        return false;

    if (digits[0] != that.digits[0])
        return false;


    return true;
}

template<typename native_t>
inline constexpr long_t<native_t>& long_t<native_t>::operator+=(const long_t& that) noexcept
{
    native_t carry = 0;

    digits[0] = addc(digits[0], that.digits[0], carry);
    digits[1] = addc(digits[1], that.digits[1], carry);

    return *this;
}

template<typename native_t>
inline constexpr long_t<native_t> long_t<native_t>::operator+(const long_t& that) const noexcept
{
    return long_t(*this) += that;
}

int main()
{
    const bool result = long_t<uintmax_t>(0) + -1 == -1;

    std::cout << "result:" << result;

    return 0;
}

Результат:

result:0

Замена оптимизированной версии add c привела к предсказуемому результату:

template<typename type_t, std::enable_if_t<std::is_unsigned_v<type_t>, int>>
inline constexpr type_t addc(const type_t& value1, const type_t& value2, type_t& carry) noexcept
{
    const type_t tmp = value2 + carry;
    const type_t result = value1 + tmp;
    carry = (tmp < value2) || (result < value1);

    return result;
}

Результат:

result:1

Проблема проявляется в Microsoft Visual Studio 2017 версии 15.9.23 на 64-битном компиляторе C ++ в режиме полной оптимизации.

1 Ответ

0 голосов
/ 29 мая 2020

Обычно, если вы сталкиваетесь с ошибкой компилятора, вы должны сообщить об этом и дождаться исправления, а временно переключиться на обходной путь, изменив свой код, чтобы избежать ошибки.

Для Visual Studio способ сообщения Ошибки: Справка -> Отправить отзыв -> Сообщить о проблеме ...

Конечно, ошибки, скорее всего, будут исправлены только в последней версии, которой в настоящее время является VS2019. Если эта ошибка уже исправлена ​​в VS2019, то, вероятно, нет смысла сообщать об этом.

У вас уже есть обходной путь:

Замена оптимизированного добавления c версия

Есть несколько вещей, которые можно сделать:

  1. Интерпретировать ошибку, что именно происходит. Если вам нужна помощь с этим, вам нужно будет опубликовать разборку. Не все используют компилятор, который вы используете, и не все имеют его под рукой. Есть несколько способов сделать это:
    • Скомпилировать с /FAc, скопировать вывод
    • Скопировать дизассемблированный код из отладчика (непосредственно из Visual Studio, из WinDbg или другого отладчика)
    • Воспроизведите вашу проблему на godbolt.org, опубликуйте ссылку
  2. Найдите лучший способ обхода. В идеале вы хотите продолжать использовать _addcarry_u64. Я думаю, вам следует попытаться упростить свой код, чтобы вывод _addcarry_u64 был прямым вводом последующих _addcarry_u64 без ссылок. Встроить оба вызова, использовать локальную переменную, избегать указателей или ссылок. Возможно, подойдет прямая передача возвращаемого значения _addcarry_u64 последующим _addcarry_u64 без переменных. Если это так, но вам нужны al oop, а не только два вызова, сделайте это l oop реализованным как рекурсия - есть шанс, что он будет работать лучше, чем итерация l oop.
...