Tuesday, February 2, 2010

Truncate in C# and C++

The Problem:  A colleague and I were working on a problem in .NET when trying to truncate a number.  The Math.Truncate method would cut off any digits beyond the decimal point without having the ability to specify the precision.

We needed a function that would truncate at various precision levels.

The Solution:  Our solution was to first multiply the number to truncate by the proper tens, hundreds, or thousands of the precision, use Math.Truncate and finally divide by the same to get our decimal position.

C# Code:
public static decimal Truncate(decimal num, int pos)
{
    int64 lDecimalPosition = 1;
    decimal tmp = 0;

    for (int i=0; i
    {
        lDecimalPosition *= 10;
    }
    
    // multiply by 10, 100, 1000, etc...
    tmp = num * lDecimalPosition;
    tmp = Math.Truncate(tmp);
    // divide by 10, 100, 1000, etc...
    tmp = tmp / lDecimalPosition;

    return tmp;
}

Next I tried to convert this to C++ as an experiment.  C++ does not have a Math.Truncate method so I needed to rely on implicit conversion.

The Bug:  C++ does not define the maximum values for types; they are defined by the compiler vendor and platform.  It is not possible to be certain that a large value using a double will not be truncated too short when it is implicitly converted to long long int.  As a matter of fact long long int is currently only supported by C++0x. (see the below function)

C++ Code:
double truncate(double z, int decimal)
{
    unsigned long lDecimalPosition = 1;
    unsigned long tmp = 0;
    long long int llMax = numeric_limits::max();
    
    for (int i=0; i<decimal; i++)
    {
        lDecimalPosition *= 10;
    }
    if (llMax < (z * lDecimalPosition) )
    {
        throw 10;
    }
    tmp = z * lDecimalPosition;

    /* explicit conversion to double to prevent 
    truncate after the division, otherwise 
    value would be truncated to long long int 
    automatically once the division has been 
    calculated. */
    z = (double) tmp / lDecimalPosition;
    return z;
}

WARNING: This code will function only with the simplest of double values passed in.  Since the type "Double" is stored as a floating point value there will be precision loss at some point.

A simple program demonstrates this:
#include <cstdlib>
#include <iostream>
#include <iomanip>
using namespace std;

int main(int argc, char** argv) {
    cout << setprecision(17);     
    cout << "1.1 as a double " << (double) 1.1 << endl;    
    return (EXIT_SUCCESS);
}

output:

1.1 as a double 1.1000000000000001

Ouch!