2013-10-11 79 views
1
  • 编译器: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

为什么会出现这种情况? (也随意点出任何其他错误,或者告诉我其他任何字符串,也有不同的值(有没有办法可以找到他们)。)

#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

你能更具体吗?你期望得到什么,取而代之的是什么? – Barmar

+0

非常小的错误将由于四舍五入,这在处理浮点时是不可避免的。根本不是错误。如果这不是问题,你将不得不说出口。 –

+1

“%.38f”表明您比32位(或64位)浮点数可以提供更高的精度。 – ryyker

回答

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

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任何在数字零的个数后非零数字,那么转换将向上圆的。如果没有非这意味着有无限制您必须检查多少数字才能确定正确舍入的结果幸运的是,除了必须执行的算术量外,还有一些限制如扫描所示)。

+0

此论文的链接已作为_comment_发布在问题上(适合作为参考)。如果该链接关闭,这_answer_将不会有任何好处,并且不包含提问者需要的信息。 –

+0

@JoshuaTaylor:论文的内容是必要的;设计正确舍入的转换并不容易。你如何建议把内容放在答案中?也许这个问题应该过于宽泛地结束。 –

+0

那么,过于宽泛的条件确实包括“有太多可能的答案,或者**这种格式的答案太好**”。如果没有整篇论文的话就无法回答这个问题,那么它可能太宽泛了。然而,我预计'strtof'和'printf(...)'行为的差异可以用不到一整篇论文来解释(虽然对其他来源的引用可能仍然适用)。 –

2

查看strtof/strtod的源代码后,它使用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)); 
} 
+0

此代码是否适用于所有情况,包括输入数字非常接近舍入因两个最接近的可表示值几乎相等而变化的点的情况? –

+0

所有测试给出相同的结果,除了-0和字符串非数字字符为例如:1e-5 –

+0

鉴于“1.30000001192092896”,此代码显示“f”和“strtof”的不同结果。 –

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,1.01*x)来表示可接受的结果。

6)典型的float约有1部分的功率(2,23)精度(约7位十进制数字)。随着OP的关闭,整体转换是好的,只需要反向解析。