Анализ числа с плавающей запятой в uint64_t завершается сбоем с помощью быстрой математикиC++

Программы на C++. Форум разработчиков
Ответить
Anonymous
 Анализ числа с плавающей запятой в uint64_t завершается сбоем с помощью быстрой математики

Сообщение Anonymous »

У меня есть код, который анализирует число с плавающей запятой и возвращает беззнаковое целое число, если число можно преобразовать в беззнаковое без потери точности:
#include
#include
#include

// Note: the source number is expected to be within 32-bit UINT_MAX range, not full 64-bit range.
uint64_t read_uint(std::string_view num)
{
double d;
auto r = std::from_chars(num.data(), num.data() + num.size(), d);
if (r.ec == std::errc() && r.ptr == num.data() + num.size())
{
uint64_t u = (uint64_t)d;
if (d == u + 0.0) // conversion back to a double produced identical value
return u;
}
return ~0ull; // error, return -1
}

и ожидания:
assert(read_uint("1.0") == 1);
assert(read_uint("1.0654553e+07") == 10654553);
assert(read_uint("1.1") == ~0ull); // error
assert(read_uint("-123") == ~0ull); // error

Однако этот код с треском проваливается при использовании clang в сборках, оптимизированных для x64/x86, при настройке avx/avx2/avx512 и используя -fast-math. В частности, синтаксический анализ отрицательных чисел не удался: Assert(read_uint("-123") == ~0llu); Вместо возврата -1 он фактически возвращает -123 (преобразованный в uint64_t). Причина неудачи заключается в том, что преобразование обратно в double для проверки идентичности результата дает другой результат:
uint64_t u = (uint64_t)d;
if (d == u + 0.0) // u + 0.0 produces different result
return u;

кстати, приведение также дает разные значения при настройке avx512:
uint64_t u = (uint64_t)d; // u might not be exact when targeting avx512

Очевидно, что этот код пронизан ошибками и ошибками, и у меня есть несколько вопросов:
  • Какие проблемы с это, есть ли какие-нибудь UB? (игнорируя очевидные вещи, например, лежащий в основе uint64_t может не быть представлен двойным числом)
  • Почему uint64_t u = (uint64_t)d дает разные результаты с помощью fast-math и avx512?
  • Почему u + 0.0 дает разные результаты при использовании fast-math и avxN?
  • Каким должен быть правильный подход здесь?
  • Есть ли флаг времени компиляции для выявления таких возможных случаев в коде?
Обратите внимание, что с компилятором MS я не видел ни одной из вышеперечисленных проблем. Значения всегда точны/идентичны независимо от оптимизации, модели с плавающей запятой или целевой арки.
Кстати, это не точный код, который используется в продукте, а некоторые его фрагменты. Он анализирует числа, возвращаемые API-интерфейсами Polygon.io json. Возможно, они небрежно сбрасывали числа с помощью Python, и я видел случаи, когда значения были чем-то вроде «1.0», «1.0654553e+07» и т. д. вместо простых целых чисел. Пока что в качестве простого обходного пути я изменил приведение на uint64_t следующим образом:
uint64_t u = (uint64_t)fabs(d);

Минимальный пример: https://godbolt.org/z/cKzrK6ven (если вы удалите -O2 из командной строки clang, вывод изменится)
Обновить .
Похоже, что сначала приведение к int64_t является наиболее оптимальным решением: https://godbolt.org/z/cjjzPMPrh
uint64_t u = (uint64_t)(int64_t)d;


Подробнее здесь: https://stackoverflow.com/questions/792 ... -fast-math
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C++»