low float precision

Hi all,
I'm trying to measure a temp sensor getting his voltage value with arduino uno.
The formula to convert voltage to temp is something like that:

float temp = -1.977905036pow(10,-1)pow(volts,10)+5.021263808pow(volts,9)-54.79044923pow(volts,8)+335.8665566pow(volts,7)-1268.136896pow(volts,6)+3037.582741pow(volts,5)-4566.827221pow(volts,4)+4061.676731pow(volts,3)-1782.286594pow(volts,2)+53.30656433*volts+342.0406525;

It's quite complex and I need that all this float operations (volts variable is a float number) be precise.
I'm getting strange temp values and after making some debugging I have seen that arduino does not store floats in the same precision as a pc.
I ended having this:

float temp = -1.977905036;
Serial.println(temp,20);

Wich outputs -1.97790508270263671875
It's obvious that seems that some of the last values are interpreted :(.
What can I do to gain precision?

A float is 32-bits which gives a precision of six or seven digits. Printing any more than that is a waste of time.

You would need to use a double to get better precision (about 16 digits) but if you're using a Nano/UNO or similar Arduino, a double is the same as float and you only get the 32-bit float.

Pete

have seen that arduino does not store floats in the same precision as a pc.

An Arduino "float" is exactly the same size and format as a "float" on my PCs, it's just "double"s that are different.

you can apply Horners method to do less math and thus get best precision you can get. Remember every float operation introduces an error.

float temp = -1.977905036*pow(10,-1)*pow(volts,10)+5.021263808*pow(volts,9)-54.79044923*pow(volts,8)+335.8665566*pow(volts,7)-1268.136896*pow(volts,6)+3037.582741*pow(volts,5)-4566.827221*pow(volts,4)+4061.676731*pow(volts,3)-1782.286594*pow(volts,2)+53.30656433*volts+342.0406525;

==>

float temp = calcTemp(volts);
...
float calcTemp(float volts)
{
  float v = volts;

  float rv = 342.0406525;
  rv += 53.30656433 * v; // pow(volts, 1)
  v*= volts;
  rv += -1782.286594 * v;  // 2
  v*= volts;
  rv += +4061.676731 * v;
  v*= volts;
  rv += -4566.827221 * v;
  v*= volts;
  rv += 3037.582741 * v;
  v*= volts;
  rv += -1268.136896 * v;
  v*= volts;
  rv += +335.8665566 * v;
  v*= volts;
  rv += -54.79044923 * v;
  v*= volts;
  rv += +5.021263808 * v;
  v*= volts;
  rv += -0.1977905036 * v;  // 10

  return rv;
}

Note: the order of evaluation is reverse but that made it possible to replace all the calls to pow() with a more robust multiply.

give it a try

(but still floats are only six decimals accurate)


The way to minimize the rounding error is to calculate every term - a*pow(v, x) - separately and put them in an array,
sort the array from small to large and then add them. The price is that this is time intensive.

Or you could use NIck Gammons port of the big number library

el_supremo:
A float is 32-bits which gives a precision of six or seven digits. Printing any more than that is a waste of time.

You would need to use a double to get better precision (about 16 digits) but if you're using a Nano/UNO or similar Arduino, a double is the same as float and you only get the 32-bit float.

Pete

Printing more than six digits has been the perfect test to see that in some way that the float value I'm trying to store is not the same that arduino actually stores.

AWOL:
An Arduino "float" is exactly the same size and format as a "float" on my PCs, it's just "double"s that are different.

Ok, but why If I store that float when I try to print that stored value it's not the same? That doesn't happen with python in a pc for example and that's why I am saying that seems that float in arduino are not the same in a pc.

robtillaart:
you can apply Horners method to do less math and thus get best precision you can get. Remember every float operation introduces an error.

float temp = -1.977905036*pow(10,-1)*pow(volts,10)+5.021263808*pow(volts,9)-54.79044923*pow(volts,8)+335.8665566*pow(volts,7)-1268.136896*pow(volts,6)+3037.582741*pow(volts,5)-4566.827221*pow(volts,4)+4061.676731*pow(volts,3)-1782.286594*pow(volts,2)+53.30656433*volts+342.0406525;

