Go Down

Topic: Extracting just the decimal part of a float (Read 2801 times) previous topic - next topic

giantsfan3

Jun 06, 2013, 04:52 pm Last Edit: Jun 06, 2013, 04:58 pm by giantsfan3 Reason: 1
I am having a bit of trouble with the floating precision of float; never thought I would need this many decimal digits of precision but today it happened!

My goal, from a given float (always with four decimal digits), is to extract just the decimal part ("mantissa") as an integer.

So I attempted this method:
Code: [Select]
void ExtractDecimalPart(float Value) {
  int IntegerPart = (int)(Value);
  int DecimalPart = 10000 * (Value - IntegerPart); //10000 b/c my float values always have exactly 4 decimal places
  Serial.println (DecimalPart);
}


But the above results in the following (note that the latter two print out wrong):
Code: [Select]

ExtractDecimalPart (1234.5677); //prints 5677
ExtractDecimalPart (1234.5678); //prints 5677
ExtractDecimalPart (1234.5679); //prints 5678


What can I do to solve this in an economical way?

el_supremo

Many decimal fractions cannot be represented exactly in binary. The fix is to round the number. Try this
Code: [Select]
int DecimalPart = 10000 * (Value - IntegerPart + 0.00005);

Pete

giantsfan3

Pete, This fails to do the trick because the the 0.00005 addition (to the float) has an uneven effect depending on each different float. E.g., in this case, it resulted in the last two values still being mis-extracted.


Many decimal fractions cannot be represented exactly in binary. The fix is to round the number. Try this
Code: [Select]
int DecimalPart = 10000 * (Value - IntegerPart + 0.00005);

Pete

TanHadron

I'm afraid you have exhausted the accuracy of the float.  It just can't tell the difference between those numbers.  The binary representation just ran out of bits.

The hexadecimal representation of 1234.5677 is 0x449A522B.  1234.5678 is 0x449A522B. 1234.5679 is 0x449A522C.

In order to get more accuracy, you're going to have to use more space.  Normally in C, you could just double the accuracy by declaring them to be doubles, but unfortunately when I tried that it didn't make one bit of difference.  Literally.  Apparently doubles are implemented as floats.  That's useless.

You could try implementing your algorithm as long integers and just multiply everything by 10000L, but you're getting close to the limits there as well.

James C4S


Apparently doubles are implemented as floats. 

Yes, which is why it is indicated on the reference page for a double. 
http://arduino.cc/en/Reference/Double

It's how avr-gcc is implemented.



Pete, This fails to do the trick because the the 0.00005 addition (to the float) has an uneven effect depending on each different float.

As indicated on the reference page for a float, there are only 6-7 digits of precision.  This includes digits before and after the decimal point.
http://arduino.cc/en/Reference/Float
Capacitor Expert By Day, Enginerd by night.  ||  Personal Blog: www.baldengineer.com  || Electronics Tutorials for Beginners:  www.addohms.com

PaulS

Quote
My goal, from a given float (always with four decimal digits),

This is where you are going wrong. You may always be printing the value with 4 digits after the decimal point, but that does not mean that the digits are accurate or correct.

You could print the value with 12 digits after the decimal point, and be assured that at least 6 of them are meaningless.

econjack

For what it's worth, I just compiled a test program using the ChipKit board (32 bit) from Diligent. Their IDE/compiler works with every Arduino sketch I've tried, so I thought it might it might be a solution. In the test, a float is 4 bytes, a double is 4 bytes, but a long double is 8 bytes. I did a small test where I divided 30001 by 3, assigned the result into a long double and displayed the answer using sprintf. (printf did not know what to do with the long double, so I copied the division result into a character array.) Instead of seeing 10000.3333333 the result was 10000.333008. It appears that their compiler doesn't have the precision you need, either.

giantsfan3

Yes, (across many languages) floating point arithmetic in general seems to be have 6-7 total digits of precision.
I've decided to store the data into a string/char-array right at the offset, then either convert it into a long and do arithmetic with it, or else directly store as a string.

Coding Badly


Or, you could give this a spin...
http://forum.arduino.cc/index.php?topic=85692.0

joe mcd

Since the microcontroller treats every number as a binary integer and merely fakes floating point, you can do the same.  You should simply avoid floating point entirely.

Msquare


Since the microcontroller treats every number as a binary integer and merely fakes floating point, you can do the same.  You should simply avoid floating point entirely.
ehhrrr?
Any and every computer treats every number as binary. Binary is all there is. All this Hex float int double is conversion to and from an ASCII picture of the number. (We could get real philosphical and ask what is "2.5" actually? It is an abstract entity)

Anyhow, where is this enormous precision needed? (asking the original poster). Anything you measure is only going to be accurate to about 3½ digits if it is measured directly by the arduino chip, possibly more with a calibrated external sensor (that sends a long bit/byte/ASCII stream with the value - if it is a proportional voltage we're limited by the ADC again).

About the only thing we can do more accuracy is counting pulses or suchlike. Counting is acurate to as long as you care to count (whole positive numbers).

robtillaart

OP original code:
Code: [Select]
void ExtractDecimalPart(float Value) {
  int IntegerPart = (int)(Value);
  int DecimalPart = 10000 * (Value - IntegerPart); //10000 b/c my float values always have exactly 4 decimal places
  Serial.println (DecimalPart);
}


I would rewrite it using longs as ints have a smaller range, and use a param for the # decimals
Code: [Select]

void ExtractDecimalPart(float Value, int numberOfDecimals)
{
  float temp = Value - (long)(Value); 
  long p = 1;
  for (int i=0; i< numberOfDecimals; i++) p*=10;
  long DecimalPart = p * temp;
  Serial.println (DecimalPart);
}


with respect to the problem:
Is there any way to reduce the integral part so the float uses its significant digits for the decimal part?
What is the max integral part?
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Msquare


All this Hex float int double is conversion to and from an ASCII picture of the number.
Wrote this in slight haste. Just to make sure - there is a different internal binary representation for integers and floats. The AVR chip does not have floats, handling them is done by code (ie. it calls functions to do it)

giantsfan3

@Msquare: That much precision is needed (and exists) here because the value being collected (via RS232) is coming in the form of a serial String, as a number with that many decimal points. Then I do some computation with it and, based on the computation, make decisions as well as send the value (or else a message) via an Xbee to a second Xbee.

@robtillaart: Hmm, I didn't consider that possibility; for some reason, I thought the float type would split the significant digits between the integral part and the decimal part in a consistent manner (as opposed to one maximum constraint on the ENTIRE number of digits).
The maximum integral part is 999, so let me give this a shot.



with respect to the problem:
Is there any way to reduce the integral part so the float uses its significant digits for the decimal part?
What is the max integral part?

westfw

Quote
I thought the float type would split the significant digits between the integral part and the decimal part in a consistent manner

It does, internally.   All floating point numbers (on almost ALL architectures) end up represented as normalized "scientific notation" ((1 + .bbbb) * base^exp) - a fraction between one and base, times the base raised to an exponent.  So when you're not looking, ALL the digits are in the "fraction" part (the extra "1" is assumed, and doesn't actually occur in storage...)

http://en.wikipedia.org/wiki/Single_precision

Go Up