1
  • 编译器:GCC 4.7.2 (Debian 4.7.2-5)
  • 平台:Linux 3.2.0 x86 (Debian 7.1)

我正在尝试将自己的字符串编写为浮点转换函数。它基本上是一个廉价的盗版strtof(),但我无法让它strtof()完全模仿。我不希望我的功能strtof()完全模仿,但我想知道为什么它在哪里不同。我测试了几个不同的字符串,我发现以下字符串在被赋予我的函数时以及被赋予strtof()和使用打印时具有不同的值printf("%.38f"))

  1. 1234.5678
  2. 44444.44444
  3. 333.333
  4. 777.777

为什么会这样?(也请随时指出任何其他错误,或告知我任何其他也具有不同值的字符串(我无法找到它们全部)。)

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

int dec_to_f(char *dec, float *f)
{
int i = 0;
float tmp_f = 0;

if(dec == NULL) return 1;

if(f == NULL) return 2;

if(dec[i] == '\000') return 3;

if(dec[i] == '-')
{
    i++;

    if(dec[i] == '\000') return 3;

    for(; dec[i] != '\000'; i++)
    {
        if(dec[i] == '.')
        {
            float dec_place = 10;
            int power_of_ten = 1;

            for(i++; dec[i] != '\000'; i++, power_of_ten++, dec_place *= 10)
            {
                if(dec[i] >= '0' && dec[i] <= '9')
                {
                    if(power_of_ten > FLT_MAX_10_EXP) return 4;
                    else tmp_f -= (dec[i] - '0') / dec_place;
                }
                else return 5;
            }

            break;
        }

        if(dec[i] >= '0' && dec[i] <= '9')
        {
            tmp_f = tmp_f * 10 - (dec[i] - '0');
            if(!isfinite(tmp_f)) return 6;
        }
        else return 5;
    }
}
else
{
    if(dec[i] == '+')
    {
        if(dec[i+1] == '\000') return 3;
        else i++;
    }

    for(; dec[i] != '\000'; i++)
    {
        if(dec[i] == '.')
        {
            float dec_place = 10;
            int power_of_ten = 1;

            for(i++; dec[i] != '\000'; i++, power_of_ten++, dec_place *= 10)
            {
                if(dec[i] >= '0' && dec[i] <= '9')
                {
                    if(power_of_ten > FLT_MAX_10_EXP) return 7;
                    else tmp_f += (dec[i] - '0') / dec_place;
                }
                else return 5;
            }

            break;
        }

        if(dec[i] >= '0' && dec[i] <= '9')
        {   
            tmp_f = tmp_f * 10 + (dec[i] - '0');
            if(!isfinite(tmp_f)) return 8;
        }
        else return 5;
    }
}

*f = tmp_f;
return 0;
    }

int main()
{
printf("FLT_MIN = %.38f\n", FLT_MIN);
printf("FLT_MAX = %f\n", FLT_MAX);
float f = 0;
int return_value = 0;
char str[256];

printf("INPUT = ");
scanf("%s", str);

return_value = dec_to_f(str, &f);

printf("return_value = %i\nstr = \"%s\"\nf = %.38f\nstrtof = %.38f\n", return_value, str, f, strtof(str, NULL));
}
4

4 回答 4

3

使用正确的舍入将十进制转换为二进制或反之亦然是复杂的,需要浮点运算的详细知识,并且需要小心。

转换困难的原因有很多。其中两个是:

  • 使用浮点执行计算时,这些计算通常会出现舍入误差。如果计算没有仔细设计,那些舍入误差会影响最终结果。
  • 一些输入将非常接近舍入点,因为两个最接近的可表示值几乎等距,舍入发生变化的点。例如,考虑 1.30000001192092895507812 x。如果x为 4,则结果应为 1.2999999523162841796875。如果是 6,结果应该是 1.30000007152557373046875。然而数字x远远超出了 32 位二进制浮点可以区分的十进制数字的数量。甚至超过了64位能分辨的位数。所以你不能使用普通的算术来执行这些转换。您需要某种形式的扩展精度算术。

