使用快速数学将浮点数解析为 uint64_t 失败

问题描述 投票:0回答:1

我有一些代码可以解析浮点数并返回一个无符号整数(如果该数字可以转换为无符号而不丢失精度):

#include <charconv>
#include <string_view>
#include <stdint.h>

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

但是,当以

avx
/
avx2
/
avx512
为目标并使用
-fast-math
时,此代码在 x64/x86 优化构建上惨遭失败。具体来说,解析负数失败:
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 编译器,我没有看到上述任何问题。无论优化、浮点模型或目标架构如何,值始终准确/相同。

顺便说明一下,这并不是产品中使用的确切代码,而是其中的一些摘录。它解析由 Polygon.io json API 返回的数字。也许,他们不小心使用 python 转储了数字,我见过一些情况,其中值是“1.0”、“1.0654553e+07”等,而不是普通整数。到目前为止,作为一个简单的解决方法,我将转换为 uint64_t 更改为:

uint64_t u = (uint64_t)fabs(d);

最小示例:https://godbolt.org/z/cKzrK6ven(如果从 clang cmdline 输出中删除 -O2 将会改变)

c++ floating-point rounding polygon.io
1个回答
0
投票

是的,您的代码有未定义的行为。

N4928 转换 fpint p1

浮点类型的纯右值可以转换为整数类型的纯右值。转换截断; 即小数部分被丢弃。如果无法表示截断值,则行为未定义 在目的地类型中。

截断值为-123,无法在目标类型中表示

uint64_t
(它只能表示非负值),因此这是未定义的行为。

请注意,无论您使用 C 风格转换

(uint64_t)d
还是
static_cast<uint64_t>(d)
,这都适用。

确实,将值为 -123 的 integer 类型的值转换为

uint64_t
会产生明确定义的结果(即 2^64 - 123 = 18446744073709551493)。 但这不适用于转换浮点类型的值。

© www.soinside.com 2019 - 2024. All rights reserved.