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

How to compute precision for conversion specification %.*f to maintain precision of floating-point value?

提问人:pmor 提问时间:9/22/2023 最后编辑:pmor 更新时间:9/26/2023 访问量:86

问:

注意:这个问题源于这个答案。

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

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

c 浮点 printf 精度

评论

0赞 Some programmer dude 9/22/2023
当你说“原始价值”时,我假设你指的是印刷价值?
0赞 Weather Vane 9/22/2023
你能说明你使用和是什么意思吗?sprintfsscanf
0赞 Eric Postpischil 9/22/2023
您是否想要一个适用于所有可能打印的浮点值的单个值(位数)?还是为即将打印的特定浮点值计算的值?如果值是 ,是否真的要打印所需的数百位数字(通常格式为 324 位)?DBL_TRUE_MINdouble
0赞 Eric Postpischil 9/22/2023
@Someprogrammerdude:我对此表示怀疑。给定 (或 ) ,具有由浮点格式定义的特定值。使用 with 将其转换为字符串,然后将字符串转换为浮点格式 using 将产生一些结果值,而获取“原始值”意味着结果值等于 的初始浮点值,无论临时字符串是什么。doublefloatxxprintf%.*fscanfx
0赞 Eric Postpischil 9/22/2023
@SimonGoater:回复“一般来说,你不能确定它是否会被读回为原始值”:每个浮点值都可以用十进制足够精确地表示,如果使用正确实现的例程,将十进制表示形式转换回浮点格式将产生原始值。也就是说,总有一些数字可以完成这项工作。

答:

0赞 pmor 9/22/2023 #1

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

#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
}

可能有一个“无循环”的常数时间复杂度解决方案,这很有趣。额外的问题:这个解决方案可以得到正式证明吗?


额外:scanf() 和 strtol() / strtod() 在解析数字方面的区别

评论

0赞 chux - Reinstate Monica 9/23/2023
尝试。幻数 256 太小了。需要一个值 ,通常大于 300。print_f(-DBL_TRUE_MIN, 0)DBL_DECIMAL_DIG - DBL_MIN_10_EXP
0赞 pmor 9/26/2023
谢谢!测试表明它是325。但是,您的公式 () 显示它是 340。更改为 340。(DBL_DECIMAL_DIG - lround(log10(fabs(x))))
0赞 chux - Reinstate Monica 9/26/2023
我的公式可能会出错,但希望只在稍微太大的尺寸上。十几个额外的字节很少是问题,而缓冲区不足会导致故障或更糟。
1赞 chux - Reinstate Monica 9/23/2023 #2

要通过(二进制浮点)值往返于同一值的十进制文本,最多需要(通常为 17 位)有效数字。double"%.*f"DBL_DECIMAL_DIG

DBL_DECIMAL_DIGITS
十进制位数 n 使得任何具有 p 基数 b 位数的浮点数都可以四舍五入为具有 n 个十进制数字的浮点数,然后再次返回,而无需更改值,
C23dr § 5.2.4.2.2 24

任何值大小 >= 10DBL_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);

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

  • 选择值可以很容易地用更少的数字通过。可以尝试逐步降低精度。double

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

  • 关于的值可能需要最长的字符串。这是关于.-DBL_TRUE_MIN2 /* "-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;
}

评论

1赞 pmor 9/27/2023
我已经调整了(32位)的代码,并对其进行了详尽的测试。未发现错误。floatFLT_