Long based Decimals
Long based DecimalsIntroduction
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); }