2011-07-26 126 views
51

好吧,所以我知道有很多文章指出我不应该使用DOUBLE在MySQL数据库上存储资金,否则我最终会遇到棘手的精确错误。关键是我没有设计一个新的数据库,我要求找到优化现有系统的方法。新版本包含783 DOUBLE类型的列,其中大多数用于存储货币或公式来计算金额。MySQL中的DOUBLE和DECIMAL

因此,我对这个问题的第一个看法是,我应该强烈建议在下一个版本中将DOUBLE转换为DECIMAL,因为MySQL文档和大家都这样说。但后来我找不到任何合理的理由来推荐这个建议,原因有三:

  • 我们不对数据库执行任何计算。所有的操作都是使用BigDecimal在Java中完成的,MySQL只是用作结果的普通存储。
  • DOUBLE提供的15位精度足够大,因为我们主要存储2位十进制数字的数字,偶尔小数字用8位数字表示公式参数。
  • 我们有6年的生产记录,没有已知的MySQL错误导致的错误问题。

即使通过对18毫米行表执行操作(如SUM和复数乘法),我也无法执行缺乏精度的错误。我们实际上并没有在生产中做这种事情。我可以告诉做什么失去精度像

SELECT columnName * 1.000000000000000 FROM tableName;

但我不能想出一个办法把它变成一个bug在第2十进制数字。我在互联网上发现的大多数真实问题都是2005年和较旧的论坛条目,我无法在5.0.51 MySQL服务器上重现它们中的任何一个。

所以只要我们不执行任何SQL算术运算,我们不打算这样做,是否有任何问题,我们应该期望从DOUBLE列中只存储和检索金额?

+0

您是否使用Java计算应税金额,然后根据合同对它们进行四舍五入,然后再存储它们?例如,如果您销售的产品价格为1.47美元,并且拥有8.25%的本地销售税,则您可能需要记录税金0.121275美元。我想知道你以什么形式在数据库中存储这种类型的字段,以及在你存储之前是否舍入到0.12美元(或取决于你的语言环境,达到0.13美元)。 – rajah9

+0

是的,我们计算税额在Java中,我们存储项目的价格,四舍五入至小数点第四和总价四舍五入至小数点第2税额。所以在你的例子中,一行将包含 1.47,0.1213和1.59。 8.25%存储在其他地方为0.08250000,不会为每次销售重复。 – user327961

回答

31

其实它是完全不同的。 DOUBLE导致舍入问题。如果你做了类似0.1 + 0.2的东西,它会给你类似0.30000000000000004的东西。我个人不会相信使用浮点数学的财务数据。影响可能很小,但谁知道。我宁愿让我知道的是可靠的数据,而不是近似的数据,特别是在处理货币价值时。

+11

嗯,这不是一个技术性的答复的话,但让我觉得最关键是 _I个人不会相信,采用浮点math._ 大多数肯定还有很多人不是100%信任这些数据甚至财务数据如果我花了一个星期的时间试图在我们的使用案件中安全地使用它,他们会怀疑的。没有客户对审计的信任确实是一个问题,并且建议从DOUBLE切换到DECIMAL是一个很好的参数。 – user327961

0

“我们应该期待什么问题,只需在DOUBLE列中存储和检索金额?”

听起来好像在您的方案中不会产生舍入错误,并且如果有的话,它们会被转换为BigDecimal而截断。所以我会说不。

但是,不能保证未来的某些变化不会引起问题。

0

从您的意见,

四舍五入至小数点第四和总价的税额四舍五入 到小数点第2。

在评论中使用示例,我可能会预见到400美元的销售额为1.47美元的情况。税前销售额为588.00美元,税后销售额为636.51美元(占税收的48.51美元,美元)。但是,$ 0.121275 * 400的销售税将是$ 48.52。

这是一种方式,虽然做作,强迫一分钱的差别。

我要指出,有来自美国国税局的工资税的形式,他们如果误差低于一定数额不关心(如果没有记错,$ 0.50)。

你的一大问题是:没有任何人关心,如果某些报告是关闭的一分钱?如果你的规格说:是的,要准确的一分钱,那么你应该经过努力转换为十进制。

我在这里报道一便士的错误作为一个软件缺陷的银行工作。我试过(徒劳无功)引用软件规范,这些规范并不要求这种程度的精确度。 (它正在执行许多链式乘法。)我还指出了用户验收测试。 (该软件进行了验证和接受。)

唉,有时你必须做出的转换。但我鼓励你:A)确保对某人很重要,然后B)写测试以表明你的报告准确到指定的程度。

+2

“你最大的问题是:有人关心某些报告是否被一分钱报废?”如果没有:前往你的盒子写一个病毒:P(重新办公室空间)对不起,我无法帮助它。 –

