【问题标题】:epsilon for various float valuesepsilon 用于各种浮点值
【发布时间】:2012-08-06 16:58:18
【问题描述】:

有一个最接近于零的FLT_MIN 常数。如何最接近some number值?

举个例子:

float nearest_to_1000 = 1000.0f + epsilon;
// epsilon must be the smallest value satisfying condition:
// nearest_to_1000 > 1000.0f

我更喜欢不使用特殊函数的数字公式。

【问题讨论】:

  • 在 IEEE754 中,将float 重新解释为uint32_t,加一并重新解释回来(模字节序)。
  • @KerrekSB 这应该是一个答案。
  • 另见this question
  • 顺便说一下,FLT_MIN 不是最接近零的浮点数。它是最小的正常浮动。非正规数更小。对于 IEEE 754,FLT_EPSILON * FLT_MIN 是最小的正浮点数。

标签: c floating-point-precision epsilon


【解决方案1】:

C 在<math.h> 标头中为此提供了一个函数。 nextafterf(x, INFINITY)x 之后的下一个可表示值,朝向INFINITY

但是,如果您想自己动手:

假设 IEEE 754,以下返回您寻找的 epsilon,用于单精度(浮点)。请参阅底部有关使用库例程的说明。

#include <float.h>
#include <math.h>


/*  Return the ULP of q.

    This was inspired by Algorithm 3.5 in Siegfried M. Rump, Takeshi Ogita, and
    Shin'ichi Oishi, "Accurate Floating-Point Summation", _Technical Report
    05.12_, Faculty for Information and Communication Sciences, Hamburg
    University of Technology, November 13, 2005.
*/
float ULP(float q)
{
    // SmallestPositive is the smallest positive floating-point number.
    static const float SmallestPositive = FLT_EPSILON * FLT_MIN;

    /*  Scale is .75 ULP, so multiplying it by any significand in [1, 2) yields
        something in [.75 ULP, 1.5 ULP) (even with rounding).
    */
    static const float Scale = 0.75f * FLT_EPSILON;

    q = fabsf(q);

    /*  In fmaf(q, -Scale, q), we subtract q*Scale from q, and q*Scale is
        something more than .5 ULP but less than 1.5 ULP.  That must produce q
        - 1 ULP.  Then we subtract that from q, so we get 1 ULP.

        The significand 1 is of particular interest.  We subtract .75 ULP from
        q, which is midway between the greatest two floating-point numbers less
        than q.  Since we round to even, the lesser one is selected, which is
        less than q by 1 ULP of q, although 2 ULP of itself.
    */
    return fmaxf(SmallestPositive, q - fmaf(q, -Scale, q));
}

下面的返回值在它传递的值之后以浮点数表示的下一个值(将-0和+0视为相同)。

#include <float.h>
#include <math.h>


/*  Return the next floating-point value after the finite value q.

    This was inspired by Algorithm 3.5 in Siegfried M. Rump, Takeshi Ogita, and
    Shin'ichi Oishi, "Accurate Floating-Point Summation", _Technical Report
    05.12_, Faculty for Information and Communication Sciences, Hamburg
    University of Technology, November 13, 2005.
*/
float NextAfterf(float q)
{
    /*  Scale is .625 ULP, so multiplying it by any significand in [1, 2)
        yields something in [.625 ULP, 1.25 ULP].
    */
    static const float Scale = 0.625f * FLT_EPSILON;

    /*  Either of the following may be used, according to preference and
        performance characteristics.  In either case, use a fused multiply-add
        (fmaf) to add to q a number that is in [.625 ULP, 1.25 ULP].  When this
        is rounded to the floating-point format, it must produce the next
        number after q.
    */
#if 0
    // SmallestPositive is the smallest positive floating-point number.
    static const float SmallestPositive = FLT_EPSILON * FLT_MIN;

    if (fabsf(q) < 2*FLT_MIN)
        return q + SmallestPositive;

    return fmaf(fabsf(q), Scale, q);
#else
    return fmaf(fmaxf(fabsf(q), FLT_MIN), Scale, q);
#endif
}

使用库例程,但fmaxf(其参数的最大值)和fabsf(绝对值)很容易替换。 fmaf 应该编译为具有融合乘加的架构上的硬件指令。否则,此用法中的fmaf(a, b, c) 可以替换为(double) a * b + c。 (IEEE-754 binary64 有足够的范围和精度来替换fmafdouble 的其他选择可能没有。)

融合乘法的另一种替代方法是为q * Scale 不正常的情况添加一些测试并单独处理这些情况。对于其他情况,可以使用普通的*+运算符分别进行乘法和加法。

【讨论】:

  • 0.750.625是什么意思?
  • q 的有效位数在 1 和 2 之间(不包括 2)。如果有效位正好是 1,那么 qFLT_EPSILON 将恰好是一个 ULP(q 的有效位中最低有效位的值,给定它的指数),所以 q+qFLT_EPSILON 正好是下一个可表示的值。但是,假设有效数字更接近 2。那么 qFLT_EPSILON 接近 2 ULP,并且 q+qFLT_EPSILON 非常接近第二个可表示值,而不是下一个,并且四舍五入会使最终结果是第二个下一个值。但是……
  • q*.625*FLT_EPSILON 介于 0.625 ULP(当 q 的有效数字接近 1 时)和 1.25 ULP(当 q 的有效数字接近 2 时)。所以 q+q*.625*FLT_EPSILON 总是比 q 或 q + 2 ULP 更接近下一个可表示值 (q + 1 ULP)。所以四舍五入使得结果正好是 q + 1 ULP,这正是我们想要的。
  • 另一个微妙之处是当 q 是负数并且恰好是 2 的幂时。那么在 INFINITY 方向上的下一个可表示数字不是正常的 q + 1 ULP,而是 q + 1/2 ULP,因为下一个可表示的数字具有较低的指数,因此其有效数字中的位与 q 有效数字中的相同位相比具有一半的值。在这种情况下,fabs(q)*.625*FLT_EPSILON 是 .625 ULP,所以 q + fabs(q)*.625*FLT_EPSILON 接近 q + 1/2 ULP,这是一个可表示的数字,也是我们想要的数字.
  • 第一个例程中的 .75 是因为该例程只需要返回 ULP;它不需要处理负q的二次幂之间的步进问题。所以它的 0.75 到 1.5 的范围很好。但是对于 NextAfter 例程,这会错误地舍入,因为 q+fabs(q)*.75*FLT_EPSILON 是 q + .75 ULP,它同样接近两个可表示的数字 q + .5 ULP 和 q + 1 ULP,并且IEEE 754 舍入规则选择 q + 1 ULP(因为它的低位是偶数)。所以 NextAfter 使用 .625 来确保 q + .5 ULP 更接近。
猜你喜欢
  • 2018-03-21
  • 1970-01-01
  • 1970-01-01
  • 2021-10-16
  • 2021-10-27
  • 1970-01-01
  • 2012-06-24
  • 1970-01-01
相关资源
最近更新 更多