|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionIn 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 PrecisionOne of the problems in the financial world is dealing with numeric precision. The decimal n=.3M;
n-=.099M;
n*=1000M;
if (n==201) ... // TRUE!
whereas using the double n=.3; n-=.099; n*=1000; if (n==201) ... // FALSE! This is an important issue when dealing with financial math. The Decimal ClassTo solve this problem, I created a 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;
static char* pad;
};
This is a pretty basic implementation. A static void Decimal::Initialize(int prec)
{
precision=prec;
// create an array of 0's for padding
pad=new char[precision+1];
memset(pad, '0', precision);
pad[precision]='\0';
// 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
String To __int64 ConversionDecimal::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
fractPart+=&pad[strlen(fractPart)];
// 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 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)];
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 __int64 To String ConversionCString 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 __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 sprintf(s, "%d.%0*d", (int)n2, precision, fract);
UsageDecimal::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 Also, this code is not "internationalized". References:
|
||||||||||||||||||||||