Create a precise atof() implementation in c

7.9k Views Asked by At

I have written an atof() implementation in c . I am facing rounding off errors in this implementation . So , putting in a test value of 1236.965 gives a result of 1236.964966 but the library atof() function reurns 1236.965000 . My question is , how to make the user defined atof() implementation more 'correct' ?

Can the library definition of atof() be found somewhere ?

#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))!='\0'){
//      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;
}
3

There are 3 best solutions below

2
Thomas Padron-McCarthy On BEST ANSWER

Change all your floats to doubles. When I tested it, that gave the same result as the library function atof for your test case.

atof returns double, not float. Remember that it actually is double and not float that is the "normal" floating-point type in C. A floating-point literal, such as 3.14, is of type double, and library functions such as sin, log and (the perhaps deceptively named) atof work with doubles.

It will still not be "precise", though. The closest you can get to 1236.965 as a float is (exactly) 1236.9649658203125, and as a double 1236.964999999999918145476840436458587646484375, which will be rounded to 1236.965000 by printf. No matter how many bits you have in a binary floating-point number, 1236.965 can't be exactly represented, similar to how 1/3 can't be exactly represented with a finite number of decimal digits: 0.3333333333333333...

And also, as seen in the discussion in comments, this is a hard problem, with many possible pitfalls if you want code that will always give the closest value.

8
chux - Reinstate Monica On

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

Easy: 1) never overflow intermediate calculation and 2) only round once (at the end).

It is hard to do those 2 steps.

Note: C's atof(), strtof(), etc. also handle exponential notation - in decimal and hex.


Potential roundings

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

Potential overflow (even though the final answer is within range)

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

Using a wider type like double can greatly lessen the occurrence of such problems and achieve OP's "more 'correct'". Yet they still exist.

This is not an easy problem to solved to get the best (correct) floating point result from all string inputs.


Sample approaches to solve.

Avoid overflow: rather than 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

Code should form (ival*10)+(c-'0') using extended integer math in the loop for exactness.

Yet this is just scratching the surface of the many corner cases.


@Eric Postpischil commented on a robust C++ code that handles non-exponential notation string input well. It does initial math using integers and only rounds later in the process. This linked code is not visible unless your rep is 10,000+ as the question was deleted.

0
user242579 On

I used your code as inspiration to write my own. What other commenters and answers do not recognize is that the original reason for the question is an embedded situation. In my case the library "atof" pulls in something that does "printf" which pulls in "systemcalls" which I don't have.

So.... here I present a simple (does not implement exponential notation) atof implementation that works in floats, and is suitable for embedding.

My implementation uses way less variables.

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