浮点转换的基本字符串

时间:2013-10-11 20:23:32

标签: c floating-point

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

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

  1. 1234.5678
  2. 44444.44444
  3. 333.333
  4. 777.777
  5. 为什么会这样? (也可以随意指出任何其他错误,或告诉我任何其他字符串也有不同的值(我无法找到所有这些)。)

    #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 个答案:

答案 0 :(得分:3)

Converting decimal to binary or vice-versa with correct rounding is complicated, requires detailed knowledge of floating-point arithmetic, and requires care.

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

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

(事实上,考虑1.30000001192092895507812500000000 ... x 。如果 x 是该数字中任意个零后的非零数字,那么转换应该向上舍入。如果没有非零数字,那么转换应该向下舍入。这意味着无限制你需要检查多少位数才能确定正确的舍入幸运的是,除了扫描数字之外,你必须做的算术量有限,如文中所示。)

答案 1 :(得分:2)

在查看strtof / strtod的来源后,它使用double然后强制转换为浮动。

用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));
}

答案 2 :(得分:2)

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

答案较长的是David Gay's paper(在其他答案中引用)和David Gay's implementation of that paper

更长的答案在我的网站上,其中I explain David Gay's code在一系列详细的文章中。

如果您不关心如何正确转换,并且只是想了解您的错误原因,请阅读我的文章Quick and Dirty Decimal to Floating-Point Conversion。它显示了一个像你这样的小程序,看起来应该可行,但却没有。然后,请参阅我的文章Decimal to Floating-Point Needs Arbitrary Precision以了解原因。

答案 3 :(得分: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具有大约1个幂(2,23)精度(~7个十进制数字)。当OP正在接近时,整体转换是正常的,只需要反向解析。