使用%sprintf和%g格式,最小的缓冲区大小是多少?

3
问题在于静态分配一个足够大的缓冲区,以适应最大精度使用%g格式化的双精度数。这似乎是一个简单的任务,但我遇到了麻烦。我想到的最好的解决方法(假设要打印的数字是x)是:
char buf[1 + DBL_DIG + DBL_DIG + 1 + 1 + 1 + DBL_DIG + 1];
int len = sprintf(buf, "%.*g", DBL_DIG, x);

DBL_DIG宏来自于 float.h,据说它指示了double类型的最大精度。我们需要:

  • 1个字节用来存储符号
  • 足够的字节来捕获有效数字
  • 每个数字最多一个“分隔符”字符(逗号等)
  • 1个字节用来存储小数点
  • 1个字节用来存储'e'
  • 1个字节用来存储指数符号
  • 一些字节用来存储指数
  • 1个字节用来存储sprintf写入的尾随null。

我使用有效数字的数量作为指数中数字数量的上限。我有任何错误吗?是否有更好的解决方案?我应该只分配64、128或256个字节并希望得到最好的结果吗?


1
你只能使用这么精确的内存量,有什么原因吗? - Ed S.
2
我不确定答案,但我想提醒的是不要使用 sprintf。你应该始终使用 snprintf,这样如果你计算的最小缓冲区大小错误,你就不会溢出缓冲区:"snprintf(buf,sizeof(buf),...);" - R Samuel Klatchko
5个回答

5
使用 snprintf() 函数来确定所需的字符数:
#include <float.h> /* DBL_DIG */
#include <stdio.h>
#include <stdlib.h>

int main(void) {
  double x = rand() / (double)RAND_MAX;
  char find_len[1];
  int need_len;
  char *buf;

  need_len = snprintf(find_len, 1, "%.*g", DBL_DIG, x);
  buf = malloc(need_len + 1);
  if (buf) {
    int used = sprintf(buf, "%.*g", DBL_DIG, x);
    printf("need: %d; buf:[%s]; used:%d\n", need_len, buf, used);
    free(buf);
  }
  return 0;
}

您需要一个支持C99标准的编译器才能使用snprintf()
snprintf()是由C99标准定义的。C89实现并不要求具有snprintf()定义,如果它作为扩展功能提供,则不需要按照C99标准所描述的方式“工作”。


你确定需要C99编译器来使用snprintf吗? - user181548
考虑将 #include <stdio.h> 添加到 int main(void) { double snprintf = 42; return snprintf - 42; } 中。这样做会使该程序严格符合 C89 标准,并成为 C99 的必要诊断。当然,C89 编译器允许提供扩展功能。 - pmg

5

在编译时无法预先计算大小。%g格式化程序考虑了语言环境(例如千位分隔符等)。请参见http://linux.die.net/man/3/sprintf,了解如何安全地计算大小。


太棒了!我忘记了本地化。添加额外的 DBL_DIG / 3 应该可以解决这个问题,对吧? - jkl
并不完全是这样。例如在印度,他们使用“拉克”和“千万”来计数,并使用分隔符表示10万(拉克)和1000万(千万)等。 - an0nym0usc0ward
好的,所以我们将术语扩展到包括数字分隔符一个完整的DBL_DIG。没有人可能会在一个数字中使用多个分隔符,对吧?还有其他特定于区域设置的变化吗? - jkl
jkl,我不是一个本地化专家,但我学到了一旦涉及到本地化,就要忘记所有的假设。我也会选择 snprintf - peterchen
假设我不想在输出中使用分隔符或其他特定于区域设置的功能,我只需将区域设置暂时设置为“C”,然后在打印完成后将其设置回来。这样做是否会使估算缓冲区大小更容易? - jkl

1
两件事情: %g 不能显示所有可表示的数字,%g 显示一个对人类友好的四舍五入结果。如果您想要不同的结果,可以使用 %f 或 %e 指定精度。
永远不要使用 sprintf() 而应该使用 snprintf()。在您的情况下: int len = snprintf(buf, dimensionof(buf), "%.*f", DBL_DIG, x);

1

可以使用asprintf来替代使用sprintf,这将分配一个正确大小的缓冲区以适应您的字符串。


我不明白为什么人们这么喜欢snprintf。如果你要使用一个非可移植的函数,为什么不直接使用像asprintf这样的函数来完成工作呢? - jkl

-2
我应该直接取整到64、128或256,然后祈求最好的结果吗?
是的,就这么做吧 -.-

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接