【问题标题】:What is the next normalised floating point number after(before) a normalised floating point number f?在归一化浮点数 f 之后(之前)的下一个归一化浮点数是多少?
【发布时间】:2010-11-23 03:12:00
【问题描述】:

给定一个标准化浮点数 f,f 之后/之前的下一个标准化浮点数是什么。

通过位旋转,提取尾数和指数我有:

next_normalized(double&){
      if mantissa is not all ones
          maximally denormalize while maintaining equality 
          add 1 to mantissa
          normalize
      else 
          check overflow
          set mantissa to 1  
          add (mantissa size in bits) to exponent.
      endif
 }

但不是这样做,可以用浮点运算来完成吗?

作为

std::numeric_limits<double>::epsilon() 

只是“邻域”为 1 的误差差异。-例如:

normalized(d+=std::numeric_limits<double>::epsilon()) = d for d large

似乎更多的是错误率而不是错误差异,因此我的天真直觉是

(1.+std::numeric_limits<double>::epsilon())*f //should be the next.

还有

(1.-std::numeric_limits<double>::epsilon())*f //should be the previous.

特别是我有 3 个问题,是否有人做过以下任何一项(针对 IEEE754):

1)对这个问题做了错误分析吗?

2)证明(或可以证明)对于任何 标准化双 d

    (1.+std::numeric_limits<double>::epsilon())*d != d ?

3)证明对于任何规范化双数 d 不存在双 f 使得

    d < f < (1.+std::numeric_limits<double>::epsilon())*d ? 

【问题讨论】:

    标签: floating-point


    【解决方案1】:

    正如 Robert Kern 所指出的,您需要 C 语言的 nextafter( ) 函数,或者 IEEE754 nextUp( ) 和 nextDown( ) 函数,尽管这两个函数尚未得到广泛实施。

    如果你想避免 nextafter 出于某种原因,你可以这样做:

    double next = x + scalbn(1.0, ilogb(x) - 52);
    

    这会将 2^(x - 52 的指数)添加到 x,这恰好是最后一位 (ULP) 的一个单位。

    如果您没有可用的常用 cmath 函数:

    double x = 1.0;
    uint64_t rep;
    assert(sizeof x == sizeof rep);
    memcpy(&rep, &x, sizeof x);
    rep += 1;
    memcpy(&x, &rep, sizeof x);
    

    通过对浮点值的按位表示进行运算,将 x 的有效位加一;如果下一个值在下一个 binade 中,这将带入指数,返回正确的值。如果您希望它适用于负值,则需要对其进行调整。

    【讨论】:

      【解决方案2】:

      1.0 - epsilon 不是 1.0 的前身,所以负数部分根本不起作用...
      1.0的前身是1.0-epsilon/2.0

      【讨论】:

        【解决方案3】:

        如下所述,经过一点点调查后发现,对于大小为 n 位的 intel IEEE754 格式的正浮点数,

        如果为负,反之亦然。特别是可以将 n-1 位整数解释为表示与符号无关的绝对幅度。因此,当负数时,必须减去 1 才能使负浮点数 f 之后的下一个浮点数更接近于零。

        【讨论】:

          【解决方案4】:

          3) 下的陈述是错误的。如果 d 略小于 2,则在 d 和 (1+eps) * d 之间恰好有 1 个浮点数。这是一个展示它的程序:

          #include <limits>
          #include <iostream>
          
          int main(int, char**)
          {
            using namespace std;
            double d = 1.875;
            cout.precision(18);
            cout << "d = " << d << "\n";
            double d2 = (1.+numeric_limits<double>::epsilon())*d;
            cout << "d2 = " << d2 << "\n";
            double f = d + (d2-d)/2;
            cout << "f = " << f << "\n";
          }
          

          原因是 (1+eps) * 1.875 等于 1.875 + 1.875 * eps,四舍五入为 1.875 + 2 * eps。但是1和2之间的连续浮点数的差是eps,所以1.875和1.875 + 2 * eps之间有一个浮点数,即1.875 + eps。

          我认为 2) 下的陈述是正确的。 Robert Kern 可能回答了你真正的问题。

          【讨论】:

          • 我可以补充一下,您已经证明最多存在 1 个这样的 d
          • 对于前任(predecessor(2)) 是正确的,对于任何 2 的幂也是正确的, (1+eps)*(pred(pred(2^i))) != pred( 2^i) 因为它们都有相同的尾数位模式
          • [succ(1.5)*2^k,pred(pred(2))*2^k] 中的每个数字都会有问题,因为设置了前两位,结果将舍入到上限值,除非您将舍入模式设置为零
          【解决方案5】:

          我不确定您所说的“规范化双数”是什么意思,但在大多数 C 标准中,通过 nextafter() function 获得下一个可表示双数数学库。

          【讨论】:

          • 我假设与 denormals 相反
          • nextafter 将返回下一个数字——即使它恰好是一个非规范化的数字。非规范化的情况并不罕见——如果你调用 nextafter(0.0, 1.0) 就会发生这种情况——因为 0 之后的下一个可表示数字是非规范化数字(4e-324,当 DBL_MIN 为 2e-308 时)。因此,只有当第一个数字不能为零或非规范化数字不成问题时,才应使用此解决方案。
          猜你喜欢
          • 2016-10-01
          • 2021-12-30
          • 2012-09-07
          • 2018-09-27
          • 2023-03-14
          • 1970-01-01
          • 2021-11-23
          • 1970-01-01
          • 2018-04-04
          相关资源
          最近更新 更多