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

C 精确预测任意浮点格式之间转换的舍入误差,c,algorithm,math,floating-point,floating-point-conversion,C,Algorithm,Math,Floating Point,Floating Point Conversion,假设您有一个具有任意值的float64_t数字,并且您希望确定所述数字是否可以安全地向下转换为float32_t,并且所产生的舍入误差不得超过给定的ε 可能的实现如下所示: float64_t before = 1.234567890123456789; float64_t epsilon = 0.000000001; float32_t mid = (float32_t)before; // 1.2345678806304931640625 double after = (float64_t

假设您有一个具有任意值的
float64_t
数字,并且您希望确定所述数字是否可以安全地向下转换为
float32_t
,并且所产生的舍入误差不得超过给定的ε

可能的实现如下所示:

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
举个例子,将64位数字
1.234567890123456789
向下转换到较低的精度会导致以下舍入错误:

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

众所周知:

  • 有关两种精度类型的规格(一种精度低于另一种):
    • 总长度(以位为单位)(例如,浮点数为32)
    • 指数长度(以位为单位)(例如,浮点数为8)
  • 每种类型的
    min
    max
    值(可以从上面导出)
  • 正正常值的数目(不包括零)(
    ((2^指数)-2)*(2^尾数)
  • 指数的
    偏差
    (2^(指数-1))-1
  • 实际
    (在给定的更高精度类型中提供)
  • 错误阈值
    epsilon
    允许向下转换在范围内,以便将其视为成功的(也在给定的更高精度类型中提供)
  • (根据其精度和偏差系数,对预期误差的近似值可能就足够了。但显然,更倾向于精确计算。)

    不需要涵盖的情况(因为它们可以单独解决):

    • 如果输入值为任何非正常值(低于正常值、无穷大、nan、零……),则应将答案定义为
    • 如果输入值落在给定类型的较低精度的已知边界(+-给定ε)之外,则应将答案定义为
      false

    到目前为止,我的想法是:

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

    我们还知道,离散值在值范围内(远离零)的分布遵循一个指数函数及其相对εa相关阶跃函数

    应该可以计算给定浮点类型的正常值范围内的给定实值将落在上的第n个
    ?鉴于此
    n
    应能根据其阶跃函数计算相应值的epsilon,并将其与指定的最大误差进行比较,是否

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

    你将如何处理这个问题?(实际代码的加分:P)


    Ps:为了提供更多的上下文:我正在研究一个
    var\u float
    实现,为了找出给定值的最小无损(或给定epsilon内的有损)可转换表示,我目前正在使用上述简单的往返逻辑执行二进制搜索,以找到合适的大小。它可以工作,但缺乏效率和冷静部门。尽管这绝不是一个性能瓶颈(yada-yada过早优化yada-yada),但我很好奇人们是否能找到一个更加数学化和优雅的解决方案

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

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

    如评论所述,上述情况仅适用于50%的情况。(向下投射导致向下舍入时)。在向下投射导致向上取整的情况下,稍微修改的方法将有助于:

    (极端/极端情况:示例:向下浇铸类型中尾数的五位数)


    类似以下的方法可能会起作用:

    double isbad(double x, double releps) {
      double y = x * (1 + 0x1.0p29);
      double z = y-x-y+x;
      return !(fabs(z/x) < releps);
    }
    
    double是坏的(double x,double releps){
    双y=x*(1+0x1.0p29);
    双z=y-x-y+x;
    返回!(晶圆厂(z/x)
    这使用了一个技巧(我相信是Dekker的功劳)将一个浮点数拆分为“大一半”和“小一半”,其总和正好等于原始数。我希望“大的一半”有23位,“小的一半”有其余的,所以我使用常量1+2^(52-23)进行拆分


    注意事项:您需要通过检查上界和下界来处理更有限的指数范围。次正常(特别是小类型的结果是次正常的,而不是大类型的结果)需要不同的特殊处理。我写了
    !(fabs(z/x)
    而不是
    fabs(z/x)你能不能看看8字节双精度码的最后一位是否有适当数量为零?还要检查指数。较小格式的范围较小。这不起作用——我认为“0.328125”可以无损地转换为
    float8_t
    0 001 0101
    )不留下尾随的零。范围问题由我的“不需要覆盖”规则涵盖。;)并且在更长的格式中,
    21/64=(1.0101)_2*2^(-2)
    的尾数以零继续。这正是压缩它所需要的条件
    Rounding down: 0x1.00007fff -> 0x1.0000 
                   -> Err == 0x0.00007fff
    
    Rounding up:   0x1.00008000 -> 0x1.0001 -> Err == 0x1.00010000 - 0x1.00008000
                   -> Err == 0x0.00008000
    
    double isbad(double x, double releps) {
      double y = x * (1 + 0x1.0p29);
      double z = y-x-y+x;
      return !(fabs(z/x) < releps);
    }