【问题标题】:Properties of 80-bit extended precision computations starting from double precision arguments从双精度参数开始的 80 位扩展精度计算的属性
【发布时间】:2012-11-23 10:19:58
【问题描述】:

这里有两种插值函数的实现。参数u1 始终介于0.1. 之间。

#include <stdio.h>

double interpol_64(double u1, double u2, double u3)
{ 
  return u2 * (1.0 - u1) + u1 * u3;  
}

double interpol_80(double u1, double u2, double u3)
{ 
  return u2 * (1.0 - (long double)u1) + u1 * (long double)u3;  
}

int main()
{
  double y64,y80,u1,u2,u3;
  u1 = 0.025;
  u2 = 0.195;
  u3 = 0.195;
  y64 = interpol_64(u1, u2, u3);
  y80 = interpol_80(u1, u2, u3);
  printf("u2: %a\ny64:%a\ny80:%a\n", u2, y64, y80);
}

在具有 80 位 long doubles 的严格 IEEE 754 平台上,interpol_64() 中的所有计算均根据 IEEE 754 双精度进行,interpol_80() 中的所有计算均根据 80 位扩展精度进行。 程序打印:

u2: 0x1.8f5c28f5c28f6p-3
y64:0x1.8f5c28f5c28f5p-3
y80:0x1.8f5c28f5c28f6p-3

我对“函数返回的结果总是介于u2u3”之间的属性感兴趣。 interpol_64() 的该属性为假,如上面main() 中的值所示。

该属性是否有机会成为interpol_80() 的真实值?如果不是,反例是什么?如果我们知道u2 != u3 或者它们之间有最小距离,它会有所帮助吗?有没有一种方法可以确定中间计算的有效宽度,在该计算中属性可以保证为真?

编辑:在我尝试的所有随机值上,当中间计算在内部以扩展精度完成时,该属性保持不变。如果interpol_80() 采用long double 参数,则构建反例相对容易,但这里的问题专门针对采用double 参数的函数。这使得建立一个反例变得更加困难,如果有的话。


注意:生成 x87 指令的编译器可能会为 interpol_64()interpol_80() 生成相同的代码,但这与我的问题无关。

【问题讨论】:

  • 你确定这个程序真的使用 80 位精度吗? IIRC 现代 Intel / AMD 机器内置 128 fp 单元,随 SSE 和朋友一起提供。
  • @FUZxxl “128 位 FP 单元”表示两个双精度或 4 个单精度数字的向量。但要回答你的问题,是的,我敢肯定。大会在这里:pastebin.com/GaM20WZS
  • 内容和演示都+1

标签: c floating-point ieee-754 extended-precision


【解决方案1】:

interpol_64 中精度损失的主要来源是乘法。将两个 53 位尾数相乘产生一个 105 位或 106 位(取决于高位是否携带)尾数。这对于 80 位扩展精度值来说太大了,因此通常在 80 位版本中也会出现精度损失。准确量化它何时发生是非常困难的;最容易说的是,当舍入误差累积时会发生这种情况。请注意,添加这两个项时还有一个小的舍入步骤。

大多数人可能会使用如下函数来解决这个问题:

double interpol_64(double u1, double u2, double u3)
{ 
  return u2 + u1 * (u3 - u2);
}

但看起来您正在寻找对舍入问题的洞察力,而不是更好的实现。

