Just to share some insights I got this morning about converting Celsius to Fahrenheit. As this conversion is used in many applications it might be worth just that extra attention (especially as it stands model for any measurements).
The formula for converting Celsius to Fahrenheit found in the books always state : F = C * 9/5 + 32
This formula translates easily to:
double Celcius2Fahrenheit(double celsius)
{
return celsius * 9 / 5 + 32;
}
To speed things up we can remove the division (compiler might do this too I know)
double Celcius2Fahrenheit(double celsius)
{
return 1.8 * celsius + 32;
}
If we need more speed we can move to the integer domain - many sensors offer only whole degrees
int Celcius2Fahrenheit(int celcius)
{
return celsius * 9 /5 + 32;
}
Problem with this function is that it has a (truncating) error for 4 out of 5 values. This error is max 0.8F and on average 0.4F.
We can decrease this error by adding rounding to the formula. (sneaked in a factor 2 to keep whole numbers)
int Celcius2Fahrenheit(int celcius)
{
return (celsius * 18 + 5)/10 + 32;
}
Still 4 out of 5 values have rounding errors but the error has decreased with almost a factor 2. Way better!
The absolute error is now max 0.4F and on average 0.24F. The cost is an extra addition.
Although we cannot improve on the precision, the extra addition introduced can be removed easily.
int Celcius2Fahrenheit(int celcius)
{
return (celsius * 18 + 325)/10; // note max celsius == 1820
}
And some people say the optimization of this relative simple conversion stops here. They are of course right and may skip the rest.
(is there anyone still left?)
When modelling the conversion function in Excel (undervalued tool for code analysis) it occurred to me that the integer versions of the function jumped with steps of 2F most of the time. This is because of the factor 9/5 which is almost 2. Then it occurred to me that when the temperature alternated between 20 and 21 C it caused jumps between 68 and 70F which indicates that 69F would be a better temperature in F.
The obvious solution is to let the "display" function take the average of the previous display value and the current reading. However that would lead to the following error:
assume the following data streams:
Celsius: ... 20 20 20 21 21 21 21
F = (F + C2F(t))/2;
Display: ... 68 68 68 69 69 69 !!! it never gets to 70 due to the integer truncating in average...oops!
OK we can try to fix this by using (F + C2F(t) + 1)/2;
Celsius: ... 20 20 20 21 21 21 21 20 20 20
F = (F + C2F(t) + 1)/2;
Display: ... 68 68 68 69 70 70 69 69 69 !!! 70 goes well now but it now it never gets back to 68 due to the +1 ... oops!
... that's not easy as expected....
As averaging after does not work we might consider doing averaging the Celsius part. But that won't work as the difference is already minimal (in integer domain).
So we must do the averaging IN the conversion function! This lead to the following code:
int Celcius2Fahrenheit(int prevCelsius, int celcius)
{
return ((prevCelsius + celcius) * 9 + 325)/10;
}
Celsius : ... 20 20 20 21 21 21 21 20 20 20 21 20 21 20
Fahrenheit: ... 68 68 69 70 70 70 69 68 68 69 69 69 69 !!!
It now looks like the conversion function remembers where it came from and uses that to calculate a better Fahrenheit temperature.
Of course one can adjust other weights to the two temperatures to reflect e.g. distance in time.
Conclusion: By keeping the previous Celsius value we can calculate a better Fahrenheit value.
update: first call (in setup) should be of course with twice the same initial Celsius value.