==>

float temp = calcTemp(volts);

...
float calcTemp(float volts)
{
  float v = volts;

float rv = 342.0406525;
  rv += 53.30656433 * v; // pow(volts, 1)
  v*= volts;
  rv += -1782.286594 * v;  // 2
  v*= volts;
  rv += +4061.676731 * v;
  v*= volts;
  rv += -4566.827221 * v;
  v*= volts;
  rv += 3037.582741 * v;
  v*= volts;
  rv += -1268.136896 * v;
  v*= volts;
  rv += +335.8665566 * v;
  v*= volts;
  rv += -54.79044923 * v;
  v*= volts;
  rv += +5.021263808 * v;
  v*= volts;
  rv += -0.1977905036 * v;  // 10

return rv;
}



Note: the order of evaluation is reverse but that made it possible to replace all the calls to pow() with a more robust multiply.

give it a try 

(but still floats are only six decimals accurate)


---


_The way to minimize the rounding error is to calculate every term - a*pow(v, x) - separately and put them in an array,_ 
*sort the array from small to large and then add them. The price is that this is time intensive.*

Yep mate! I think I have studied this some years ago!!
Seems that is working better! will make a few tests more to see what's the loss now.
Time is not a problem here.

robtillaart:
Or you could use NIck Gammons port of the big number library

I tried but I think it doesn't work for floats... or are you thinking in multiply the values to make them as integer?

Ok, but why If I store that float when I try to print that stored value it's not the same?

OK, prove it

AWOL:
OK, prove it

look at my first post:
I tried to store -1.977905036 and when I print the value with 20 decimal points outputs -1.97790508270263671875

No, I mean prove it.

-1.977905036 != -1.97790508270263671875

I'm still not seeing a proof - you know, "QED" and all that stuff.

You still don't seem to have grasped the point that there are only about 6 or 7 digits of precision in a float.
-1.977905036 is ten digits of precision. Of those, only the first seven are significant in a 32-bit float and the rest are not stored. So your number is stored as approximately -1.977905 and when you print it, trying to print more than those seven digits is meaningless.

Pete

for one decimal digit a binary representation needs 2log(10) bits ~ 3.322 bits
the mantisse in a IEE754 float is 23 bits.

That means the mantisse can hold max 23/3.322 ~ 6.924 decimal digits, all other are discarded.
(as in fact the first bit is always a 1 one could say 24/3.322 ~ 7.225 decimal digits)

1.977905036 has 10 digits so the float will represent the number nearest to 1.977905.

Then printing it again involves converting that binary representation to decimals again, meaning multiplications by 10 to split of digits (among other math). These multiplications introduce rounding errors of their own and will in the end generate "additional decimals"

"Floating point numbers are like piles of sand; every time you move one you lose a little sand and pick up a little dirt."

If you think you need better precision than that, for measuring temperature, you are deluded. Your measurement device is unlikely to be so accurate, anyway.

-1.977905036*pow(10,-1)*pow(volts,10)

What is this piece of crap supposed to calculate ?

pow(10,-1) ????

Really ?

Have you actually looked at the behavior of your ridiculous polynomial ?

What range of volts do you expect to be measuring ?

Do you have an A/D converter that is precise to at least 6 decimal digits over that voltage range ?

Do know how the pow( ) function is implemented, or how accurate it is ?

DiSCLAiMER:
...
Ok, but why If I store that float when I try to print that stored value it's not the same? That doesn't happen with python in a pc for example and that's why I am saying that seems that float in arduino are not the same in a pc.

Yep mate! I think I have studied this some years ago!!
Seems that is working better! will make a few tests more to see what's the loss now.
Time is not a problem here.
I tried but I think it doesn't work for floats... or are you thinking in multiply the values to make them as integer?

A float in a PC is most often a (64bit) double and internally in the processor it is even an 80 bit IIRC.

On a typical machine running Python, there are 53 bits of precision available for a Python float,
- 14. Floating Point Arithmetic: Issues and Limitations — Python 2.7.18 documentation -

enough said,
did you do the accuracy tests already?
Where did you get the formula from?

