Pages: [1] 2   Go Down
Author Topic: Extracting just the decimal part of a float  (Read 2141 times)
0 Members and 1 Guest are viewing this topic.
Portland, OR
Offline Offline
Sr. Member
****
Karma: 7
Posts: 260
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
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:
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?
« Last Edit: June 06, 2013, 09:58:08 am by giantsfan3 » Logged

Offline Offline
Edison Member
*
Karma: 48
Posts: 1632
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Pete
Logged

Portland, OR
Offline Offline
Sr. Member
****
Karma: 7
Posts: 260
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
int DecimalPart = 10000 * (Value - IntegerPart + 0.00005);

Pete
Logged

Offline Offline
God Member
*****
Karma: 25
Posts: 526
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Fort Lauderdale, FL
Offline Offline
Faraday Member
**
Karma: 71
Posts: 6144
Baldengineer
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

Capacitor Expert By Day, Enginerd by night.  ||  Personal Blog: www.baldengineer.com  || Electronics Tutorials for Beginners:  www.addohms.c

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 614
Posts: 49365
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Cincinnati, OH
Online Online
God Member
*****
Karma: 46
Posts: 796
I'm not bossy...I just know what you should be doing.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Portland, OR
Offline Offline
Sr. Member
****
Karma: 7
Posts: 260
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Global Moderator
Dallas
Online Online
Shannon Member
*****
Karma: 207
Posts: 12919
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


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

0
Offline Offline
Sr. Member
****
Karma: 8
Posts: 394
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Copenhagen, Denmark
Offline Offline
Edison Member
*
Karma: 32
Posts: 1214
Have you testrun your INO file today?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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).
Logged

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 217
Posts: 13718
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

OP original code:
Code:
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:
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?
Logged

Rob Tillaart

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

Copenhagen, Denmark
Offline Offline
Edison Member
*
Karma: 32
Posts: 1214
Have you testrun your INO file today?
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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)
Logged

Portland, OR
Offline Offline
Sr. Member
****
Karma: 7
Posts: 260
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@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?
Logged

SF Bay Area (USA)
Offline Offline
Tesla Member
***
Karma: 133
Posts: 6755
Strongly opinionated, but not official!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

Pages: [1] 2   Go Up
Jump to: