Decimal 类实现






4.77/5 (15投票s)
2003年2月17日
3分钟阅读

215138

1400
当基本数值运算需要高精度时,请使用此类。
引言
在我上一篇文章[^]中,我曾提到我的代码中隐藏着另一个bug。好吧,它在这里!我猜这不完全算是一个bug,除非在某些特定情况下,比如处理需要精度的货币时。
数学精度
金融领域的一个问题是处理数值精度。double
数据类型并不完全适合,正如我们将看到的。这在C++中成了一个问题,它没有像C#中那样的decimal
数据类型。例如,下面的C#代码会得到一个“true”的求值n==201
decimal n=.3M;
n-=.099M;
n*=1000M;
if (n==201) ... // TRUE!
而在C++中使用double
类型则不行
double n=.3; n-=.099; n*=1000; if (n==201) ... // FALSE!
在处理金融数学时,这是一个重要的问题。
Decimal类
为了解决这个问题,我创建了一个Decimal
类。我在Code Project和Google搜索中到处寻找类似的东西,但没有找到,所以如果我遗漏了其他人的贡献,我提前表示歉意。
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;
};
这是一个非常基础的实现。静态的Initialize
方法用于设置类的期望精度,该精度适用于所有Decimal
实例。内部初始化了一些辅助变量,这些变量用于string
到Decimal
的转换以及乘法和除法运算符。
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);
}
微软专用的64位整数被用来维护值的整数部分和小数部分,使用__int64
数据类型。这是一个非ANSII标准类型。如果你想要一个96位整数,你可以用PJ Naughter的96位整数类修改我的类,该类可以在这里找到[^]。
字符串转__int64转换
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
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);
}
从字符串到64位整数的转换很有趣,因为它揭示了类的内部工作原理。首先,整数部分和小数部分从数字中分离出来。AutoString
类是CString
的派生类,为这类事情提供了更友好的接口。
例如,给定"123.456"
AutoString intPart=num.LeftEx('.'); AutoString fractPart=num.RightEx('.');
intPart="123"
fractPart="456"
现在假设你已经将类的精度初始化为小数点后4位。这会在初始化函数中创建一个"0000"的填充字符串,用于确定需要向小数字符串附加多少个零。在代码中
fractPart+=&pad[strlen(fractPart)];
fractPart
会附加一个"0"变成"4560"。
最后,将整数部分和小数部分两个组件组合起来,方法是将整数部分向左(基数10)移动小数精度,然后加上小数部分。
n=atoi(intPart);
n*=q;
n+=atoi(fractPart);
结果是一个单一的整数,它同时维护了整数和小数部分。因为所有Decimal
“数字”都以这种方式归一化,所以四种基本运算(+、-、*、/)的实现非常简单。
__int64转字符串转换
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;
}
同样,这段代码也揭示了Decimal
类的内部工作原理。64位值向右(基数10)移动精度,并提取整数部分。
__int64 n2=n/q;
通过将整数部分向左移位并从原始值中减去,提取小数部分。
int fract=(int)(n-n2*q);
最后构造字符串。注意printf
例程中使用的*
指令,它告诉printf
根据变量列表确定整数的精度。
sprintf(s, "%d.%0*d", (int)n2, precision, fract);
用法
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");
上面的例子展示了用法,并产生了以下输出
因为__int64
类型由两个int
类型组成(在转换回字符串时分解为int
类型),所以它的范围限制为与有符号四字节数字相同的值,即+/- 2^31,或+/-2,147,483,648。
此外,此代码不是“国际化”的。