【讨论】:

  • u1 是 0.025,而不是 0.25,所以设置的位数更多,尾数为 1999999999999a。
  • @R.: u1 是 0.025,而不是 0.25;它的有效位(不是尾数)设置了不止一位。问题不是如何改变计算以产生范围内的结果,问题是在什么情况下计算可能超出范围。
  • 我编辑了我的答案以更好地匹配 OP 似乎正在寻找的内容,但现在它不是一个非常令人满意的答案。
  • 问题是在争论带有FLT_EVAL_METHOD = 0 的编译器更好,因为更可预测。手头的属性很难争论这一点,因为它在那里是错误的,并且当编译器静默编译 interpol_64() 就好像它是 interpol_80() 时是正确的(例如,FLT_EVAL_METHOD = 2 编译器,例如针对 x87 的现代 GCC 或 I -don't-care 编译器,例如针对 x87 的旧 GCC)。你是对的,改变源代码不是这里的问题。另外,u2 + u1 * (u3 - u2) 选好的u2u3u1=1 会不会有同样的问题?
  • 我认为您误解了“更可预测”。在这种情况下,“可预测”一词并不意味着“给你你天真期望的答案”。这意味着结果与编译器是否需要/选择在计算期间溢出寄存器无关。 FLT_EVAL_METHOD==2 是“不可预测的”,因为您无法知道或控制编译器是否会用完浮点寄存器中的临时空间并溢出到堆栈上的标称精度存储。
【解决方案2】:

是的,interpol_80() 是安全的,我们来演示一下。

问题表明输入是 64 位浮点数

rnd64(ui) = ui

结果正好是(假设 * 和 + 是数学运算)

r = u2*(1-u1)+(u1*u3)

四舍五入为 64 位浮点数的最佳返回值为

r64 = rnd64(r)

因为我们有这些属性

u2 <= r <= u3

保证

rnd64(u2) <= rnd64(r) <= rnd64(u3)
u2 <= r64 <= u3

u1,u2,u3 转换为 80 位也是准确的。

rnd80(ui)=ui

现在,让我们假设0 &lt;= u2 &lt;= u3,那么执行不精确的浮点运算会导致最多 4 个舍入错误:

rf = rnd(rnd(u2*rnd(1-u1)) + rnd(u1*u3))

假设四舍五入到最接近的偶数,这将最多与精确值相差 2 ULP。 如果使用 64 位浮点数或 80 位浮点数进行舍入:

r - 2 ulp64(r) <= rf64 <= r + 2 ulp64(r)
r - 2 ulp80(r) <= rf80 <= r + 2 ulp80(r)

rf64 可以关闭 2 ulp 所以 interpol-64() 是不安全的,但是 rnd64( rf80 ) 呢?
我们可以说:

rnd64(r - 2 ulp80(r)) <= rnd64(rf80) <= rnd64(r + 2 ulp80(r))

由于0 &lt;= u2 &lt;= u3,那么

ulp80(u2) <= ulp80(r) <= ulp80(r3)
rnd64(u2 - 2 ulp80(u2)) <= rnd64(r - 2 ulp80(r)) <= rnd64(rf80)
rnd64(u3 + 2 ulp80(u3)) >= rnd64(r + 2 ulp80(r)) >= rnd64(rf80)

幸运的是,就像我们得到的(u2-ulp64(u2)/2 , u2+ulp64(u2)/2) 范围内的每个数字一样

rnd64(u2 - 2 ulp80(u2)) = u2
rnd64(u3 + 2 ulp80(u3)) = u3

自从ulp80(x)=ulp62(x)/2^(64-53)

我们因此得到了证明

u2 <= rnd64(rf80) <= u3

对于 u2

要研究的最后一个案例是 u2 因此,我们所做的这个断言不再成立:

r - 2 ulp64(r) <= rf64 <= r + 2 ulp64(r)

幸运的是,u2 &lt;= u2*(1-u1) &lt;= 0 &lt;= u1*u3 &lt;= u3 并在四舍五入后保留

u2 <= rnd(u2*rnd(1-u1)) <= 0 <= rnd(u1*u3) <= u3

因此,由于添加量的符号相反:

u2 <= rnd(u2*rnd(1-u1)) + rnd(u1*u3) <= u3

四舍五入后也是如此,所以我们可以再次保证

u2 <= rnd64( rf80 ) <= u3

QED

为了完整起见,我们应该注意非正规输入(逐渐下溢),但我希望您在压力测试中不要那么恶毒。我不会演示这些会发生什么......

编辑

这是一个后续,因为以下断言有点近似,并且在 0 时生成了一些 cmets

r - 2 ulp80(r) <= rf80 <= r + 2 ulp80(r)

我们可以写出以下不等式:

rnd(1-u1) <= 1
rnd(1-u1) <= 1-u1+ulp(1)/4
u2*rnd(1-u1) <= u2 <= r
u2*rnd(1-u1) <= u2*(1-u1)+u2*ulp(1)/4
u2*ulp(1) < 2*ulp(u2) <= 2*ulp(r)
u2*rnd(1-u1) < u2*(1-u1)+ulp(r)/2

对于下一个舍入操作,我们使用

ulp(u2*rnd(1-u1)) <= ulp(r)
rnd(u2*rnd(1-u1)) < u2*(1-u1)+ulp(r)/2 + ulp(u2*rnd(1-u1))/2
rnd(u2*rnd(1-u1)) < u2*(1-u1)+ulp(r)/2 + ulp(r)/2
rnd(u2*rnd(1-u1)) < u2*(1-u1)+ulp(r)

对于总和的第二部分,我们有:

u1*u3 <= r
rnd(u1*u3) <= u1*u3 + ulp(u1*u3)/2
rnd(u1*u3) <= u1*u3 + ulp(r)/2

rnd(u2*rnd(1-u1))+rnd(u1*u3) < u2*(1-u1)+u1*u3 + 3*ulp(r)/2
rnd(rnd(u2*rnd(1-u1))+rnd(u1*u3)) < r + 3*ulp(r)/2 + ulp(r+3*ulp(r)/2)/2
ulp(r+3*ulp(r)/2) <= 2*ulp(r)
rnd(rnd(u2*rnd(1-u1))+rnd(u1*u3)) < r + 5*ulp(r)/2

我没有证明最初的主张,但还没有证明...

【讨论】:

  • 你的回答帮助我更清楚地思考我自己的问题,但有些东西我还不明白。当我尝试计算表达式u2*(1-u1)+(u1*u3)的数学版本和浮点版本之间的差异时,我得到ulp(u2) + ulp(u3) + ulp(u2 + u3),第一项是u2*(1-u1)的错误,第二项是(u1*u3)的错误第三个是产品引入的错误。你的 2 ulps 的结果似乎更好,但我不确定你是如何得出它的......
  • @PascalCuoq 你是对的,这有点快......假设 0
  • 哦,关于非正规数,不用担心:当u1u2u3是双精度数时,那么u2 * (1.0 - (long double)u1) + u1 * (long double)u3的子表达式都不能成为long double 不正常的人。
  • 我不明白为什么 rf 中的错误最多为 2 ULP 的断言是正确的。 1-u1 中的错误可能高达 ULP(1)/4。然后乘以 u2,因此误差可能高达 |u2|•ULP(1)/4。这个误差被带入 rf。如果 |u2|大而rf小,误差很多ULP。
  • 但是,可以根据这一点来修正证明。如果|u2|>>|r|,则显然u2 ≤ rnd(rf80)。所以我们只需要考虑是否 rnd(rf80) ≤ u3。仅当 r 接近 u3 时才需要考虑,因此 u1 接近 1。当 u1 接近 1 时,1-u1 是精确的,因此该操作的误差为零。
猜你喜欢
  • 2011-02-27
  • 2012-11-08
  • 1970-01-01
  • 1970-01-01
  • 2016-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-27
相关资源
最近更新 更多