在C语言中将双精度浮点数转换为char*所需的缓冲区大小

4
为了将C语言中的double(或float、int、long)转换为char*,我们可以使用以下方法:
i) sprintf()
ii) itoa()、ftoa()等函数
但是,所有这些函数都需要一个缓冲区作为参数。在每种情况下,缓冲区的大小应该是多少,以便能够转换最大整数或最高精度的浮点/double?

1
看看这篇帖子是否有帮助。不过,关于浮点数我就不知道了。 - rubikonx9
没有标准的 ftoa 函数。 - user1196549
4个回答

3

snprintf可以用来确定大小,使用0作为大小参数:

double x = 42;
const char fmt[] = "%f";
int size = snprintf(0, 0, fmt, x);
if(size < 0) { /* handle error */ }
char buf[size+1];
sprintf(buf, fmt, x);

C标准要求使用0大小参数的snprintf工作。请注意,一些旧的实现不符合规范(例如,根据man page,Glibc直到版本2.0.6如果输出被截断,则返回-1而不是将被写入的字符数)。上面的错误检查可能是无用的,唯一可能发生的错误是编码错误。在snprintf和sprintf调用之间不应更改语言环境,因为语言环境可能会影响大小。如果介入语言环境变化是一个问题,那么可以再次使用snprintf调用来替换sprintf,但然后您可能需要通过malloc分配内存,然后在必要时realloc。[感谢Chux!]
在Glibc中,还有一个asprintf函数可以分配缓冲区本身。不要忘记在使用后释放它。[感谢Abligh!]

极端严谨的观点:由于可变长度数组是可选的,因此C11并不要求这个工作。 - M.M
关于locale的更改(或者如果x可能是volatile),建议也调用第二次snprintf() - chux - Reinstate Monica
@MattMcNabb:是的,我在这里使用它们,因为这是我发现VLAs(而不是指向VLAs的指针)有用的少数情况之一:你没有一个好的长度上限,但你知道它不会“巨大”。如何在没有它们的情况下完成它是基本的C知识,与问题并不真正相关,所以我没有提到它。 - mafso
@chux:然后做什么?在调试版本中,您可以使用“abort”。无论哪种方式,我认为最好在函数文档中记录不接受语言环境更改。或者,在循环中使用“malloc”和“realloc”,直到一切正常。但是,是的,可能需要提到,我稍后会进行编辑,目前没有时间。 - mafso
1
glibc 下,您可以使用 asprintf 来分配正确大小的缓冲区。 - abligh
我认为asprintf是正确的选择。内存分配已经处理好了。 - user1423561

1

让缓冲区刚好足够大。一个双精度浮点数大约有15位数字加上指数和符号。一个缓冲区 char buffer[25] 应该足够。


“大到什么程度才算够大”这取决于架构等因素。而且,它永远不会是最小可能值(如果这很重要的话)。 - rubikonx9
只要架构使用IEEE浮点数,双精度浮点数在字符串表示中的最大长度应该是相当恒定的。只需将pi * -1.0 * 1e-15打印为字符串并计算字符数,再加上一个用于安全和一个用于\0 - DrKoch
1
这样粗心地进行大小计算真的是一个糟糕的建议。例如,%f 默认情况下会打印小数点后的 6 位数字,这使得 char[25] 对于一些 IEEE 双精度值来说太小了。 - mafso
如果你能得到一个类似于 sizeof(int) * CHAR_BIT / 3 + 2 的好的大小公式,我会认为这是一个非常有帮助的答案。但我不知道有没有一个比较合理的方法来得到一个上限,特别是因为我不知道小数点的长度在所有语言环境中是否有一个上限(也许是 MB_LEN_MAX?)。<float.h> 中的 DECIMAL_DIG(以及其他宏)可能会有所帮助。 - mafso
@mafso 为了以指数形式独特地打印每个double,代码需要至少打印DBL_DECIMAL_DIG位数字。 DBL_DECIMAL_DIG<float.h>中。 参考 - chux - Reinstate Monica
显示剩余2条评论

1

很遗憾,使用%f说明符无法限制长度,因为长度参数是最小值而不是最大值。因此,大值需要的字符数与其指数相同,不包括小数部分。

例如,"%f.10", 1.e20f会得到100000002004087730000.0000000000

从这个角度来看,sprintf非常不安全(应该被禁止),建议使用snprintf


注意:在C语言环境中,"%f"的长度限制为DBL_MAX_10_EXP + 9(+1用于'\0')。同意关于sprintf()的担忧。 - chux - Reinstate Monica

1
double 转换为字符串并使用 sprintf(),缓冲区大小取决于格式说明符。
请注意,下面的缓冲区在编译时是固定的,而不是VLA。
//       -   digits               .   000000 \0
char buf[1 + 1 + DBL_MAX_10_EXP + 1 + 6 + 1];
sprintf(buf, "%f", x);

//       -   digit.digits          e   -  xxxx \0
char buf[1 + 2 + DBL_DECIMAL_DIG + 1 + 1 + 4 + 1];
sprintf(buf, "%.*e", DBL_DECIMAL_DIG, x);

// Others: %0.100f %a %*.*E etc.

但是printf()函数会考虑它们的locale,因此可能会出现额外的字符。建议将缓冲区大小超过预期大小的2倍,使用snprintf()函数,然后安心睡觉。
//       -   digit.digits          e   -  xxxx \0
char buf[(1 + 2 + DBL_DECIMAL_DIG + 1 + 1 + 4 + 1)*2];
int len = snprintf(buf, "%.*e", DBL_DECIMAL_DIG, x);
if (len < 0 || len >= sizeof buf) {
  Handle_Insanity();
}

参考: 如何使用Printf中的格式说明符来保持浮点数的精度


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