(实际上,考虑 1.30000001192092895507812500000000...<i>x。如果x是该数字中任意数量的零之后的非零数字,则转换应向上舍入。如果没有非零数字,则转换应舍入向下。这意味着您必须检查多少位数才能确定正确的舍入结果。幸运的是,除了扫描数字外,您必须执行的算术量是有限的,如本文所示。

于 2013-10-11T20:48:09.967 回答
2

简短的回答是:您不能使用浮点数或双精度数转换为浮点数或双精度数。您需要更高精度的算术,“大浮点数”或“大整数”。

更长的答案在David Gay 的论文(在其他答案中引用)和David Gay 对该论文的实施

更长的答案在我的网站上,在那里我用一系列详细的文章解释了 David Gay 的代码。

如果您不关心如何正确进行转换,而只想了解您的错误的原因,请阅读我的文章Quick and Dirty Decimal to Floating-Point Conversion。它显示了一个像你这样的小程序,它似乎应该工作,但没有。然后查看我的文章Decimal to Floating-Point Needs Arbitrary Precision了解原因。

于 2013-10-12T15:31:52.180 回答
2

看了下strtof/strtod的源码后,它使用了double,然后强制转换为float。

用 double 替换 float 得到与 strtof 相同的结果:

#include <stdlib.h>
#include <stdio.h>
#include <float.h>
#include <math.h>
int dec_to_f(char *dec, float *f)
{
int i = 0;
double tmp_f = 0;
if(dec == NULL) return 1;
if(f == NULL) return 2;
if(dec[i] == '\000') return 3;
if(dec[i] == '-')
{
    i++;
    if(dec[i] == '\000') return 3;
    for(; dec[i] != '\000'; i++)
    {
        if(dec[i] == '.')
        {
            double dec_place = 10;
            int power_of_ten = 1;
            for(i++; dec[i] != '\000'; i++, power_of_ten++, dec_place *= 10)
            {
                if(dec[i] >= '0' && dec[i] <= '9')
                {
                    if(power_of_ten > FLT_MAX_10_EXP) return 4;
                    else tmp_f -= (dec[i] - '0') / dec_place;
                }
                else return 5;
            }
            break;
        }
        if(dec[i] >= '0' && dec[i] <= '9')
        {
            tmp_f = tmp_f * 10 - (dec[i] - '0');
            if(!isfinite(tmp_f)) return 6;
        }
        else return 5;
    }
}
else
{
    if(dec[i] == '+')
    {
        if(dec[i+1] == '\000') return 3;
        else i++;
    }
    for(; dec[i] != '\000'; i++)
    {
        if(dec[i] == '.')
        {
            double dec_place = 10;
            int power_of_ten = 1;
            for(i++; dec[i] != '\000'; i++, power_of_ten++, dec_place *= 10)
            {
                if(dec[i] >= '0' && dec[i] <= '9')
                {
                    if(power_of_ten > FLT_MAX_10_EXP) return 7;
                    else tmp_f += (dec[i] - '0') / dec_place;
                }
                else return 5;
            }
            break;
        }
        if(dec[i] >= '0' && dec[i] <= '9')
        {   
            tmp_f = tmp_f * 10 + (dec[i] - '0');
            if(!isfinite(tmp_f)) return 8;
        }
        else return 5;
    }
}
*f = (float)tmp_f;
return 0;
    }
int main()
{
printf("FLT_MIN = %.38f\n", FLT_MIN);
printf("FLT_MAX = %f\n", FLT_MAX);
float f = 0;
int return_value = 0;
char str[256];
printf("INPUT = ");
scanf("%s", str);
return_value = dec_to_f(str, &f);
printf("return_value = %i\nstr = \"%s\"\nf = %.38f\nstrtof = %.38f\n", return_value, str, f, strtof(str, NULL));
}
于 2013-10-11T20:53:11.137 回答
1

@Eric Postpischil 和 @Nahuel Fouilleul 提供了很好的信息。我将添加更多不适合作为评论的想法。

1)文本到FP需要在另一个方向进行评估。而不是最重要的数字。从最不重要到最重要形成结果。忽略前导零。这将最好地保持最低有效文本数字的微妙影响。当你从右到左时,最后保持一个 power_of_10 到倍数。

power_of_ten *= 10.0;
...
loop()
  // tmp_f = tmp_f * 10 + (dec[i] - '0');
  tmp_f = tmp_f/10 + (dec[i] - '0');
  power_of_ten *= 10.0;
...
tmp_f *= power_of_10;

2) 在注意到 DP 后.,(从右到左),将你的 power_of_10 重置为 1.0。

3)将您的-+代码合二为一。

4) 使用“%.9e”比较结果。

5) 使用next_afterf(x,0.99*x)next_afterf(x,1.01*x)将可接受的结果括起来。

6)典型float的功率(2,23)精度约为 1 部分(约 7 个十进制数字)。由于 OP 正在接近这一点,因此整体转换是可以的,只需要反向解析。

于 2013-10-11T22:45:43.973 回答