val != std::numeric_limits<double>::infinity() 或 !isinf(val) 或 isfinite(val)
Posted
技术标签:
【中文标题】val != std::numeric_limits<double>::infinity() 或 !isinf(val) 或 isfinite(val)【英文标题】:val != std::numeric_limits<double>::infinity() or !isinf(val) or isfinite(val) 【发布时间】:2017-08-13 07:34:37 【问题描述】:这可能是bike-shedding,但也许我错过了一些有趣的东西......
如果一个类将成员 val
初始化为 std::numeric_limits<double>::infinity()
并且稍后想要检查 val 是否已更改为有效的值(+/- inf 在这里无效),那么这 3 种方法的权衡是什么?我想念任何其他有趣的方法来解决这个问题。 (为了便于阅读,示例中删除了 const。)
bool IsInit() return MinX != std::numeric_limits<double>::infinity(); // a
bool IsInit() return !std::isinf(MinX); // b
bool IsInit() return std::isfinite(MinX); // c
目前代码是 C++03,但随着 C++11、C++14 和 C++17 的变化,选项将如何变化。例如对于 C++17,此代码可能只是 std::optional<double> val
。或者如果 +/-inf 在未来变得有效,那么安静的 NaN 会是一个更安全的选择吗?
当我阅读此代码的补丁时出现了这个问题:
https://github.com/OSGeo/gdal/commit/30194c640f403008625ff0c8be7aca155ac7ebe1 https://trac.osgeo.org/gdal/browser/trunk/gdal/ogr/ogr_core.h?rev=37821#L76为了方便参考:
std::numeric_limits::infinity() std::isinf std::isfinite std::numeric_limits::quiet_NaN std::optional相关类:
is it safe to use std::numeric_limits::max() as a special “flag”?【问题讨论】:
【参考方案1】:使用这些特殊值作为初始化标志只有在变量不能以其他方式获得特殊值时才有效。就数学运算而言,无穷大并不难获得,因为溢出也会得到它。所以这可能是有问题的。如果不变量只是这个浮点值不能由用户设置为这个值,那么使用安静的 NaN 会更容易使用。
就这三种方法而言,只有方法“a”与初始值完全匹配。所以如果不变量是只有那个初始值代表初始化值,这是最准确的方法。当然,这些方法都没有对保护不变量一开始做任何事情,这在我看来是一个更大的问题:不变量是否或可以有效地执行。
虽然这不是一个代码审查网站,但就 github 上OGREnvelope
类(似乎只是一个 AABB)的链接代码编辑而言,我认为应该注意以下几点:
IsInit
- 基于其代码 - 似乎意味着返回用户是否实际设置了 AABB。不是它是否处于其初始状态/值,正如IsInit
所暗示的那样(并误导我相信)。对于这个测试,我个人更喜欢 IsValid
这样的名称。
至于不变量实际上是什么,编辑本身似乎认识到,一个有效的 AABB 不应具有大于 MaxX
的 MinX
值,也不应具有大于 MaxY
的 MinY
。换句话说,它们不应该在有效的 AABB 中反转。 MinX
应始终小于或等于 MaxX
(最小和最大 Y 变量相同)。
使用反向无穷大值,使其: (a) 更容易潜在地扩大 AABB 以包围其他 AABB(并且编辑说明了我将代码行数从 14 行减少到 4 行的意思); (b) 使其能够处理比使用 max()
和 lowest()
值更广泛的有效 AABB。
MinX
、MaxX
、MinY
、MaxY
成员变量是可公开访问的这一事实意味着 IsInit
和不变量本身只是建议性的,因为没有封装来保护不变量.
在提到这些事情的更广泛的背景下,NaN 是不合适的。不变量从来不是真正的MinX != std::numeric_limits<double>::infinity()
。而IsInit
(在该代码中实现)充其量是不完整的。在将其重命名为 IsValid
之类的上下文中,逻辑上更一致的实现是:
bool IsValid() const
return !std::isnan(MinX) && !std::isnan(MinY) && MinX <= MaxX && MinY <= MaxY;
通过此实现,一个有效的 AABB 是一个 OGREnvelope
,其值都是有效数字(都不是 NaN),并且其最小值必须分别小于或等于 X 和 Y 的最大值。
【讨论】:
选项 'a' 的另一个优点是你可以在你的类中给常量一个有意义的名字(作为一个私有静态常量),这样MinX != UNINITIALISED_VAL
测试就更清楚了。
@TobySpeight 这是风格的问题,不是吗。我更喜欢namespace x template <typename T> constexpr bool is_uninitialized(T v)return v >= -(std::numeric_limits<T>::max)() && v <= (std::numeric_limits<T>::max)();
,然后是x::is_uninitialized(MinX)
@mloskot 您的检查与is_uninitialized(static_cast<signed char>(-128))
中断。它可能应该被称为is_initialized
。
@MaximEgorushkin 是的,它确实坏了。这只是命名常量的替代方案的说明。当然,对于所有可能的数字 T,它并不是一个完整且在数学上有效的实现。确实感谢名称修复(UNINITIALISED_VAL
常量名称复制和粘贴错误,我是否提到它是使用常量的反例?:-))
我认为 AABB 是“Axis-Aligned Bounding Box”。【参考方案2】:
我不希望这是一个被接受的答案,但希望它确实提供了一些有用的信息。
我使用 Godbolt 工具对上述三个提议的实现进行了彻底的比较:https://godbolt.org/z/184Zb6
基本观察结果是,在打开编译器优化的情况下,与std::numeric_limits<double>::infinity()
相比,与std::isfinite()
相比效率更高(CPU 指令和内存移动更少);也就是说,它只比!std::isinf()
更有效,因为添加了逻辑否定操作。
【讨论】:
以上是关于val != std::numeric_limits<double>::infinity() 或 !isinf(val) 或 isfinite(val)的主要内容,如果未能解决你的问题,请参考以下文章
val != std::numeric_limits<double>::infinity() 或 !isinf(val) 或 isfinite(val)