Introduction
Arduino has floats that are only accurate to about 6-7 digits. When combined with the fact that floats cannot represent certain decimal values well ( like .1 or .3 ) because they are expressed as negative powers of base 2, you may often see error - especially when full value is printed, or the output is truncated instead of rounded.
I have written a class here that basically stores a whole number into a long. Similar to a float, there is even an "exponent" field that says where the decimal point is in the whole number. Unlike a float, operations are done in base 10 (onto a base 2 number). In exchange for the flexibility of true floating point number in the range of possible values, we gain greater precision in the nearer to zero area - the area firmly within the range of the long (-2,147,483,648 to 2,147,483,647). The smaller the left hand side (LHS) of the decimal place is, the more precision is available for the right hand side (RHS) of the decimal.
Usage
So far, all I have is a really basic class that only handles the construcor and the bare minimum needed for printing.
#include "longDecimal.h"
void setup() {
Serial.begin(9600);
Serial.println( "BEGIN TEST:");
LongDecimal dec(131,158272);
Serial.print( dec.getLHS() );
Serial.print( '.' );
Serial.println( dec.getRHS() );
LongDecimal dec1(-131,158272);
Serial.print( dec1.getLHS() );
Serial.print( '.' );
Serial.println( dec1.getRHS() );
LongDecimal dec2(12345678,158272);
Serial.print( dec2.getLHS() );
Serial.print( '.' );
Serial.println( dec2.getRHS() );
}
void loop() {}
Output
BEGIN TEST: 131.1582720 -131.1582720 12345678.15
Class
You might be able to figure out what is going on just based on the documentation on the code for the class as it is:
#include <Arduino.h>
class LongDecimal {
private:
long decimal; //the left and right side of decimal
unsigned int decimalPlace; //where the decimal is in the above variable
public:
//constructors
LongDecimal( long lhs, long rhs );
LongDecimal( float f );
//type conversions
long getLHS();
long getRHS();
};
LongDecimal::LongDecimal( long lhs, long rhs ) {
decimalPlace = 0;
//shift lhs to the left in the long
long lhsSpace = abs(lhs);
long rhsSpace = 1;
// float the LHS all the way to the left of the long - may not be desirable
while ( 214748364L > lhsSpace ) { // maximum long divided by ten
lhsSpace *= 10L;
decimalPlace++;
rhsSpace *= 10L;
}
// put the LHS into the main decimal variable
decimal = lhs * rhsSpace;
// float the right hand side to align with LHS
//moves right if necessary - losing precision
while ( rhs > rhsSpace ) {
rhs /= 10;
}
//moves left if necessary
while ( rhs * 10L < rhsSpace ) {
rhs *= 10L;
}
// put the RHS into the main decimal variable
// add to the decimal if positive subtract if negative
if ( lhs < 0 ) {
decimal -= rhs;
}
else {
decimal += rhs;
}
}
//shifts decimal to the right until it is just the left hand side of the decimal
long LongDecimal::getLHS() {
long LHS = decimal;
for( int i = 0; i < decimalPlace; i++ ) {
LHS /= 10;
}
return LHS;
}
//removes the left hand side of the decimal by subtraction
long LongDecimal::getRHS() {
long LHS = getLHS(); //removes RHS, but is shifted right
//shift back to the left
for( int i = 0; i < decimalPlace; i++ ) {
LHS *= 10;
}
return abs(decimal) - abs(LHS);
}