Because you're losing precision, you should do intermediate calculations in higher precision (e.g. double
) and only convert back at the end.
So, you're better off doing:
void
Exp_Filt(double In, float *Out, double time_base, double time_constant)
{
*Out = In + ((*Out - In) * exp(-time_base / time_constant));
}
Also, you may never get "perfect" convergence to 0.0. So your loop might be better off with:
#define ERRVAL 1e-5 // some smallish value ...
for (float Out = 1.0f; Out > ERRVAL;)
UPDATE:
Setting the rounding mode via fesetround
seems to have an effect -- see below. In particular, doing fesetround(FE_DOWNWARD)
[once] does get convergence to zero.
Also, I get slightly different results depending upon what -std=
option is given.
I created a test program:
/* 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;
}
Here is a Makefile
for it:
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)
Here is the output of 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
However, here is the output of 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
Edit: Original post getting DV'ed:
When you call a given function, if one of the function's parameters is float
, it is automatically passed by the calling function as a double
and interpreted as a double
by the called function.
That is, In
, time_base
, and time_constant
are double
even though you specified float
. Only Out
[because it's a pointer to float
and not passed by value], is a float
.
And, in expressions, a float
is automatically promoted to a double
during evaluation.