C++具有std::nextafter()
,它返回给定浮点值f之后的下一个可表示值。在我的情况下,我想在低尾数位中允许n位斜率,因此3位斜率将要求在某个给定值f之后获得第8个下一个值。我可以调用nextafter()
八次,但是有没有更好的方法来处理呢?
对于大多数值,您可以通过将FP值强制转换为uint_64
,添加容差(斜率3位的1<<3
),然后通过IEEE 754的布局将其强制转换回double
。但是,这依赖于IEEE 754浮点数(一个很好的假设,但也不是坚如磐石)。
(对于背景,我想用它来提升射线表面相交点,由于FP不精确性,射线相交点有时位于表面内部。熟悉稳健浮点的人员将理解为什么epsilon
是一个糟糕的解决方案。)
最佳答案
一个幼稚的方法可能是将一个值和下一个可表示的浮点数之间的距离乘以8,而不是调用8次std::nextafter
double advance_float(double x, int d)
{
double step = std::copysign((std::nextafter(x, x + d) - x) * d, d);
return x + step;
}
Here有一些测试,但是要由您确定这是否适合您的用例。
编辑
如Steve Hollash所述,
x
可能太大,以至于x + d == d
。 Daniel Jour建议利用frexp
(和ldexp
),但是在以下尝试中,我将使用另一种方法来确定方向。double advance_float(double x, int d)
{
const double to = std::copysign(std::numeric_limits<double>::infinity(), d);
const double next = std::nextafter(x, to);
return x + std::copysign(d * (next - x), d);
}
请注意,它假设使用
std::numeric_limits<double>::has_infinity == true
,否则必须使用::lowest()
和::max()
。这些是一些结果
x d上一个x下一个
-------------------------------------------------- ----------------------------------------
1 1 0x1.fffffffffffffp-1 0x1p + 0 0x1.0000000000001p + 0
1 8 0x1.ffffffffffff8p-1 0x1p + 0 0x1.0000000000008p + 0
3.14159 8 0x1.921fb54442d1p + 1 0x1.921fb54442d18p + 1 0x1.921fb54442d2p + 1
100.01 8 0x1.900a3d70a3d69p + 6 0x1.900a3d70a3d71p + 6 0x1.900a3d70a3d79p + 6
-100.01 8 -0x1.900a3d70a3d79p + 6 -0x1.900a3d70a3d71p + 6 -0x1.900a3d70a3d69p + 6
1e + 67 8 0x1.7bd29d1c87a11p + 222 0x1.7bd29d1c87a19p + 222 0x1.7bd29d1c87a21p + 222
1e-59 8 0x1.011c2eaabe7dp-196 0x1.011c2eaabe7d8p-196 0x1.011c2eaabe7ep-196
0 8 -0x0.0000000000008p-1022 0x0p + 0 0x0.0000000000008p-1022
4.94066e-324 8 -0x0.0000000000007p-1022 0x0.0000000000001p-1022 0x0.0000000000009p-1022