53

我编写财务应用程序时,我不断地与使用双精度还是使用小数的决定作斗争。

我所有的数学都适用于小数点后不超过 5 位且不大于 ~100,000 的数字。我有一种感觉,无论如何,所有这些都可以表示为双精度数而不会出现舍入误差,但从未确定过。

为了明显的速度优势,我会继续从小数切换到双精度,除了在一天结束时,我仍然使用 ToString 方法将价格传输到交易所,并且需要确保它始终输出数字 I预计。(89.99 而不是 89.99000000001)

问题:

  1. 速度优势真的像天真的测试所暗示的那样大吗?(约 100 次)
  2. 有没有办法保证 ToString 的输出是我想要的?我的号码总是可表示的这一事实是否可以保证这一点?

更新:在我的应用程序运行之前,我必须处理大约 100 亿次价格更新,出于明显的保护原因,我现在已经使用小数实现了,但是开启需要大约 3 个小时,双打会大大减少我的开启时间. 双打有安全的方法吗?

4

7 回答 7

84
  1. 浮点运算几乎总是会明显更快,因为它是由硬件直接支持的。到目前为止,几乎没有广泛使用的硬件支持十进制算术(尽管这种情况正在发生变化,请参阅评论)。
  2. 金融应用程序应该始终使用十进制数,在金融应用程序中使用浮点数产生的恐怖故事数不胜数,您应该能够通过 Google 搜索找到很多这样的例子。
  3. 虽然十进制运算可能比浮点运算慢得多,但除非您花费大量时间处理十进制数据,否则对程序的影响可能可以忽略不计。与往常一样,在开始担心差异之前进行适当的分析。
于 2008-11-30T23:46:38.520 回答
24

这里有两个可分离的问题。一个是 double 是否有足够的精度来保存你需要的所有位,另一个是它可以准确地表示你的数字。

至于确切的表示,您应该谨慎,因为像 1/10 这样的精确十进制分数没有精确的二进制对应物。但是,如果您知道您只需要 5 位十进制数字的精度,则可以使用缩放算术,在其中对数字乘以 10^5 进行运算。因此,例如,如果您想准确表示 23.7205,您可以将其表示为 2372050。

让我们看看是否有足够的精度:双精度为您​​提供 53 位精度。这相当于 15+ 个十进制数字的精度。因此,这将允许您在小数点后有 5 位数字,在小数点前有 10 位数字,这对于您的应用程序来说似乎足够了。

我会将这个 C 代码放在一个 .h 文件中:

typedef double scaled_int;

#define SCALE_FACTOR 1.0e5  /* number of digits needed after decimal point */

static inline scaled_int adds(scaled_int x, scaled_int y) { return x + y; }
static inline scaled_int muls(scaled_int x, scaled_int y) { return x * y / SCALE_FACTOR; }

static inline scaled_int scaled_of_int(int x) { return (scaled_int) x * SCALE_FACTOR; }
static inline int intpart_of_scaled(scaled_int x) { return floor(x / SCALE_FACTOR); }
static inline int fraction_of_scaled(scaled_int x) { return x - SCALE_FACTOR * intpart_of_scaled(x); }

void fprint_scaled(FILE *out, scaled_int x) {
  fprintf(out, "%d.%05d", intpart_of_scaled(x), fraction_of_scaled(x));
}

可能有一些粗糙的地方,但这应该足以让你开始。

没有加法开销,乘法或除法的成本加倍。

如果您可以访问 C99,您还可以尝试使用int64_t64 位整数类型的缩放整数算术。哪个更快取决于您的硬件平台。

于 2008-12-01T01:24:22.677 回答
18

始终使用 Decimal 进行任何财务计算,否则您将永远追逐 1cent 的舍入误差。

于 2008-11-30T23:59:08.947 回答
13
  1. 是的; 软件算术确实比硬件慢 100 倍。或者,至少,它要慢得多,并且 100 倍,给或取一个数量级,大约是正确的。回到糟糕的过去,当你不能假设每个 80386 都有一个 80387 浮点协处理器时,你也有二进制浮点的软件模拟,这很慢。
  2. 不; 如果您认为纯二进制浮点数可以准确地表示所有十进制数,那么您就生活在幻想的世界中。二进制数可以组合一半、四分之一、八分之一等,但由于 0.01 的精确小数需要五分之一的两个因数和四分之一的一个因数 (1/100 = (1/4)*(1/5)*(1 /5)) 并且由于五分之一在二进制中没有精确表示,因此您不能用二进制值精确表示所有十进制值(因为 0.01 是一个无法精确表示的反例,但它代表了一大类十进制数不能准确表示)。

因此,您必须在调用 ToString() 之前决定是否可以处理舍入,或者是否需要找到一些其他机制来处理将结果转换为字符串时的舍入。或者您可以继续使用十进制算法,因为它会保持准确,并且一旦发布支持硬件中新的 IEEE 754 十进制算法的机器,它会变得更快。

强制性交叉引用:每个计算机科学家都应该知道的关于浮点运算的知识。这是许多可能的 URL 之一。

此Speleotrove站点上有关十进制算术和新 IEEE 754:2008 标准的信息。

于 2008-12-01T00:59:06.877 回答
8

只需使用 long 并乘以 10 的幂。完成后,除以 10 的相同幂。

于 2009-05-03T15:20:04.440 回答
6

财务计算应始终使用小数。数字的大小并不重要。

对我来说,最简单的解释方法是通过一些 C# 代码。

double one = 3.05;
double two = 0.05;

System.Console.WriteLine((one + two) == 3.1);

即使 3.1 等于 3.1,那段代码也会打印出False ......

同样的事情......但使用十进制:

decimal one = 3.05m;
decimal two = 0.05m;

System.Console.WriteLine((one + two) == 3.1m);

现在将打印出True

如果您想避免此类问题,我建议您坚持使用小数。

于 2008-12-01T01:13:03.370 回答
1

我请你参考我对这个问题的回答。

使用 long 存储您需要跟踪的最小数量,并相应地显示值。

于 2008-12-17T00:31:58.280 回答