因为你正在失去精度,你应该以更高的精度进行中间计算(例如double),并且只在最后转换回来。
所以,你最好这样做:
void
Exp_Filt(double In, float *Out, double time_base, double time_constant)
{
*Out = In + ((*Out - In) * exp(-time_base / time_constant));
}
此外,您可能永远无法“完美”收敛到 0.0。所以你的循环可能会更好:
#define ERRVAL 1e-5 // some smallish value ...
for (float Out = 1.0f; Out > ERRVAL;)
更新:
通过fesetround 设置舍入模式似乎有效果——见下文。特别是,执行fesetround(FE_DOWNWARD) [once] 确实会收敛到零。
此外,根据给出的-std= 选项,我得到的结果略有不同。
我创建了一个测试程序:
/* all.c */
#include <stdio.h>
#include <math.h>
#include <fenv.h>
#ifndef ITER
#define ITER 1000000
#endif
void
Exp_Filt_orig(float In, float *Out, float time_base, float time_constant)
{
*Out = In + ((*Out - In) * expf(-time_base / time_constant));
}
void
Exp_Filt_fix1(double In, float *Out, double time_base, double time_constant)
{
*Out = In + ((*Out - In) * exp(-time_base / time_constant));
}
double
Exp_Filt_fix2(double In, double Out, double time_base, double time_constant)
{
return In + ((Out - In) * exp(-time_base / time_constant));
}
#define DOLOOP(_fnc,_lim) \
do { \
float Out; \
iter = 0; \
for (Out = 1.0f; (_lim) && (iter < ITER); ++iter) \
_fnc(0.0f, &Out, 0.1f, 1.0f); \
printf("fnc=%s lim='%s' iter=%d Out=%.9g\n", \
#_fnc,#_lim,iter,(double) Out); \
} while (0)
#define DOLOOP2(_fnc,_lim) \
do { \
double Out; \
iter = 0; \
for (Out = 1.0f; (_lim) && (iter < ITER); ++iter) \
Out = _fnc(0.0f, Out, 0.1f, 1.0f); \
printf("fnc=%s lim='%s' iter=%d Out=%.9g\n", \
#_fnc,#_lim,iter,(double) Out); \
} while (0)
#define LIM 1e-5
#define LIM2 1e-10
int
main(void)
{
int iter;
#ifdef __STDC__VERSION__
printf("std=%ld\n",__STDC_VERSION__);
#endif
printf("LIM=%.6g\n",LIM);
printf("LIM2=%.6g\n",LIM2);
printf("ITER=%d\n",ITER);
printf("fegetround=%8.8X\n",fegetround());
#ifdef FE
fesetround(FE);
printf("fegetround=%8.8X\n",fegetround());
#endif
DOLOOP(Exp_Filt_orig,Out != 0.0f);
DOLOOP(Exp_Filt_orig,Out > LIM);
DOLOOP(Exp_Filt_fix1,Out > LIM);
DOLOOP2(Exp_Filt_fix2,Out > LIM);
DOLOOP2(Exp_Filt_fix2,Out > LIM2);
DOLOOP2(Exp_Filt_fix2,Out != 0.0);
return 0;
}
这是一个Makefile:
ALL += allxx all90 all99
ifdef O
OLVL := -O$(O)
endif
ifdef ITER
XFLAGS += -DITER=$(ITER)
endif
ifdef FE
XFLAGS += -DFE=$(FE)
endif
.PHONY: all
all: $(ALL)
allxx: all.c
cc -o allxx all.c -lm $(OLVL) $(XFLAGS)
all90: all.c
cc -o all90 all.c -lm $(OLVL) $(XFLAGS) -std=c90
all99: all.c
cc -o all99 all.c -lm $(OLVL) $(XFLAGS) -std=c99
run: all
./all90
./all99
./allxx
clean:
rm -f $(ALL)
这是make clean run的输出:
rm -f allxx all90 all99
cc -o allxx all.c -lm
cc -o all90 all.c -lm -std=c90
cc -o all99 all.c -lm -std=c99
./all90
LIM=1e-05
LIM2=1e-10
ITER=1000000
fegetround=00000000
fnc=Exp_Filt_orig lim='Out != 0.0f' iter=1 Out=0
fnc=Exp_Filt_orig lim='Out > LIM' iter=1 Out=0
fnc=Exp_Filt_fix1 lim='Out > LIM' iter=116 Out=9.16608587e-06
fnc=Exp_Filt_fix2 lim='Out > LIM' iter=116 Out=9.16608615e-06
fnc=Exp_Filt_fix2 lim='Out > LIM2' iter=231 Out=9.28532947e-11
fnc=Exp_Filt_fix2 lim='Out != 0.0' iter=1000000 Out=2.47032823e-323
./all99
LIM=1e-05
LIM2=1e-10
ITER=1000000
fegetround=00000000
fnc=Exp_Filt_orig lim='Out != 0.0f' iter=1000000 Out=7.00649232e-45
fnc=Exp_Filt_orig lim='Out > LIM' iter=116 Out=9.16610225e-06
fnc=Exp_Filt_fix1 lim='Out > LIM' iter=116 Out=9.16608587e-06
fnc=Exp_Filt_fix2 lim='Out > LIM' iter=116 Out=9.16608615e-06
fnc=Exp_Filt_fix2 lim='Out > LIM2' iter=231 Out=9.28532947e-11
fnc=Exp_Filt_fix2 lim='Out != 0.0' iter=1000000 Out=2.47032823e-323
./allxx
LIM=1e-05
LIM2=1e-10
ITER=1000000
fegetround=00000000
fnc=Exp_Filt_orig lim='Out != 0.0f' iter=1000000 Out=7.00649232e-45
fnc=Exp_Filt_orig lim='Out > LIM' iter=116 Out=9.16610225e-06
fnc=Exp_Filt_fix1 lim='Out > LIM' iter=116 Out=9.16608587e-06
fnc=Exp_Filt_fix2 lim='Out > LIM' iter=116 Out=9.16608615e-06
fnc=Exp_Filt_fix2 lim='Out > LIM2' iter=231 Out=9.28532947e-11
fnc=Exp_Filt_fix2 lim='Out != 0.0' iter=1000000 Out=2.47032823e-323
然而,这里是make FE=FE_DOWNWARD clean run的输出:
rm -f allxx all90 all99
cc -o allxx all.c -lm -DFE=FE_DOWNWARD
cc -o all90 all.c -lm -DFE=FE_DOWNWARD -std=c90
cc -o all99 all.c -lm -DFE=FE_DOWNWARD -std=c99
./all90
LIM=1e-05
LIM2=1e-10
ITER=1000000
fegetround=00000000
fegetround=00000400
fnc=Exp_Filt_orig lim='Out != 0.0f' iter=1000000 Out=3.40282346e+38
fnc=Exp_Filt_orig lim='Out > LIM' iter=1000000 Out=3.40282346e+38
fnc=Exp_Filt_fix1 lim='Out > LIM' iter=116 Out=9.16604039e-06
fnc=Exp_Filt_fix2 lim='Out > LIM' iter=116 Out=9.16608615e-06
fnc=Exp_Filt_fix2 lim='Out > LIM2' iter=231 Out=9.28532947e-11
fnc=Exp_Filt_fix2 lim='Out != 0.0' iter=7427 Out=0
./all99
LIM=1e-05
LIM2=1e-10
ITER=1000000
fegetround=00000000
fegetround=00000400
fnc=Exp_Filt_orig lim='Out != 0.0f' iter=1015 Out=0
fnc=Exp_Filt_orig lim='Out > LIM' iter=116 Out=9.16599037e-06
fnc=Exp_Filt_fix1 lim='Out > LIM' iter=116 Out=9.16604039e-06
fnc=Exp_Filt_fix2 lim='Out > LIM' iter=116 Out=9.16608615e-06
fnc=Exp_Filt_fix2 lim='Out > LIM2' iter=231 Out=9.28532947e-11
fnc=Exp_Filt_fix2 lim='Out != 0.0' iter=7427 Out=0
./allxx
LIM=1e-05
LIM2=1e-10
ITER=1000000
fegetround=00000000
fegetround=00000400
fnc=Exp_Filt_orig lim='Out != 0.0f' iter=1015 Out=0
fnc=Exp_Filt_orig lim='Out > LIM' iter=116 Out=9.16599037e-06
fnc=Exp_Filt_fix1 lim='Out > LIM' iter=116 Out=9.16604039e-06
fnc=Exp_Filt_fix2 lim='Out > LIM' iter=116 Out=9.16608615e-06
fnc=Exp_Filt_fix2 lim='Out > LIM2' iter=231 Out=9.28532947e-11
fnc=Exp_Filt_fix2 lim='Out != 0.0' iter=7427 Out=0
编辑:原始帖子被 DV 化:
当你调用一个给定的函数时,如果函数的一个参数是float,它会被调用函数自动传递为double,并被解释为double 调用函数。
也就是说,In、time_base 和 time_constant 是 double,即使您指定了 float。只有Out [因为它是一个指向float 的指针 并且不是 按值传递] 是float。
并且,在表达式中,float 在评估期间会自动提升为 double。