Long based Decimals

Evan Boldt's picture

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); }