1

Note: I am coding the following in the Arduino variant of C++.

From a given float (always with four decimal digits), I wish to extract just the decimal part ("mantissa") as an integer.

So I attempted this method:

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:

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

Note that the latter two print out wrong; I'm guessing this is due to floating point precision issues.

What is an economical way to solve the above?

phuclv
  • 37,963
  • 15
  • 156
  • 475
boardbite
  • 259
  • 4
  • 17
  • Would using fixed point be a possibility? What range are you looking to represent? What are the sizes of `int`, `long`, and (if supported) `long long` in your compiler? Alternatively, does your compiler support `stdint.h` and, if so, what's the largest type in there? – jerry Jun 06 '13 at 16:30

3 Answers3

2

This is a cool question.

I will assume that ardiuno is using IEEE 754 when you set a float equal to 1234.5677 the number closest to that that fits in 4 bytes is 1.2345677490234375E3 which looks like 0x449A522B in hex.

but when you put 1234.5678 in to a float the best number it can form is 1.2345677490234375E3 Which is just short. In hex it is 0x449A522B.

So in short floats just can't store number that require number of digites you are using.

John b
  • 1,338
  • 8
  • 16
  • Thank you for the explanation of why this is happening. If not floats, then what other datatypes are there for this purpose? (Apart from strings, regarding which I've replied to the answer above.) – boardbite Jun 06 '13 at 15:47
  • Where do your numbers come from? Are you interested in the digits infront of the decimal point. There might be a nice way to use a long. Floats are really two numbers and the exponent really give a wide range, but it take bits away from the mantisa and you loose granularity. – John b Jun 06 '13 at 17:21
  • Good news. I've attempted two methods, including your suggestion of casting to a string. Both worked well -- first option, storage into a _string_ right from the original (external gage) measurement, and second option, storage into a long and performing arithmetic with it, and only then converting to a float. Both work satisfactorily, so consider this solved! – boardbite Jun 08 '13 at 02:21
  • Just thought I'd comment for those who are thinking of using doubles, in Arduino, double == float, so you will not get any further precision from using a double. – Ibraheem Oct 26 '13 at 02:15
1

The simplest way to avoid problems with floating point precision is to cast your number to a string and then only print what's after the point.

EDIT

I'm not sure how to do it with arduino's version of c++ so feel free to edit my answer to add an example everyone!

blue
  • 2,683
  • 19
  • 29
  • Casting to a string will work well, and it will be a pretty quick fix. I feel it would be better if you could avoid depending on the last digit in a float. – John b Jun 06 '13 at 15:32
  • Hmm, I tried the suggested approach: Used `char Buffer[20]; dtostrf (Value, 4, 4, Buffer); Serial.println(Buffer);` This still had precision errors, and printed **1234.5677**, **1234.5677**, and **1234.5679**, respectively. – boardbite Jun 06 '13 at 15:45
  • That is to be expected. The precision is lost when you save the data as a float, so save it to a float then changing the data to a string will not avail you. But depending on how you get the data you might be able to save it a string when you receive it. – John b Jun 06 '13 at 20:17
0

32-bit float has 23 bits of mantissa, so it can hold only about 7 digits of precision. Your example uses 8 digits, so the last one is just "garbage". You must use a higher precision floating-point type (which is double and long double in standard C++).

On Arduino depending on the variant you may have a standard compliant 64-bit double type or not. If double is exactly the same as float you have several solutions:

  • Write or use some double math library.
    • Or if double is just too costly on an 8-bit MCU like that you can create a custom floating-point type like 40-bit or 48-bit.
    • Or you can use float-float arithmetic type like this.
  • Use a fixed-point type. Since you always have 4 decimal digits this may suit you the most for this usecase.
    • You can use a Q26.6 or Q25.7 binary format for better arithmetic performance
    • Or better use a scaled decimal type with the last 4 digits as the fractional part for better decimal math output. In some cases you can also store the last 4 decimal digits and the integer part separately, but be careful with this implementation.

You can find out more information regarding fixed-point in this question, or in tag

phuclv
  • 37,963
  • 15
  • 156
  • 475
  • 1
    Arduino does not support doubles at all if a double means a datatype with higher precision than a float. A double == float for the Arduino. – Ibraheem Oct 26 '13 at 02:17
  • @Ibraheem it's not Arduino doesn't support doubles, it's the compiler has no double arithmetic libraries. With [arbitrary-precision arithmetic](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic) a computer can do operations on any precision as long as it has enough memory (and time) – phuclv May 30 '14 at 13:51
  • Arduino is software not hardware, albeit Arduino do make "Arduino" branded boards, but they are just implementations of Atmega chips. – Ibraheem May 30 '14 at 16:58
  • @Ibraheem whatever it is, you can write a 64-bit double library your own – phuclv May 31 '14 at 01:54