代码之家  ›  专栏  ›  技术社区  ›  pmor

如何计算转换规范%.*f的精度以保持浮点值的精度?

  •  1
  • pmor  · 技术社区  · 2 年前

    注:此问题源自 this 答复

    如何计算转换规范的精度 %.*f 以保持浮点值的精度?

    注:这里的“保持精度”是指在打印值被读回后(例如 strtod 或通过 scanf )结果值等于(NaNs除外)原始值(用于带有转换规范的打印 %.*f )。

    2 回复  |  直到 2 年前
        1
  •  1
  •   chux    2 年前

    往返 double (二进制浮点)值转换为十进制文本 "%.*f" 并且恢复到相同的值需要高达 DBL_DECIMAL_DIG (通常为17) 重要的 数字。

    DBL_DECIMAL_DIGITS
    小数位数,n,使得任何具有p基数b数字的浮点数都可以四舍五入为具有n个小数位数的浮点数,然后在不改变值的情况下再次返回,
    C23dr§5.2.4.2.2 24

    任何值大小>=10 DBL_DECIMAL_DIG-1 打印 "%.0f" 将至少打印 DBL_DECIMAL_DIG 数字。只有小于该值的值可能需要小数点后的一些数字。

    int prec = DBL_DECIMAL_DIG - log10(fabs(x));
    if (prec < 0) {
      prec = 0;
    }
    printf("%.*f\n", prec, x);
    

    • 需要小心 int prec = DBL_DECIMAL_DIG - log10(fabs(x)) 因为非常接近10的幂的值可能会产生计算误差,从而导致一次误差。最好是四舍五入,并且潜在的精度为+1。

    • 选择 双重的 用更少的数字可以很容易地获得值。可以尝试逐步降低精度。

    • 无穷大、NaN和零可能需要特殊处理。

    • 关于的值 -DBL_TRUE_MIN 可能需要最长时间 一串 。这是关于 2 /* "-0." */ - DBL_MIN_10_EXP + DBL_DECIMAL_DIG + 1 /* \0 */


    要找到最佳的最小格式精度:

    #include <float.h>
    #include <math.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    // Buffer size needed for all `double`
    #define BUF_N (3 /* "-0." */ - DBL_MIN_10_EXP + DBL_DECIMAL_DIG + 1 /* \0 */)
    
    // Untested code.  Grandparent duty calls.
    // Needs review for off-by-1 errors.
    int round_trip_precision_min(double x) {
      if (!isfinite(x) || x == 0.0) {
        return 0;
      }
      char buf[BUF_N + 10];  // 10 extra for margin
      int prec = (int) (DBL_DECIMAL_DIG - lround(log10(fabs(x))));
      if (prec < 0) {
        prec = 0;
      }
      // Try with less precision
      while (prec > 0) {
        sprintf(buf, "%.*f", prec - 1, x);
        if (atof(buf) != x) {
          break;
        }
        prec--;
      }
      return prec;
    }
    
        2
  •  0
  •   pmor    2 年前

    一个简单的解决方案是通过x槽 sprintf 然后 strtod (产生y),直到x=y:

    #include <float.h>
    #include <math.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    
    #define PREC_MAX 340
    
    void print_f(double x, bool newline)
    {
        double y;
        char s[PREC_MAX * 3]; // what is the precise multiplier ?
        bool f_printed = false;
    
        if (isnan(x) || isinf(x))
        {
            printf("%f", x);
        }
        else
        {
            for (int prec = 1; prec < PREC_MAX; ++prec)
            {
                if (sprintf(s, "%.*f", prec, x) > 0) // may encoding error occur ?
                {
                    y = strtod(s, 0); // is there a real need of error handling ?
                    if (x == y)
                    {
                        printf("%s", s);
                        f_printed = true;
                        break;
                    }
                }
            }
            if (!f_printed)
            {
                printf("%.*g", DBL_DECIMAL_DIG, x); 
            }
        }
        if (newline)
        {
            printf("\n");
        }
    }
    
    int main(void)
    {
        print_f(0.000000000000000000000001617, true);   // 0.000000000000000000000001617
        print_f(1.0, true);                             // 1.0
        print_f(0x1p-27, true);                         // 0.000000007450580596923828
        print_f(NAN, true);                             // nan
        print_f(INFINITY, true);                        // inf
    }
    

    可能有一个“无循环”的恒定时间复杂性解决方案,这很有趣。额外的问题:这个解决方案能被正式证明吗?


    额外: Difference between scanf() and strtol() / strtod() in parsing numbers