12,622,741 members (27,460 online)
alternative version

164.8K views
22 bookmarked
Posted

# A Decimal Class Implementation

, 16 Feb 2003
 Rate this:
Use this class when high precision is required in basic numerical operations.

## Introduction

In my last article[^], I eluded to the fact that there was another bug hiding in my code. Well, here it is! I guess it isn't precisely a bug, except under certain conditions, such as working with currency, in which precision is required.

## Math Precision

One of the problems in the financial world is dealing with numeric precision. The `double` data type doesn't quite cut it, as we shall see. This presents a problem in C++ which has no `decimal` data type as found in C#. For example, the following C# code results in a "true" evaluation of `n==201`:

```decimal n=.3M;
n-=.099M;
n*=1000M;
if (n==201) ...   // TRUE!```

whereas using the `double` type in C++ does not:

```double n=.3;
n-=.099;
n*=1000;
if (n==201) ...   // FALSE!```

This is an important issue when dealing with financial math.

## The Decimal Class

To solve this problem, I created a `Decimal` class. Now, I looked high and low on Code Project and Google searches for something like this, and I didn't find anything, so if I missed a contribution by another person regarding this issue, then I apologize in advance.

```class Decimal
{
public:

static void Initialize(int precision);

Decimal(void);
Decimal(AutoString num);
Decimal(const Decimal& d);
Decimal(const __int64 n);
Decimal(int intPart, int fractPart);

virtual ~Decimal(void);

Decimal operator+(const Decimal&);
Decimal operator-(const Decimal&);
Decimal operator*(const Decimal&);
Decimal operator/(const Decimal&);

Decimal operator +=(const Decimal&);
Decimal operator -=(const Decimal&);
Decimal operator *=(const Decimal&);
Decimal operator /=(const Decimal&);

bool operator==(const Decimal&) const;
bool operator!=(const Decimal&) const;
bool operator<(const Decimal&) const;
bool operator<=(const Decimal&) const;
bool operator>(const Decimal&) const;
bool operator>=(const Decimal&) const;

CString ToString(void) const;
double ToDouble(void) const;

protected:

__int64 n;

static int precision;
static __int64 q;
};```

This is a pretty basic implementation. A static `Initialize` method is used to set up the desired precision of the class, for all instances of `Decimal`. Internally, a few helper variables are initialized, which are used elsewhere for `string` to `Decimal` conversions and the multiplication and division operators:

```void Decimal::Initialize(int prec)
{
precision=prec;
// create an array of 0's for padding
// get fractional precision
q=(__int64)pow(10.0, (double)prec);
}```

A Microsoft specific 64 bit integer is used to maintain both integer and fractional components of the value, using the `__int64` data type. This is a non-ANSII standard type. If you want a 96 bit integer instead, you can modify my class with PJ Naughter's 96 bit integer class found here [^].

## String To __int64 Conversion

```Decimal::Decimal(AutoString num)
{
// get the integer component
AutoString intPart=num.LeftEx('.');
// get the fractional component
AutoString fractPart=num.RightEx('.');

// "multiply" the fractional part by the desired precision

// create the 64bit integer as a composite of the
// integer and fractional components.
n=atoi(intPart);
n*=q;
n+=atoi(fractPart);
}```

The conversion from a string to a 64 bit integer is interesting to look at as it reveals the internal workings of the class. First, the integer part and fractional parts are separated from the number. The `AutoString` class is a `CString` derived class and provides a bit nicer interface for these kind of things.

For example, given "123.456":

```AutoString intPart=num.LeftEx('.');
AutoString fractPart=num.RightEx('.');
```
```intPart="123"
fractPart="456"```

Now let's say you've initialized the class with a precision of 4 digits past the decimal point. This creates a pad string of "0000" in the initialization function, which is used to determine how many zeros to append to the fractional string. In code:

```fractPart+=&pad[strlen(fractPart)];
```

`fractPart` is appended with a single "0" and becomes "4560".

