3

我正在将双精度值转换为这样的字符串:

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

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

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

PS:我不能使用ostringstreamboost::lexical_cast出于性能原因(见此

4

5 回答 5

3

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

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

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

于 2009-12-19T20:46:13.733 回答
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将缓冲区的大小作为参数,并且不允许输出字符串超过该大小。

于 2009-12-19T20:58:42.957 回答
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 个字符的缓冲区就足够了。

于 2009-12-19T22:00:00.693 回答
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的答案似乎分别是 27 和 28 LDBL_MAXLDBL_MIN

手册页(在我的 for 上sprintf%g

double 参数以样式 f 或 e 进行转换(或 F 或 E 用于 G 转换)。精度指定有效位数。如果缺少精度,则给出 6 位数字;如果精度为零,则将其视为 1。如果从其转换得到的指数小于 -4 或大于或等于精度,则使用样式 e。从结果的小数部分中删除尾随零;只有在小数点后跟至少一位数字时才会出现小数点。

类似的措辞在 C 标准中。

所以我认为如果你使用上述程序的输出作为你的数组大小,你会很安全。

于 2009-12-19T20:52:59.083 回答
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。虽然他们有类似的东西snprintf,但如果传入的缓冲区太小,它不会返回所需的大小,而是返回-1,这是完全没用的(并且它不接受NULL作为缓冲区,即使有0长度)。

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

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

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

于 2009-12-19T21:23:46.827 回答