将单精度除法实现为双精度乘法

时间:2013-10-25 12:32:26

标签: c floating-point c99 compiler-optimization ieee-754

问题

对于实现精确IEEE 754算法的C99编译器,f类型的divisorfloat的值是否存在,f / divisor != (float)(f * (1.0 / divisor))

编辑:通过“实现精确的IEEE 754算术”,我指的是一个将FLT_EVAL_METHOD正确定义为0的编译器。

上下文

提供符合IEEE 754标准的浮点的AC编译器只能通过单次精度乘以逆来用常量替换单精度除法,如果所述逆本身可以表示为float。< / p>

在实践中,这只发生在两个人的权力上。因此,程序员Alex可能会确信f / 2.0f将被编译为f * 0.5f,但如果Alex可以接受0.10f而不是除以10, Alex应该通过在程序中编写乘法或使用编译器选项(如GCC的-ffast-math)来表达它。

这个问题是关于将单精度除法转换为双精度乘法。它总能产生正确的圆形结果吗?它是否有可能更便宜,因此是编译器可能做出的优化(即使没有-ffast-math)?

我已将(float)(f * 0.10)f / 10.0ff的所有单精度值进行了比较,介于1和2之间,未找到任何反例。这应该涵盖正常float s的所有分区,产生正常结果。

然后我用以下程序将测试推广到所有除数:

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

int main(void){
  for (float divisor = 1.0; divisor != 2.0; divisor = nextafterf(divisor, 2.0))
    {
      double factor = 1.0 / divisor; // double-precision inverse
      for (float f = 1.0; f != 2.0; f = nextafterf(f, 2.0))
        {
          float cr = f / divisor;
          float opt = f * factor; // double-precision multiplication
          if (cr != opt)
            printf("For divisor=%a, f=%a, f/divisor=%a but (float)(f*factor)=%a\n",
                   divisor, f, cr, opt);
        }
    }
}

搜索空间足够大,使其变得有趣(2 46 )。该程序目前正在运行。在完成之前,有人能告诉我它是否会打印出某些东西,或许是为什么或为什么不解释?

3 个答案:

答案 0 :(得分:6)

假设圆形连接到均匀舍入模式,您的程序将不会打印任何内容。论证的实质如下:

我们假设fdivisor都在1.02.0之间。对于f = a / 2^23范围内的某些整数divisor = b / 2^23a,我需要b[2^23, 2^24)。案例divisor = 1.0并不重要,因此我们可以进一步假设b > 2^23

(float)(f * (1.0 / divisor))给出错误结果的唯一方法是精确值f / divisor如此接近中间情况(即两个单精度浮点数之间的数字正好)表达式f * (1.0 / divisor)中累积的错误将我们从真实值推到了那个中间案例的另一面

但这不可能发生。为简单起见,我们首先假设f >= divisor,以便确切的商在[1.0, 2.0)。现在,[1.0, 2.0)区间内单精度的任何中间情况都具有c / 2^24形式,用于c的奇数整数2^24 < c < 2^25f / divisor的确切值为a / b,因此差异f / divisor - c / 2^24的绝对值下限为1 / (2^24 b),因此至少为1 / 2^48(自{ {1}})。因此,我们距离任何中途情况都超过b < 2^24双精度ulps,并且应该很容易证明双精度计算中的误差永远不会超过16 ulps。 (我没有完成算术运算,但我猜想在误差上显示3 ulps的上限很容易。)

所以16不能足够接近中途案例来制造问题。请注意,f / divisor不能 是一个完全中途的情况:由于f / divisor是奇数,cc是相对素数,所以如果2^24c / 2^24 = a / b的倍数,我们b的唯一方法就是2^24。但是b(2^23, 2^24)范围内,所以这是不可能的。

f < divisor相似的情况:中途案例的格式为c / 2^25,类似的论证表明abs(f / divisor - c / 2^25)大于1 / 2^49,这再次给了我们使用16双精度ulps的余量。

答案 1 :(得分:2)

如果可以使用非默认舍入模式,那肯定是不可能的。例如,在用3.0f / 3.0f替换3.0f * C时,小于精确倒数的C值会在向下或向零舍入模式中产生错误的结果,而值C 1}}大于精确倒数会产生向上舍入模式的错误结果。

如果您限制为默认舍入模式,我不太清楚您正在寻找的是什么。如果我拿出任何东西,我会考虑并修改这个答案。

答案 2 :(得分:1)

随机搜索产生了一个例子。

看起来当结果是“非正规/次正规”数时,不等式是可能的。但是,也许我的平台不符合IEEE 754标准?

f        0x1.7cbff8p-25 
divisor -0x1.839p+116
q       -0x1.f8p-142
q2      -0x1.f6p-142

int MyIsFinite(float f) {
  union {
    float f;
    unsigned char uc[sizeof (float)];
    unsigned long ul;
  } x;
  x.f = f;
  return (x.ul & 0x7F800000L) != 0x7F800000L;
}

float floatRandom() {
  union {
    float f;
    unsigned char uc[sizeof (float)];
  } x;
  do {
    size_t i;
    for (i=0; i<sizeof(x.uc); i++) x.uc[i] = rand();
  } while (!MyIsFinite(x.f));
  return x.f;
}

void testPC() {
  for (;;) {
    volatile float f, divisor, q, qd;
    do {
      f = floatRandom();
      divisor = floatRandom();
      q = f / divisor;
    } while (!MyIsFinite(q));
    qd = (float) (f * (1.0 / divisor));
    if (qd != q) {
      printf("%a %a %a %a\n", f, divisor, q, qd);
      return;
    }
  }

}

Eclipse PC版:Juno Service Release 2 建造ID:20130225-0426