在设计8087数字协处理器时,对于语言来说,使用最高精度类型执行所有浮点数学运算是很常见的,并且在将结果分配给较低精度变量时仅将结果舍入为较低精度。例如,在原始的C标准中,序列:
float a = 16777216, b = 0.125, c = -16777216;
float d = a+b+c;
将促进a
和b
到double
,添加它们,推进c
到double
,添加它,然后存储结果四舍五入到float
。即使在许多情况下,编译器生成可以直接对type执行操作的代码会更快float
,但拥有一组仅对type操作的浮点例程和将double
其转换为/的例程更为简单。从float
,而不是有独立的组例程来处理业务float
和double
。8087是围绕该算术方法设计的,使用80位浮点类型来执行所有所有算术运算[可能选择80位是因为:
在许多16位和32位处理器上,使用64位尾数和单独的指数要比使用值在尾数和指数之间划分一个字节的值更快。
要进行精确到一个人使用的数值类型的精确度的计算是非常困难的。如果要尝试例如计算log10(x)之类的东西,则计算精确到80位类型的100ulp之内的结果要比计算精确到64位1ulp范围内的结果更容易和更快。类型,并将前一个结果四舍五入为64位精度将产生一个比后者更准确的64位值。
不幸的是,该语言的未来版本改变了浮点类型应如何工作的语义。如果语言始终如一地支持8087语义,如果函数f1(),f2()等返回type的话float
,本来很好,但是许多编译器作者还是会自己long double
为64位double类型做一个别名而不是编译器的80位类型(并且不提供其他创建80位变量的方法),而是任意求值,例如:
double f = f1()*f2() - f3()*f4();
以下列任何一种方式:
double f = (float)(f1()*f2()) - (extended_double)f3()*f4();
double f = (extended_double)f1()*f2() - (float)(f3()*f4());
double f = (float)(f1()*f2()) - (float)(f3()*f4());
double f = (extended_double)f1()*f2() - (extended_double)f3()*f4();
请注意,如果f3和f4分别返回与f1和f2相同的值,则原始表达式显然应该返回零,但是后面的许多表达式可能不会。这导致人们谴责8087的“超精密度”,尽管最后一种形式通常会优于第三种形式,并且-适当地使用扩展双精度类型的代码-很少会逊色。
在最近的几年中,英特尔通过响应语言的(不幸的恕我直言)趋势,通过设计后期处理器以偏向于将结果取整为操作数的精度,从而有利于这种行为,从而损害了代码的利益,而使用较高的代码将使代码受益中间计算的精度。