sprintf(buf,“%。20g”,x)//应该有多大?

时间:2009-12-19 20:41:30

标签: c++ floating-point printf buffer-overflow floating-accuracy

我正在将double值转换为字符串,如下所示:

std::string conv(double x) {
    char buf[30];
    sprintf(buf, "%.20g", x);
    return buf;
}

我已经将缓冲区大小硬编码为30,但我不确定它是否足够大以适应所有情况。

  • 如何找到我需要的最大缓冲区大小?
  • 从32位切换到64?
  • 时,精度是否会提高(因此缓冲区需要增加)?

PS:出于效果原因,我无法使用ostringstreamboost::lexical_cast(请参阅this

5 个答案:

答案 0 :(得分:3)

printf("%.20g", 1.79769e+308);1.7976900000000000632e+308,27个字节,包括尾随\ 0。我会选择64或128只是为了确定。

(因为它在堆栈上并且在您也可以使用大缓冲区之后立即释放,甚至是2048字节,而不会遇到非嵌入式应用程序的问题)

另外,您确定程序的瓶颈是lexical_cast ..?做你正在做的事对我来说似乎很愚蠢

答案 1 :(得分:3)

我似乎记得,如果您使用sprintf目的地致电NULL,它就不会做任何事情。但是,它确实返回了“写”的字符数。如果我是对的(我似乎无法找到它的来源),那么你可以这样做:

// find the length of the string
int len = sprintf(NULL, fmt, var1, var2,...);
// allocate the necessary memory.
char *output = malloc(sizeof(char) * (len + 1)); // yes I know that sizeof(char) is defined as 1 but this seems nicer.
// now sprintf it after checking for errors
sprintf(output, fmt, var1, var2,...);

另一个选择是使用snprintf,它允许您限制输出的长度:

#define MAX 20 /* or whatever length you want */
char output[MAX];
snprintf(output, MAX, fmt, var1, var2,...);

snprintf将缓冲区的大小作为参数,并且不允许输出字符串超过该大小。

答案 2 :(得分:3)

  

我已将缓冲区大小硬编码为30,但我不确定这是否足够大,适用于所有情况。

是的。 %。20g指定尾数中的20位数。为小数点加1。 1表示(可能)符号,5表示“e + 308”或“e-308”,最坏情况指数。和1表示终止null。

20 + 1 + 1 + 5 + 1 = 28.

  

从32位切换到64位时,精度是否会提高(因此缓冲区需要增加)

没有。

两种体系结构中的double大小相同。如果将变量声明为long double,那么在指数“e + 4092”中可能还有1个数字,它仍然适合30个字符的缓冲区。但仅限于X86,仅适用于较旧的处理器。

long double是一种过时的80位浮点值形式,它是486 FPU的原生格式。 FPU体系结构不能很好地扩展,因为已经丢弃了有利于SSE样式指令,其中最大可能浮点值是64位双精度。

只要您将打印输出中的尾数限制为20位,只要说30个字符的缓冲区就足够了。

答案 3 :(得分:0)

这是一个程序,用于打印任何系统可以采用的double最大值和最小值所需的位数:

#include <float.h>
#include <stdio.h>

int main(void)
{
    double m = DBL_MAX;
    double n = DBL_MIN;
    int i;
    i = printf("%.20g\n", m);
    printf("%d\n", i);
    i = printf("%.20g\n", n);
    printf("%d\n", i);
    return 0;
}

对我来说,它会打印出来:

1.7976931348623157081e+308
27
2.2250738585072013831e-308
27

由于27包含换行符但不包括字符串的终止0,我想说在这个系统上,27就足够了。对于long double,我的系统上LDBL_MAXLDBL_MIN的答案似乎分别是27和28。

手册页(在我的sprintf上说明了%g

  

转换双参数   样式f或e(或G的F或E)                转换)。精度指定的数量   重大                数字。如果精度丢失,则给出6位数字;如果                精度为零,它被视为1.如果是,则使用样式e                转换的指数小于-4或更大   比或                等于精度。尾随零从中移除                结果的小数部分;小数点仅出现   如果它                之后至少有一位数。

类似的措辞在C标准中。

因此,如果您使用上述程序的输出作为数组大小,我认为您将是安全的。

答案 4 :(得分:0)

如果您使用的是支持POSIX或C99的平台,则应该能够使用snprintf来计算所需缓冲区的大小。 snprintf接受一个参数,指示您传入的缓冲区的大小;如果字符串的大小超过该缓冲区的大小,它会截断输出以适应缓冲区,并返回适合整个输出所需的空间量。您可以使用此输出来分配一个确切正确大小的缓冲区。如果你只想计算所需缓冲区的大小,你可以传入NULL作为缓冲区,大小为0来计算你需要多少空间。

int size = snprintf(NULL, 0, "%.20g", x);
char *buf = malloc(size + 1); // Need the + 1 for a terminating null character
snprintf(buf, size + 1, "%.20g", x);

在使用它之后请记住free(buf)以避免内存泄漏。

这个问题是它在Visual Studio中不起作用,它仍然不支持C99。虽然它们有something like snprintf,但如果传入的缓冲区太小,则不会返回所需的大小,而是返回-1,这完全没用(并且它不接受NULL作为缓冲区,即使长度为0

如果你不介意截断,你可以简单地使用snprintf一个固定大小的缓冲区,并确保你不会溢出它:

char buf[30];
snprintf(buf, sizeof(buf), "%.20g", x);

确保在snprintf上查看您的平台文档;特别是,如果字符串被截断,某些平台可能不会在字符串末尾添加终止空值,因此您可能需要自己执行此操作。