我有一些代码可以解析浮点数并返回一个无符号整数(如果该数字可以转换为无符号而不丢失精度):
#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
显然,这段代码充满了错误和陷阱,我有一些问题:
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 将会改变)
是的,您的代码有未定义的行为。
浮点类型的纯右值可以转换为整数类型的纯右值。转换截断; 即小数部分被丢弃。如果无法表示截断值,则行为未定义 在目的地类型中。
截断值为-123,无法在目标类型中表示
uint64_t
(它只能表示非负值),因此这是未定义的行为。
请注意,无论您使用 C 风格转换
(uint64_t)d
还是 static_cast<uint64_t>(d)
,这都适用。
确实,将值为 -123 的 integer 类型的值转换为
uint64_t
会产生明确定义的结果(即 2^64 - 123 = 18446744073709551493)。 但这不适用于转换浮点类型的值。