DiSCLAiMER,
what sensor are you using?

Take in account that your Arduino A/D converter has only 1024 values from 0 to 1023.

el_supremo:
You still don't seem to have grasped the point that there are only about 6 or 7 digits of precision in a float.
-1.977905036 is ten digits of precision. Of those, only the first seven are significant in a 32-bit float and the rest are not stored. So your number is stored as approximately -1.977905 and when you print it, trying to print more than those seven digits is meaningless.

Pete

robtillaart:
for one decimal digit a binary representation needs 2log(10) bits ~ 3.322 bits
the mantisse in a IEE754 float is 23 bits.

That means the mantisse can hold max 23/3.322 ~ 6.924 decimal digits, all other are discarded.
(as in fact the first bit is always a 1 one could say 24/3.322 ~ 7.225 decimal digits)

1.977905036 has 10 digits so the float will represent the number nearest to 1.977905.

Then printing it again involves converting that binary representation to decimals again, meaning multiplications by 10 to split of digits (among other math). These multiplications introduce rounding errors of their own and will in the end generate "additional decimals"

"Floating point numbers are like piles of sand; every time you move one you lose a little sand and pick up a little dirt."

Thank guy guys, now I'm beging to understand where those last decimals come from, so basically it's a behavoiur of "casting" and "decasting" a number.

michinyon:
What is this piece of crap supposed to calculate ?

pow(10,-1) ????

Really ?

Well, -1.977905036*pow(10,-1) can be probably substitute by -0.1977905036 and will save us from calculate a silly operation.

michinyon:
Have you actually looked at the behavior of your ridiculous polynomial ?

yes, It's a nice curve, what's the problem?

michinyon:
What range of volts do you expect to be measuring ?

michinyon:
Do you have an A/D converter that is precise to at least 6 decimal digits over that voltage range ?

from 0 to 5v

michinyon:
Do know how the pow( ) function is implemented, or how accurate it is ?

no idea

robtillaart:
A float in a PC is most often a (64bit) double and internally in the processor it is even an 80 bit IIRC.

On a typical machine running Python, there are 53 bits of precision available for a Python float,
- 14. Floating Point Arithmetic: Issues and Limitations — Python 2.7.18 documentation -

enough said,
did you do the accuracy tests already?
Where did you get the formula from?

So, in python I don't have no problem because can store more decimals than in arduino right?

I have to test more to see if Horner solves my problem, hope this week get time.

zoomx:
DiSCLAiMER,
what sensor are you using?

Take in account that your Arduino A/D converter has only 1024 values from 0 to 1023.

The temperature sensor is an automotive fluid temperature sensor. The brand gives a table with the resistance the sensor has depending of the temperature exposed, so, as we can not measure resistance directly, I used a simple voltage divisor circuit to be able to measure resistance.

So the values of the table are interpolated in a grade 10 polynomial to convert voltage to temperature.

Yes, I know, so instead of using the arduino uno adc i'm using a 12bit adc mcp3008, so I have values from 0 to 4095.

And what about this

Python code:
adc=3669
volts=adc*(5.0/4095.0)
temp = -1.977905036pow(10,-1)pow(volts,10)+5.021263808pow(volts,9)-54.79044923pow(volts,8)+335.8665566pow(volts,7)-1268.136896pow(volts,6)+3037.582741pow(volts,5)-4566.827221pow(volts,4)+4061.676731pow(volts,3)-1782.286594pow(volts,2)+53.30656433*volts+342.0406525
print temp

output: 38.5857557064

Arduino code:
void setup() {
Serial.begin(9600);
}

void loop() {
int adc = 3669;
float volts = adc*(5.0/4095.0);
float temp = -1.977905036pow(10,-1)pow(volts,10)+5.021263808pow(volts,9)-54.79044923pow(volts,8)+335.8665566pow(volts,7)-1268.136896pow(volts,6)+3037.582741pow(volts,5)-4566.827221pow(volts,4)+4061.676731pow(volts,3)-1782.286594pow(volts,2)+53.30656433*volts+342.0406525;
Serial.println(temp);
}

output: 37.70

have you checked multimap() to interpolate between values