我在尝试将一段代码放入模板函数中时遇到了问题。该代码基本上执行钳位。下面是代码的可重现版本。
#include<iostream>
int main(){
constexpr float lower_value{0.0F};
constexpr float upper_value{4294967295.0F};
float value{4294967295.0F};
uint32_t clampedValue{0};
if (value <= lower_value) {
clampedValue = static_cast<decltype(clampedValue)>(lower_value);
} else if (value >= upper_value) {
clampedValue = static_cast<decltype(clampedValue)>( upper_value);
} else {
clampedValue = static_cast<decltype(clampedValue)>(std::round(value));
}
std::cout << clampedValue << std::endl;
return 0;
}
只要
lower_value
和 upper_value
是 const
或 constexpr
,就可以正常工作。否则它输出 0
。
当我运行以下几行时,它分别给出了
0
、4294967295
和 4294967295
。 0
的原因是浮点转换错误,因为4294967295
增加了1
到4294967296
以适合23位尾数,但这超出了Uint32的范围,并且发生了回绕,在之后给出了0
static_cast<uint32_t>
。但是为什么使用 const float
或 constexpr float
时会得到正确的结果?
float value{4294967295.0F};
constexpr float valueConstexpr{4294967295.0F};
const float valueConst{4294967295.0F};
std::cout << static_cast<uint32_t>(value) << std::endl;
std::cout << static_cast<uint32_t>(valueConstexpr) << std::endl;
std::cout << static_cast<uint32_t>(valueConst) << std::endl;
上述
if else
程序的 main
逻辑被放入一个模板函数中,其形状如下。
#include <iostream>
template <typename A, typename B, typename C, typename D>
constexpr auto process_value(const B& inVal, const C& lower_bound, const D& upper_bound) {
A outVal;
if (inVal <= static_cast<B>(lower_bound)){
outVal = static_cast<A>(lower_bound);
} else if (inVal >= static_cast<B>(upper_bound)){
outVal = static_cast<A>(upper_bound);
} else {
outVal = static_cast<A>(std::round(inVal));
}
return outVal;
}
int main(){
constexpr float lower_value{0.0F};
constexpr float upper_value{4294967295.0F};
float value{4294967295.0F};
uint32_t clampedValue{0};
clampedValue = process_value<decltype(clampedValue), float, float, float>(value, lower_value, upper_value);
std::cout << clampedValue << std::endl;
return 0;
}
但这也给出了
0
作为输出而不是 4294967295
。
我希望两个版本都能给出相同的输出。有人可以帮助我如何修复模板函数,使其与原始代码中的完全一样吗?
假设
float
是 IEEE 754 表示。
std::uint32_t
可以保真度保存的最大 float
是 4294967040.0f。 将 float
转换为 std::uint32_t
是明确定义的行为。
浮点数可以保存的下一个值是4294967296.0f,它超出了
std::uint32_t
的范围。 将 float
值转换到 std::uint32_t
范围之外是 未定义行为,应该避免。
为什么?因为
float
具有 24 位精度有效数(有时称为尾数,尽管用词不当)。
最高的单位增量
float
值为16777216.0f,此后存在可表达性的“单位值差距”。 有时这也是需要注意的一点信息。
要修复原始发布的代码,请更改 upper_value:
constexpr float upper_value{ 4294967240.0f };
static_cast<float>(std::numeric_limits<std::uint32_t>::max())
是 4294967296,比 4294967295 大 1,并且不再适合 32 位无符号整数。 Godbolt 演示
原因是 float 的尾数只有 23 位,因此它无法准确表示适合
uint32_t
的所有整数。因此它会圆形。例如,4294967167 将四舍五入为 4294967040,而 4294967168 已四舍五入为 4294967296。
因此,如果您将限制转换为浮动,然后转换为夹紧,这将会失败。
您可以使用
std::nextafter(static_cast<float>(std::numeric_limits<std::uint32_t>::max()), -std::numeric_limits<float>::infinity())
获取仍然适合两种数据类型的浮点值之前的浮点值,即 4294967040。任何大于该值的 float
值都会自动四舍五入为不适合的值在uint32_t
。