将double/float转换为字符串

33
我需要将浮点数转换成相应的十进制(或其他进制)字符串。首先需要按照格式xE+0进行转换,其中x是浮点数。
我的想法是先将浮点数截断为一个临时整数,然后将该整数转换为字符串,考虑小数部分,当小数部分不为0时,乘以10。在小数部分被转移至十进制点左侧后,再次应用整数转字符串函数,并将小数部分转换为字符串。是否有更好、更快的方法?这种方法会引起任何副作用吗?
要将浮点数转换为指数表示法,我需要像上面那样做,然后调整幂次吗?还是直接掩码IEEE 754浮点表示法,并将每个部分转换为字符串。
注意:不能使用其他函数,因为我无法访问任何库函数。这段代码将用于玩具内核。

13
我希望人们尝试回答这个问题,而不是问“为什么不能使用库函数?” - Frerich Raabe
3
http://www.exploringbinary.com/quick-and-dirty-floating-point-to-decimal-conversion/ 可以作为一个起点,帮助您进行浮点数到十进制的转换。 - user786653
整个尾数是“小数部分”。IEEE 754规范化浮点数是一个小数部分(“1.”是隐含的)。 - Pascal Cuoq
你正在考虑的方法会丢失精度并产生糟糕的舍入误差。但是你需要多少精度呢?固定的位数?足以在读回十进制表示时重现“double”的确切值?还是确切的值? - R.. GitHub STOP HELPING ICE
显示剩余4条评论
9个回答

30

使用 stdlib.h 中的 snprintf()。对我有用。

double num = 123412341234.123456789; 
char output[50];

snprintf(output, 50, "%f", num);

printf("%s", output);

11
我无法使用任何标准库函数,因为我所使用的平台上没有任何可用的函数。 - phoxis
2
我使用GCC和LLVM/Clang编译了这段代码,结果是"123412341234.123459",而不是"123412341234.123456789"。这个答案部分正确,因为它只适用于小范围的浮点数值。 - PADYMKO
1
@PADYMKO 这不是代码的错误,而是浮点数只能在十进制下保留约15位数字的结果。对于这个例子,仅“123412341234.123459”就已经转换为相同的双精度值。您可以查看下一个和上一个双精度值 - Mingye Wang

22
唯一精确的解决方案是执行任意精度的十进制算术进行基本转换,因为确切值可能非常长 - 对于80位的long double,最多可达约10000个小数位。幸运的是,对于IEEE double,它只有大约700个小数位左右。
与其使用单个十进制数字,不妨使用基于10的最高32位整数次幂的“基-10亿”进行计算,然后在计算结束时将这些“基-10亿数字”转换为每个9个十进制数字。
我在这里提供了一个非常密集(难以阅读)但高效的实现,采用LGPL MIT许可证: http://git.musl-libc.org/cgit/musl/blob/src/stdio/vfprintf.c?h=v1.1.6 如果您剥离所有十六进制浮点支持、无穷大/NaN支持、%g/%f/%e变体支持、舍入(如果您只需要精确答案,则永远不需要)和其他您可能不需要的功能,则剩余的代码相当简单。

你的意思是让我先将其转换为2^32进制数,然后从中获取10进制字符串吗?在代码中加上一些注释和/或简要描述会真正帮助理解代码。 - phoxis
1
@phoxis:你对fmt_fp感兴趣。 y是输出到f的数字。 w=字符串宽度,p=精度,fl=标志,t=类型。格式代码"%20.5g"给出了w=20,p=5,fl=0,t='g'。(w=0,p=-1会给你默认格式)。提取所需部分应该是可行的。(请注意,我只是简要地查看了源代码,但它看起来相当稳定,并且在快速和肮脏的链接失败的情况下可以正常工作) - user786653
@phoxis:有一些东西可以让你开始:http://pastebin.com/c84Dbvd7(提前为我增加到讨论中的任何混乱向 R.. 道歉) - user786653
1
看到链接可能会失效(并且不是每个人都能访问它们),也许您可以直接将相关的“剩余代码”添加到您的答案中? - domsson

6

请查看某个C库中使用"%f"printf()实现。


5

