准确预测任意浮点格式之间的转换舍入误差

时间:2015-04-15 11:09:07

标签: c algorithm math floating-point floating-point-conversion

假设您有一个float64_t数字,其中包含一个任意值,并且您想知道所述数字是否可以安全地降为float32_t,并且限制所产生的舍入误差不得超过给定的epsilon。

可能的实现可能如下所示:

float64_t before = 1.234567890123456789;
float64_t epsilon = 0.000000001;
float32_t mid = (float32_t)before;  // 1.2345678806304931640625
double after = (float64_t)mid; // 1.2345678806304931640625
double error = fabs(before - after); // 0.000000009492963526369635474111
bool success = error <= epsilon; // false

为了让事情变得更有趣,让我们假设您不应该对这两种类型之间的值进行任何实际类型转换(如上所示)。

只是为了提升一个档次:让我们假设您转向float32_t,但浮点类型 任意精度(8位,16位,32位,甚至可能是24位)由其位数和指数长度指定(并遵循IEEE 754的约定,例如舍入为偶数)。

所以我正在寻找的是一种类似于此的通用算法:

float64_t value = 1.234567890123456789;
float64_t epsilon = 0.000000001;
int bits = 16;
int exponent = 5;
bool success = here_be_dragons(value, epsilon, bits, exponent); // false

举一个例子,将64位数字1.234567890123456789向下压缩到较低的精度会导致以下舍入误差:

 8bit: 0.015432109876543309567864525889
16bit: 0.000192890123456690432135474111
24bit: 0.000005474134355809567864525889
32bit: 0.000000009492963526369635474111
40bit: 0.000000000179737780214850317861
48bit: 0.000000000001476818667356383230
56bit: 0.000000000000001110223024625157

众所周知:

  1. 所讨论的两种精度类型的规范(精度低于另一种):
    • 总长度(以位为单位)(浮动为32,例如)
    • 指数长度(以位为单位)(浮点数为8,例如)
  2. 每种类型的minmax值(可以从上面得出)。
  3. 正正常值的数量(不包括零)(((2^exponent) - 2) * (2^mantissa)
  4. 指数的bias(2^(exponent - 1)) - 1
  5. 实际value(以给定的更高精度类型提供)。
  6. 为了被视为成功,允许向下转换的误差阈值epsilon(也在给定的更高精度类型中提供)。
  7. (预期误差的近似值可能已足够,具体取决于其准确度和偏差因子。显然,确切的计算是合适的。)

    无需涵盖的案例(因为它们可以单独解决):

    • 如果输入值为非正常值(次正常,无穷大,纳米,零......),则答案应定义为true
    • 如果输入值在给定类型的较低精度的已知边界(+ - 给定的epsilon)之外,那么答案应定义为false。< / LI>

    到目前为止我已经想到了什么:

    我们知道给定浮点类型中正正常值(不包括零)的计数,并且我们知道值空间对称积极一。

    我们也知道离散值在值范围内(远离零)的分布遵循指数函数及其相对epsilon 相关的阶跃函数

    应该可以计算给定浮点类型实际值nth 离散正态值 >会落到(通过某种对数投影,还是什么?),不应该吗?鉴于此n,应该能够步骤函数计算相应值的epsilon ,并将其与指定的最大错误进行比较,不是吗?

    我觉得这实际上足以计算(或至少准确估计)预期的铸造误差。我根本不知道如何把这些东西放在一起。

    你会怎么做? (实际代码的奖励积分:P)

    Ps:提供更多上下文:我正在进行var_float实现,以便找出给定值的最小无损(或给定epsilon内的有损)可转换表示我目前正在利用上述天真的往返逻辑执行二进制搜索,以找到合适的大小。它有效,但缺乏效率和凉爽部门。即使它绝不是性能瓶颈(yada yada早熟优化yada yada),我很好奇是否可以找到更加数学基础和优雅的解决方案。 ;)

2 个答案:

答案 0 :(得分:4)

以下内容可能有效:

double isbad(double x, double releps) {
  double y = x * (1 + 0x1.0p29);
  double z = y-x-y+x;
  return !(fabs(z/x) < releps);
}

这使用了一个技巧(由于Dekker,我相信)将浮点数分成“大半”和“小半”,它们与原始数字完全相加。我希望“大半”有23位而“小半”有其余的,所以我用常数1 + 2 ^(52-23)分割。

注意事项:您需要通过检查上限和下限来处理更有限的指数范围。次正规(特别是小型但不是大型的结果是低于正常的情况)需要不同的特殊处理。我写了!(fabs(z/x) < releps)而不是fabs(z/x <= releps因为我希望NaN符合“坏”的条件。 releps是该变量的错误名称,因为阈值实际上比使用舍入到最近时指定的数字大半个ulp。

答案 1 :(得分:2)

向下转换相当于将尾数的最低有效位设置为零。

因此,对于给定的浮点数,只需提取尾数的最低有效位(宽度取决于向下转换类型)并使用当前指数进行缩放。这应该(非常精确地)是在向下转换中发生的“舍入误差”。

<小时/> 的修改

如上述评论所述,上述情况仅适用于所有案例的50%。 (当向下转向导致向下舍入时)。如果向下转向导致四舍五入,稍微修改的方法将有所帮助:

(极端/极端情况:示例:下调类型的五位尾数)

Rounding down: 0x1.00007fff -> 0x1.0000 
               -> Err == 0x0.00007fff

Rounding up:   0x1.00008000 -> 0x1.0001 -> Err == 0x1.00010000 - 0x1.00008000
               -> Err == 0x0.00008000