65.9K
CodeProject 正在变化。 阅读更多。
Home

Decimal 类实现

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (15投票s)

2003年2月17日

3分钟阅读

viewsIcon

215138

downloadIcon

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实例。内部初始化了一些辅助变量,这些变量用于stringDecimal的转换以及乘法和除法运算符。

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。

此外,此代码不是“国际化”的。

参考文献

© . All rights reserved.