我知道这可能是不必要的,但我编写了一个将浮点数转换为字符串的函数:

代码:

#include <stdio.h>

/** Number on countu **/

int n_tu(int number, int count)
{
    int result = 1;
    while(count-- > 0)
        result *= number;

    return result;
}

/*** Convert float to string ***/
void float_to_string(float f, char r[])
{
    long long int length, length2, i, number, position, sign;
    float number2;

    sign = -1;   // -1 == positive number
    if (f < 0)
    {
        sign = '-';
        f *= -1;
    }

    number2 = f;
    number = f;
    length = 0;  // Size of decimal part
    length2 = 0; // Size of tenth

    /* Calculate length2 tenth part */
    while( (number2 - (float)number) != 0.0 && !((number2 - (float)number) < 0.0) )
    {
         number2 = f * (n_tu(10.0, length2 + 1));
         number = number2;

         length2++;
    }

    /* Calculate length decimal part */
    for (length = (f > 1) ? 0 : 1; f > 1; length++)
        f /= 10;

    position = length;
    length = length + 1 + length2;
    number = number2;
    if (sign == '-')
    {
        length++;
        position++;
    }

    for (i = length; i >= 0 ; i--)
    {
        if (i == (length))
            r[i] = '\0';
        else if(i == (position))
            r[i] = '.';
        else if(sign == '-' && i == 0)
            r[i] = '-';
        else
        {
            r[i] = (number % 10) + '0';
            number /=10;
        }
    }
}

2
最好情况下,这可能只能正确打印出一些小的浮点数。而2011年R..已经发布了所有双精度数字的正确答案,现在添加一个不正确的答案的价值是多少呢? - Pascal Cuoq
我只是提供帮助,或许可以指导如何将浮点数转换为字符串。我尝试使用atof将字符串转换回来,它起作用了。 - JJJakubJJ

4
看看BSD C标准库是否有fcvt()函数。你可以以它的源代码为起点,而不是从头开始编写你自己的代码。UNIX 98标准的fcvt()函数似乎不输出科学计数法,所以你需要自己实现,但我认为这应该不难。

2
请使用以下内容:
void double_to_char(double f,char * buffer){
    gcvt(f,10,buffer);
}

1

我会将您的问题解释为“将 double/float 转换为字符串”,并不会过于深入讨论确切的建议格式;我假设您希望得到一个“尽可能好地以ASCII表示值”的结果,并且最好能够再次回转到相同的浮点数值。

有几个选项可供选择:

  • Ryan Juckett 在这篇四部分的博客文章中相当好地描述了他的(Dragon41)实现: https://www.ryanjuckett.com/printing-floating-point-numbers。注意: 他的实现是C++;如果C是你使用情况的严格要求,那么就不适用了。
  • dtoa.c 来自 netlib.org,在 Ryan 的博客文章中提到:

    由David Gay编写(也是上述论文之一的作者),这似乎是被接受的安全实现。从1991年开始迭代,被用于从Python到MySQL到任天堂Wii等各个方面。

  • double-conversion,也在 Ryan 的博客文章中提到 - 此实现在Google V8 JS引擎中使用。使用 Grisu2 算法。这同样是用C++编写的,所以可能会或者不会是一个选择。
  • Milo Yip的 RapidJSON中的实现。这使用了一些C++结构,但快速浏览表明,如果需要的话,可以很容易地转换为C。

1: https://kurtstephens.com/files/p372-steele.pdf

2: https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf


0

或者,您可以使用C99输出格式,即:[-]0x1.<significand>p<biasied_exponent-bias>,其中偏差为0x3ff,适用于双精度数字。这也是序列化的首选格式,因为它是精确的,并且不会依赖于当前的浮点环境设置(如舍入或将非规格化数值刷新为零)。有点不寻常,但仍然可能很有用并且非常快速。


-4

sprintf 可以做到这一点:

#include <stdio.h>
int main() {
  float w = 234.567;
  char x[__SIZEOF_FLOAT__];
  sprintf(x, "%g", w);
  puts(x);
}

3
这有一个问题。__SIZEOF_FLOAT__是float在内存中使用的字节数。这里需要表示任意float所需的字符数。 - Tod

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