15

我们刚刚经历了同样的问题,但相反。也就是说,我们将美元金额存储为DECIMAL,但现在我们发现MySQL正在计算值为4.389999999993,但是当将其存储到DECIMAL字段中时,它将其存储为4.38而不是4.39,就像我们想要的那样它来。因此,虽然DOUBLE可能会导致取整问题,但看起来DECIMAL也会导致一些截断问题。

+4

我只是尝试这样做:CREATE TABLE IF NOT EXISTS'exact'( 'N'十进制(5,2)NOT NULL )ENGINE = InnoDB的默认字符集= UTF8 COLLATE = utf8_bin;我插入像这样的数据:插入到'精确'('n')值(4.389999999993);由于四舍五入,有一个课程警告。但它像你写的那样存储4.39而不是4.38。我正在使用mySQL 5.5.28-log – broadband

+3

如果不使用'exact()',插入的值将被截断,而不是四舍五入。 – RandomSeed

+4

截断行为在MySQL手册[记录](http://dev.mysql.com/doc/refman/5.5/en/fixed-point-types.html):“当这样的列分配有值小数点比由指定的比例是允许以下更多的数字,则该值被转换成比例。(精确行为是特定于操作系统,但一般效果是截短到数字的允许数量。)” 我倒是建议使用[ROUND()](http://dev.mysql.com/doc/refman/5.5/en/mathematical-functions.html#function_round) – Stephen

27

从MySQL文档http://dev.mysql.com/doc/refman/5.1/en/problems-with-float.html示例(ⅰ其缩小,此节文档是5.5相同)

mysql> create table t1 (i int, d1 double, d2 double); 

mysql> insert into t1 values (2, 0.00 , 0.00), 
          (2, -13.20, 0.00), 
          (2, 59.60 , 46.40), 
          (2, 30.40 , 30.40); 

mysql> select 
     i, 
     sum(d1) as a, 
     sum(d2) as b 
     from 
     t1 
     group by 
     i 
     having a <> b; -- a != b 

+------+-------------------+------+ 
| i | a     | b | 
+------+-------------------+------+ 
| 2 | 76.80000000000001 | 76.8 | 
+------+-------------------+------+ 
1 row in set (0.00 sec) 

基本上如果你总结一个你0-13.2 + 59.6 + 30.4 = 76.8。如果我们总结b,我们得到0 + 0 + 46.4 + 30.4 = 76.8。 a和b的总和相同,但MySQL文档中提到:

写入SQL语句中的浮点值可能与内部表示的值不同。

如果我们重复相同的十进制:

mysql> create table t2 (i int, d1 decimal(60,30), d2 decimal(60,30)); 
Query OK, 0 rows affected (0.09 sec) 

mysql> insert into t2 values (2, 0.00 , 0.00), 
          (2, -13.20, 0.00), 
          (2, 59.60 , 46.40), 
          (2, 30.40 , 30.40); 
Query OK, 4 rows affected (0.07 sec) 
Records: 4 Duplicates: 0 Warnings: 0 

mysql> select 
     i, 
     sum(d1) as a, 
     sum(d2) as b 
     from 
     t2 
     group by 
     i 
     having a <> b; 

Empty set (0.00 sec) 

预期的结果是空集。

所以只要你不执行,你可以使用双任何SQL arithemetic操作,但我还是喜欢十进制数。

如果小数部分太大,还需要注意关于DECIMAL的另一件事。例如:

mysql> create table t3 (d decimal(5,2)); 
Query OK, 0 rows affected (0.07 sec) 

mysql> insert into t3 (d) values(34.432); 
Query OK, 1 row affected, 1 warning (0.10 sec) 

mysql> show warnings; 
+-------+------+----------------------------------------+ 
| Level | Code | Message        | 
+-------+------+----------------------------------------+ 
| Note | 1265 | Data truncated for column 'd' at row 1 | 
+-------+------+----------------------------------------+ 
1 row in set (0.00 sec) 

mysql> select * from t3; 
+-------+ 
| d  | 
+-------+ 
| 34.43 | 
+-------+ 
1 row in set (0.00 sec) 
+0

“基本上,如果你总和你明确指定舍入行为0-13.2 + 59.6 + 30.4 = 76.8。如果我们总结b得到0 + 0 + 46.4 + 30.4 = 78.8。“这有一个错误,结果是“a = 76.80000000000001,b = 76.8”,如SQL结果所示。错误是1的最后,但这是如此之小,只是使用二进制而不是十进制编码的结果。 – markwatson

+0

@markwatson这是一个错字,你是对的。 b的总和当然是76.8,我现在纠正了。 – broadband

相关问题