在 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 类型,而库函数(例如 sin
、log
和(可能具有欺骗性的名称)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;
}
我用 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 类型,而库函数(例如 sin
、log
和(可能具有欺骗性的名称)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;
}