在 c 中创建精确的 atof() 实现

Create a precise atof() implementation in c

我用 c 写了一个 atof() 实现。我在这个实现中面临四舍五入的错误。因此,输入 1236.965 的测试值会得到 1236.964966 的结果,但库 atof() 函数会返回 1236.965000。我的问题是,如何使用户定义的 atof() 实现更多 'correct'?

能否在某处找到 atof() 的库定义?

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

float str_to_float(char *);
void float_to_str(float,char *);

int main(){
    int max_size;
    float x;
    char *arr;
    printf("Enter max size of string : ");
    scanf("%d",&max_size);
    arr=malloc((max_size+1)*sizeof(char));
    scanf("%s",arr);
    x=str_to_float(arr);
    printf("%f\n%f",x,atof(arr));
    return 0;
}

float str_to_float(char *arr){
    int i,j,flag;
    float val;
    char c;
    i=0;
    j=0;
    val=0;
    flag=0;
    while ((c = *(arr+i))!='[=10=]'){
//      if ((c<'0')||(c>'9')) return 0;
        if (c!='.'){
            val =(val*10)+(c-'0');
            if (flag == 1){
                --j;
            }
        }
        if (c=='.'){ if (flag == 1) return 0; flag=1;}
        ++i;
    }
    val = val*pow(10,j);
    return val;
}

把你所有的花车改成双打。当我测试它时,它给出了与您的测试用例的库函数 atof 相同的结果。

atof returns double,不是 float。请记住,C 中的 "normal" 浮点类型实际上是 double 而不是 float。浮点文字(例如 3.14)是 double 类型,而库函数(例如 sinlog 和(可能具有欺骗性的名称)atof 使用双打。

不过,它仍然不会是 "precise"。最接近 1236.965 的浮点数是(准确)1236.9649658203125,而双精度数 1236.964999999999918145476840436458587646484375 将被 printf 四舍五入为 1236.965000。无论你在二进制浮点数中有多少位,1236.965 都不能精确表示,类似于 1/3 不能用有限位数的小数精确表示:0.3333333333333333...

而且,正如在评论中的讨论中看到的那样,这是一个难题,如果您希望代码始终给出最接近的值,则可能存在许多陷阱。

how to make the user defined atof() implementation more 'correct' ?

简单:1) 永远不会溢出中间计算和 2) 只循环一次(在最后)。

很难完成这两个步骤。

注意:C 的 atof()strtof() 等也处理指数表示法 - 十进制和十六进制。


可能的四舍五入

val*10
(val*10)+(c-'0');
pow(10,j)
val*pow(10,j)  // This last multiplication is the only tolerable one.

潜在溢出(即使最终答案在范围内)

val*10
(val*10)+(c-'0');
pow(10,j)

使用像double这样更宽的类型可以大大减少此类问题的发生并实现OP的"more 'correct'"。 但它们仍然存在。

要从所有字符串输入中获得最佳(正确)浮点结果,这不是一个容易解决的问题


解决问题的示例方法。

避免溢出:而不是pow(10,j):

val = val*pow(5,j);  // rounds, `pow(5,j)` not expected to overflow a finite final result.
val = val*pow(2,j);  // Does not round except at extremes

代码应在循环中使用扩展整数数学来形成 (ival*10)+(c-'0') 以确保准确性。

然而,这只是许多角落案例的冰山一角。


@Eric Postpischil 评论了一个可以很好地处理非指数符号字符串输入的健壮的 C++ 代码。它使用整数进行初始数学运算,并且仅在该过程的后期进行舍入。除非您的代表超过 10,000,因为问题已被删除,否则此链接代码不可见。

我以您的代码为灵感编写了自己的代码。 其他评论者和答案没有意识到的是,这个问题的最初原因是一个嵌入的情况。在我的例子中,库“atof”引入了一些执行“printf”的东西,它引入了我没有的“系统调用”。

所以....我在这里提出一个简单的(不实现指数表示法)atof 实现,它在浮点数中工作,适合嵌入。

我的实现使用了更少的变量。

float ratof(char *arr)
{
  float val = 0;
  int afterdot=0;
  float scale=1;
  int neg = 0; 

  if (*arr == '-') {
    arr++;
    neg = 1;
  }
  while (*arr) {
    if (afterdot) {
      scale = scale/10;
      val = val + (*arr-'0')*scale;
    } else {
      if (*arr == '.') 
    afterdot++;
      else
    val = val * 10.0 + (*arr - '0');
    }
    arr++;
  }
  if(neg) return -val;
  else    return  val;
}