如何正确地将浮点数舍入到 epsilon 溢出

How to round a float to epsilon overflow-correctly

提问人:Jan Schultke 提问时间:8/23/2023 最后编辑:Jan Schultke 更新时间:8/23/2023 访问量:97

问:

为了在不允许重复的数据结构(如二叉树、堆等)中对浮点数进行排序,我想四舍五入到一定的精度级别,如下所示:float

float round_to_epsilon(x);

bool less_than(float x, float y) {
    return round_to_epsilon(x) < round_to_epsilon(y);
}

换句话说,我想将 s 量化为 的倍数,即 2-23floatstd::numeric_limits<float>::epsilon

第一个想法:乘法和地板

乍一看,您似乎可以这样做:

float round_to_epsilon(float x) {
    // note: floor or ceil must be used to avoid bias around zero
    return std::floor(x / std::numeric_limits<float>::epsilon());
}

但是,这会将每个大于 2105 的数字转换为正无穷大,这不是溢出正确的。

第二个思路:除法以放弃精度

另一种方法是乘以一个非常小的数字,故意造成下溢,并丢弃一些数字。但是,这使得非规范化数字的可能性很大,因此这种方法可能存在严重的性能问题。

问题

我怎样才能四舍五入到epsilon;或者换句话说,我怎样才能将数字降低到 2-23 的量级以下,而不会造成不必要的溢出或下溢?

我的直觉是,也许我可以除以 2 的幂,然后再次乘以得到避免非规范化数字,但我不确定如何做到这一点,或者这是否是一个好方法。

C++ 浮点 舍入

评论

1赞 Ben Voigt 8/23/2023
注意:如果,则结果应等于输入。没有达到这个早期回报的数字没有溢出的风险。abs(x) >= 1.0f
1赞 Ben Voigt 8/23/2023
另请注意,这非常适合乘以和除以 2 的幂。ldexp
1赞 Eljay 8/23/2023
Knuth 的浮点比较可能很有用:stackoverflow.com/a/253874/4641116
0赞 Ben Voigt 8/23/2023
在您的相关答案中,您有一个使用 .我想你真的想用modfstd::fmod
2赞 Eric Postpischil 8/23/2023
@JanSchultke:量化不能用于建立等价性。量化是一个不连续的函数。它将浮点数划分为多个段,并且存在以浮点格式相邻的数字,这些数字通过量化分隔(一个在一段末尾,另一个在下一段的开头)。除非这组数字没有位于这些区域的浮点值或具有涉及它们的其他特殊属性,否则基于量化的等价性将失败。

答:

0赞 njuffa 8/23/2023 #1

换句话说,我想将 s 量化为floatstd::numeric_limits<float>::epsilon

我将按照所提出的问题来回答这个问题,而不讨论这是否是所述用例的最合适方法。你的第一个想法实际上是可行的,只要有人照顾好输入域的末端。

量级足够大的数字自然会被量化为 的整数倍。小于 1 的数字将四舍五入为零。对于在量化过程中四舍五入到最接近或偶数,可以使用 ,前提是当前的四舍五入模式设置为四舍五入到最接近或偶数(这是大多数环境中的默认值)。此警告是必要的,因为与 、、 和 相反,它不使用固定的舍入模式,而是使用当前有效的舍入模式。epsepsrint()floor()ceil()trunc()round()rint()

float round_to_epsilon (float x)
{
    float eps, cutoff, r;
    eps = std::numeric_limits<float>::epsilon();
    cutoff = 0.5f * eps;
    if (fabsf (x) < cutoff) {
        r = copysignf (0.0f, x);
    } else if (fabsf (x) >= 1.0f) {
        r = x;
    } else {
        r = eps * rintf (x / eps);
    }
    return r;
}

评论

1赞 Eric Postpischil 8/23/2023
为什么要做任何花哨的事情而不是?return x - std::remainder(x, std::numeric_limits<float>::epsilon();
2赞 chux - Reinstate Monica 8/23/2023 #2

...将浮点数量化为 的倍数,即 2-23std::numeric_limits<float>::epsilon

虽然我不认为这是一个好主意,因为 OP 的目标似乎是解决 xy 问题,但可以通过以下方式完成:

float round_to_epsilon_muliple(float f) {
  // Rounding only needed for small values as larger ones 
  // are already a multiple of epsilon.
  if (fabs(f) < 1.0f) {
    static const float one_over_epsilon =
        1.0f/std::numeric_limits<float>::epsilon();
    f = round(f * one_over_epsilon); // or floor(), trunc(), etc.
    f /= one_over_epsilon;
  }
  return f;
}

@Eric Postpischil 提供了一种简化的方法:

float round_to_epsilon_muliple_EP(float f) {
  if (isfinite(f)) {
    return f - std::remainder(f, std::numeric_limits<float>::epsilon());
  }
  return f;
}

对数分布的舍入到 epsilon 的线性倍数似乎很奇怪,因为它将浮点数从浮点中取出float

也许 OP 想降低 ?float

然后考虑提取分数/幂并操纵它们。FP frexp(FP f, int *p)


float reduce_precision(float f) {
  if (isfinite(f)) {
    int p;
    f = float(f, &p);
    // Let us say we want to lop off 4 of the least significant bits.
    static const unsigned long lop = 1LU << 4;
    static const float one_over_epsilon =
        1.0f/std::numeric_limits<float>::epsilon();
    static const float one_over_scale = one_over_epsilon / lop;
    f = trunc(f * one_over_scale); // or round(), ...
    f /= one_over_scale;
    f = ldexp(f, p);
  }
  return f;
}

评论

2赞 Eric Postpischil 8/23/2023
为什么不呢?return x - std::remainder(x, std::numeric_limits<float>::epsilon();
0赞 chux - Reinstate Monica 8/23/2023
@EricPostpischil 看起来也不错(通过测试避免无穷大 - 无穷大)并且作为 NAN 令人不安。x