Finally, the two components, the integer and fractional components, are combined by shifting (base 10) the integer component left by the fractional precision and adding the fractional component:

```n=atoi(intPart);
n*=q;
n+=atoi(fractPart);
```

The result is a single integer which maintains both integer and fractional components. Because all `Decimal` "numbers" are normalized in this way, the four basic operations (+, -, *, /) are trivial to implement.

## __int64 To String Conversion

```CString Decimal::ToString(void) const
{
char s[64];
__int64 n2=n/q;
int fract=(int)(n-n2*q);
sprintf(s, "%d.%0*d", (int)n2, precision, fract);
return s;
}```

Again, this code reveals the internal workings of the `Decimal` class. The 64 bit value is shifted right (base 10) by the precision and the integer component is extracted:

```__int64 n2=n/q;
```

The fractional component is extracted by shifting left the integer component and subtracting from the original value:

```int fract=(int)(n-n2*q);
```

And finally the string is constructed. Note the use of the `*` directive which tells the `printf` routine to determine the precision of the integer from the variable list:

```sprintf(s, "%d.%0*d", (int)n2, precision, fract);
```

## Usage

```Decimal::Initialize(4);
double n=.3;
n-=.099;
n*=1000;

printf("n=%.04lf  (int)n=%d\r\n", n, (int)n);
printf("n == 201 ? %s\r\n", n==201 ? "yes" : "no");
printf("n >= 201 ? %s\r\n", n>=201 ? "yes" : "no");

Decimal dec(".3");
dec-=Decimal(".099");
dec*=Decimal("1000");

printf("dec=%s\r\n", dec.ToString());
printf("dec == 201 ? %s\r\n", dec==Decimal("201") ? "yes" : "no");
printf("dec >= 201 ? %s\r\n", dec>=Decimal("201") ? "yes" : "no");```

The above is an example usage and produces the following output:

Because the `__int64` type is composed from two `int` types (and decomposed into `int` types when converted back to a string), it is limited in range to the same values as a signed four byte number, +/- 2^31, or +/-2,147,483,648.

Also, this code is not "internationalized".

## References:

A list of licenses authors might use can be found here

## Share

 United States
Marc is the creator of two open source projects, MyXaml, a declarative (XML) instantiation engine and the Advanced Unit Testing framework, and Interacx, a commercial n-tier RAD application suite.  Visit his website, www.marcclifton.com, where you will find many of his articles and his blog.

Marc lives in Philmont, NY.

