我有一些代码可以解析浮点数,如果该数字可以在不丢失精度的情况下转换为无符号整数,则返回无符号整数:
#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
并使用时,此代码在 x64/x86 优化版本上使用 clang 时会失败-fast-math
。具体来说,解析负数会失败:assert(read_uint("-123") == ~0llu);
它实际上返回 -123(转换为 uint64_t),而不是返回 -1。失败的原因是因为转换回 以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 可能无法用 double 表示)
- 为什么它会
uint64_t u = (uint64_t)d
产生与 fast-math 和 avx512 不同的结果? - 为什么它会
u + 0.0
产生与 fast-math 和 avxN 不同的结果? - 这里正确的方法应该是什么?
- 是否存在编译时标志来识别代码中的此类可能情况?
请注意,使用 MS 编译器时我没有看到上述任何问题。无论优化、浮点模型或目标架构如何,值始终是精确/相同的。
顺便说一句,这不是 prod 中使用的确切代码,而是从中摘录的一些代码。它解析由 poly.io json API 返回的数字。也许,他们粗心地使用 python 转储数字,我见过一些情况,其中值是“1.0”、“1.0654553e+07”等,而不是普通整数。到目前为止,作为一种简单的解决方法,我将 uint64_t 的转换更改为:
uint64_t u = (uint64_t)fabs(d);
最小示例:https://godbolt.org/z/cKzrK6ven(如果从 clang 中删除 -O2,cmdline 输出将会改变)
是的,您的代码有未定义的行为。
N4928 转换 fpint p1
截断的值为 -123,无法在目标类型中表示
uint64_t
(它只能表示非负值),因此这是未定义的行为。请注意,无论您使用 C 风格转换
(uint64_t)d
还是 ,这都适用static_cast<uint64_t>(d)
。确实,将值为 -123的整数
uint64_t
类型值转换为 会产生明确的结果(即 2^64 - 123 = 18446744073709551493)。但是,转换浮点类型的值时,这并不适用。