这个问题得到了很好的讨论,但是由于我已经研究了这个问题一段时间,所以我想分享一些我的结果。
问题定义:众所周知,小数比双精度数要慢得多,但金融应用程序不能容忍对双精度数执行计算时出现的任何伪影。
研究
我的目标是测量存储浮点数的不同方法,并得出结论应该将哪一种用于我们的应用程序。
如果我们可以使用它Int64
来存储具有固定精度的浮点数是可以接受的。10^6 的乘数给了我们两个:足够的数字来存储分数,并且仍然有很大的范围来存储大量。当然,您必须小心使用这种方法(乘法和除法运算可能会变得很棘手),但我们已经准备好并且也想测量这种方法。除了可能的计算错误和溢出之外,您必须记住的一件事是,通常您不能将这些长数字公开给公共 API。因此,所有内部计算都可以使用 long 执行,但在将数字发送给用户之前,它们应该转换为更友好的东西。
我已经实现了一个简单的原型类,它将一个 long 值包装到一个类似小数的结构(称为 it Money
)并将其添加到测量中。
public struct Money : IComparable
{
private readonly long _value;
public const long Multiplier = 1000000;
private const decimal ReverseMultiplier = 0.000001m;
public Money(long value)
{
_value = value;
}
public static explicit operator Money(decimal d)
{
return new Money(Decimal.ToInt64(d * Multiplier));
}
public static implicit operator decimal (Money m)
{
return m._value * ReverseMultiplier;
}
public static explicit operator Money(double d)
{
return new Money(Convert.ToInt64(d * Multiplier));
}
public static explicit operator double (Money m)
{
return Convert.ToDouble(m._value * ReverseMultiplier);
}
public static bool operator ==(Money m1, Money m2)
{
return m1._value == m2._value;
}
public static bool operator !=(Money m1, Money m2)
{
return m1._value != m2._value;
}
public static Money operator +(Money d1, Money d2)
{
return new Money(d1._value + d2._value);
}
public static Money operator -(Money d1, Money d2)
{
return new Money(d1._value - d2._value);
}
public static Money operator *(Money d1, Money d2)
{
return new Money(d1._value * d2._value / Multiplier);
}
public static Money operator /(Money d1, Money d2)
{
return new Money(d1._value / d2._value * Multiplier);
}
public static bool operator <(Money d1, Money d2)
{
return d1._value < d2._value;
}
public static bool operator <=(Money d1, Money d2)
{
return d1._value <= d2._value;
}
public static bool operator >(Money d1, Money d2)
{
return d1._value > d2._value;
}
public static bool operator >=(Money d1, Money d2)
{
return d1._value >= d2._value;
}
public override bool Equals(object o)
{
if (!(o is Money))
return false;
return this == (Money)o;
}
public override int GetHashCode()
{
return _value.GetHashCode();
}
public int CompareTo(object obj)
{
if (obj == null)
return 1;
if (!(obj is Money))
throw new ArgumentException("Cannot compare money.");
Money other = (Money)obj;
return _value.CompareTo(other._value);
}
public override string ToString()
{
return ((decimal) this).ToString(CultureInfo.InvariantCulture);
}
}
实验
我测量了以下操作:加法、减法、乘法、除法、相等比较和相对(更大/更少)比较。我正在测量以下类型的操作:double
、long
和. 每个操作执行 1.000.000 次。所有数字都预先分配在数组中,因此在构造函数中调用自定义代码不应影响结果。decimal
Money
decimal
Money
Added moneys in 5.445 ms
Added decimals in 26.23 ms
Added doubles in 2.3925 ms
Added longs in 1.6494 ms
Subtracted moneys in 5.6425 ms
Subtracted decimals in 31.5431 ms
Subtracted doubles in 1.7022 ms
Subtracted longs in 1.7008 ms
Multiplied moneys in 20.4474 ms
Multiplied decimals in 24.9457 ms
Multiplied doubles in 1.6997 ms
Multiplied longs in 1.699 ms
Divided moneys in 15.2841 ms
Divided decimals in 229.7391 ms
Divided doubles in 7.2264 ms
Divided longs in 8.6903 ms
Equility compared moneys in 5.3652 ms
Equility compared decimals in 29.003 ms
Equility compared doubles in 1.727 ms
Equility compared longs in 1.7547 ms
Relationally compared moneys in 9.0285 ms
Relationally compared decimals in 29.2716 ms
Relationally compared doubles in 1.7186 ms
Relationally compared longs in 1.7321 ms
结论
- 加法、减法、乘法、比较运算
decimal
比long
or慢约 15 倍double
;除法要慢约30倍。
- 由于缺乏 CLR 的支持,类包装器的性能
Decimal
优于Decimal
但仍明显低于 的性能。double
long
- 以绝对数字执行计算
Decimal
非常快:每秒 40.000.000 次操作。
建议
- 除非您有一个非常繁重的计算案例,否则请使用小数。在相对数字上,它们比 long 和 double 慢,但绝对数字看起来不错。
Decimal
由于缺乏 CLR 的支持,用您自己的结构重新实现没有多大意义。你可能会比它更快,Decimal
但它永远不会像double
.
- 如果
Decimal
您的应用程序的性能不够,您可能需要考虑将计算切换为long
固定精度。在将结果返回给客户端之前,应将其转换为Decimal
.