## You may also be interested in...

 Pro

 First Prev Next
 How to deal with multiplication/division? DimoneSem10-Aug-15 3:30 DimoneSem 10-Aug-15 3:30
 and what about the Automation DECIMAL struct abo hashem12-Sep-13 2:06 abo hashem 12-Sep-13 2:06
 Re: and what about the Automation DECIMAL struct Marc Clifton12-Sep-13 2:17 Marc Clifton 12-Sep-13 2:17
 Re: and what about the Automation DECIMAL struct brekehan12-Nov-14 13:56 brekehan 12-Nov-14 13:56
 Re: and what about the Automation DECIMAL struct Marc Clifton12-Nov-14 14:11 Marc Clifton 12-Nov-14 14:11
 My vote of 1 Byron Goodman4-Jul-12 13:34 Byron Goodman 4-Jul-12 13:34
 Another implementation for C++ Member 9427193-Jan-11 14:31 Member 942719 3-Jan-11 14:31
 This code needs a health warning cslocombe27-Jul-08 21:29 cslocombe 27-Jul-08 21:29
 Re: This code needs a health warning Marc Clifton28-Jul-08 1:43 Marc Clifton 28-Jul-08 1:43
 Re: This code needs a health warning brekehan12-Nov-14 13:58 brekehan 12-Nov-14 13:58
 Boost.Rational Gast1284-Feb-08 5:02 Gast128 4-Feb-08 5:02
 Looking at your article I see a resemblance with Boost.Rational (www.boost.org/libs/rational/index.html). This rational class is probably also resilient against precision loss.Example would then be:#include #include void Test(){ typedef boost::rational RationalType; RationalType n1(3, 10); n1 /= RationalType(99, 1000); n1 *= 1000; std::cout << "n1 = " << n1 << std::endl;}
 compare floats jswoofer13-Oct-06 4:18 jswoofer 13-Oct-06 4:18
 Why do we need decimal? Andrew Phillips8-Dec-04 16:14 Andrew Phillips 8-Dec-04 16:14
 Re: Why do we need decimal? Marc Clifton8-Dec-04 16:56 Marc Clifton 8-Dec-04 16:56
 Re: Why do we need decimal? Andrew Phillips13-Dec-04 16:11 Andrew Phillips 13-Dec-04 16:11
 Improvement Suggestion Jörgen Sigvardsson5-Dec-03 9:26 Jörgen Sigvardsson 5-Dec-03 9:26
 C# ? Richard Deeming18-Feb-03 0:02 Richard Deeming 18-Feb-03 0:02
 Re: C# ? Marc Clifton18-Feb-03 2:48 Marc Clifton 18-Feb-03 2:48
 Precision Taka Muraoka17-Feb-03 13:18 Taka Muraoka 17-Feb-03 13:18
 Re: Precision Marc Clifton17-Feb-03 14:24 Marc Clifton 17-Feb-03 14:24
 Interesting Jörgen Sigvardsson17-Feb-03 12:59 Jörgen Sigvardsson 17-Feb-03 12:59
 Re: Interesting Peter Hancock17-Feb-03 13:28 Peter Hancock 17-Feb-03 13:28
 Re: Interesting Jörgen Sigvardsson17-Feb-03 14:04 Jörgen Sigvardsson 17-Feb-03 14:04
 Re: Interesting Marc Clifton17-Feb-03 14:29 Marc Clifton 17-Feb-03 14:29
 Re: Interesting Jörgen Sigvardsson17-Feb-03 14:41 Jörgen Sigvardsson 17-Feb-03 14:41
 Re: Interesting Peter Hancock17-Feb-03 15:30 Peter Hancock 17-Feb-03 15:30
 Re: Interesting Jörgen Sigvardsson17-Feb-03 23:41 Jörgen Sigvardsson 17-Feb-03 23:41
 Re: Interesting Peter Hancock18-Feb-03 0:05 Peter Hancock 18-Feb-03 0:05
 Re: Interesting Marc Clifton19-Feb-03 7:41 Marc Clifton 19-Feb-03 7:41
 Re: Interesting Taka Muraoka17-Feb-03 19:04 Taka Muraoka 17-Feb-03 19:04
 Re: Interesting Marc Clifton17-Feb-03 14:31 Marc Clifton 17-Feb-03 14:31
 Re: Interesting Jörgen Sigvardsson17-Feb-03 14:46 Jörgen Sigvardsson 17-Feb-03 14:46
 Re: Interesting Marc Clifton17-Feb-03 14:55 Marc Clifton 17-Feb-03 14:55
 Re: Interesting Jörgen Sigvardsson17-Feb-03 23:55 Jörgen Sigvardsson 17-Feb-03 23:55
 Re: Interesting Kris Vandermotten8-Oct-03 7:11 Kris Vandermotten 8-Oct-03 7:11
 Re: Interesting Jörgen Sigvardsson23-Feb-03 14:21 Jörgen Sigvardsson 23-Feb-03 14:21
 Re: Interesting brekehan12-Nov-14 14:01 brekehan 12-Nov-14 14:01
 Re: Interesting Marc Clifton12-Nov-14 14:15 Marc Clifton 12-Nov-14 14:15
 Re: Interesting Jörgen Sigvardsson9-Dec-14 2:00 Jörgen Sigvardsson 9-Dec-14 2:00
 Last Visit: 31-Dec-99 19:00     Last Update: 3-Dec-16 15:24 Refresh 1