我将双精度值转换为字符串,如下所示:

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

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

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

附:我无法使用 ostringstream 或者 boost::lexical_cast 出于性能原因(请参阅 )

有帮助吗?

解决方案

我已将缓冲区大小硬编码为 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 个字符的缓冲区就始终足够。

其他提示

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

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

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

我好像记得如果你打电话 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 将缓冲区的大小作为参数,并且不允许输出字符串超过该大小。

这是一个打印最大值和最小值所需位数的程序 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 分别在我的系统上。

手册页(在我的 sprintf 说这个关于 %g:

双重参数以f或e(或f或e表示g转换)的样式转换。精度指定了显着数字的数量。如果精度缺失,则给出6位;如果精度为零,则将其视为1。如果指数转换的指数小于-4或大于或等于精度,则使用样式E。从结果的分数中删除了尾随的零;仅当小数点遵循至少一个数字时才会出现小数点。

C 标准中也有类似的措辞。

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

如果您使用的平台支持 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,因此您可能需要自己执行此操作。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top