对于实现精确IEEE 754算法的C99编译器,f
类型的divisor
,float
的值是否存在,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.0f
与f
的所有单精度值进行了比较,介于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 )。该程序目前正在运行。在完成之前,有人能告诉我它是否会打印出某些东西,或许是为什么或为什么不解释?
答案 0 :(得分:6)
假设圆形连接到均匀舍入模式,您的程序将不会打印任何内容。论证的实质如下:
我们假设f
和divisor
都在1.0
和2.0
之间。对于f = a / 2^23
范围内的某些整数divisor = b / 2^23
和a
,我需要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^25
。 f / 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
是奇数,c
和c
是相对素数,所以如果2^24
是c / 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