MySQL中的双vs十进制


71

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

因此,我对此主题的第一意见是,我强烈建议在下一个版本中将DOUBLE转换为DECIMAL,因为MySQL文档和所有人都这么说。但是由于以下三个原因,我找不到合适的论据来证明这一建议:

  • 我们不对数据库执行任何计算。所有操作都使用BigDecimal在Java中完成,而MySQL仅用作结果的普通存储。
  • 双精度提供的15位精度足够了,因为我们存储的金额主要是2个小数位,对于公式参数,偶尔会存储8个小数位的数字。
  • 我们已经有6年的生产记录,由于MySQL方面的精度损失,没有已知的bug问题。

即使通过对18毫米行表执行运算(例如SUM和复杂的乘法),我也无法执行缺乏精度的错误。而且我们实际上并没有在生产中做这种事情。我可以通过执行类似的操作来显示丢失的精度

SELECT columnName * 1.000000000000000 FROM tableName;

但是我想不出一种方法来将它转换为错误的小数点后两位数字。我在互联网上发现的大多数实际问题都是2005年和更早的论坛条目,我无法在5.0.51 MySQL服务器上重现它们中的任何一个。

因此,只要我们不执行我们不打算执行的任何SQL算术运算,那么仅在DOUBLE列中存储和取回金额是否会有任何问题吗?


您是否用Java计算应纳税额,然后根据合同将其四舍五入后再存储?例如,如果您销售的商品价格为$ 1.47,并有8.25%的本地营业税,则可能需要记录$ 0.121275的税款。我想知道您以何种形式将这种字段存储在数据库中,以及在存储之前是否舍入到$ 0.12(或舍入到$ 0.13,具体取决于您的语言环境)。
rajah9 2011年

是的,我们用Java计算税金,然后存储商品的价格,税额四舍五入到小数点后四位,总价格四舍五入到小数点后第二位。因此,在您的示例中,一行将包含1.47、0.1213和1.59。8.25%存储在其他地方为0.08250000,并且每次销售都不会重复。
user327961 2011年

Answers:


56

其实是完全不同的。DOUBLE会导致舍入问题。如果你做这样的事情,0.1 + 0.2它就会给你类似0.30000000000000004。我个人不相信使用浮点数学的财务数据。影响可能很小,但谁知道。我宁愿拥有我所知道的可靠数据,也不愿获得近似数据,尤其是在处理货币价值时。


19
嗯,这不是解决问题的技术方法,但是让我觉得最重要的一点是,我个人不信任使用浮点数学的财务数据。可以肯定的是,即使我花了一个星期的时间来证明我们的用例中的数据安全,许多其他人也不是100%相信这些数据,它们是值得怀疑的。客户对审核的不信任确实是一个问题,并且是推荐从DOUBLE切换到DECIMAL的一个很好的论据。
user327961 2011年

38

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)

基本上,如果您求和a,您将得到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算术运算,就可以使用DOUBLE,但我还是希望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)

“基本上,如果您求和a,您将得到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 2014年

@markwatson,这是一个错字,您是正确的。b的总和当然是76.8,我现在对其进行了更正。
宽带

16

我们刚刚经历了同样的问题,但反过来说。也就是说,我们将美元金额存储为DECIMAL,但是现在我们发现,例如,MySQL正在计算4.389999999993的值,但是当将其存储到DECIMAL字段时,它将其存储为4.38而不是我们想要的4.39它来。因此,尽管DOUBLE可能会导致舍入问题,但DECIMAL似乎也可能导致一些截断问题。


4
我只是试过这个:如果不存在则创建表exactn十进制(5,2)不为NULL)ENGINE = InnoDB DEFAULT CHARSET = utf8 COLLATE = utf8_bin; 我这样插入数据:插入exactn)值(4.389999999993); 由于四舍五入,所以有一个当然的警告。但是它存储的是4.39,而不是您写的4.38。我正在使用mySQL 5.5.28-log
宽带

3
如果不使用exact(),则插入的值将被截断而不是四舍五入。
2013年

6
截断行为记录在MySQL手册中:“当为这样的列分配的值在小数点后的位数比指定刻度允许的位数多时,该值将转换为该刻度。(确切的行为是操作系统-特定的,但通常效果是截断到允许的位数。)“我建议使用ROUND()
Stephen

如果要存储8位小数精度,请使用DECIMAL(10, 8)field so。如果您在数据库中具有实际价值,则最好在应用程序中处理舍入。
文森特·德克斯

0

“仅在DOUBLE列中存储和取款会有什么问题吗?”

听起来您的场景中不会产生舍入错误,如果存在舍入错误,则将其转换为BigDecimal将被截断。

所以我会说不。

但是,不能保证将来的某些更改不会带来问题。


0

根据您的评论,

税额四舍五入到小数点后第四位,总价格四舍五入到小数点后第二位。

使用注释中的示例,我可以预见一个案例,您有400笔销售额为1.47美元。税前销售额为588.00美元,税后销售额总计为636.51美元(占税额48.51美元)。但是,$ 0.121275 * 400的营业税为$ 48.52。

尽管是人为的,但这是迫使一分钱差的一种方法。

我要指出的是,美国国税局提供了薪资税表,它们不关心错误是否低于一定金额(如果存储了内存,则为$ 0.50)。

您的大问题是:有人在乎某些报告是否少收了几分钱?如果您的规格说:是的,一分钱都准确,那么您应该努力转换为DECIMAL。

我曾在一家银行工作过,该银行报告一个一分钱的错误是软件缺陷。我试图(徒劳地)引用软件规范,该规范对于该应用程序不需要这种精确度。(它执行了许多链式乘法。)我还指出了用户接受度测试。(该软件已通过验证并被接受。)

las,有时候您只需要进行转换。但我鼓励您A)确保对某人来说很重要,然后B)编写测试以表明您的报告在指定的范围内是准确的。


3
“您最大的问题是:有人在乎某些报告是否少付给任何人钱吗?” 如果没有,那就去写病毒的盒子:P(re Office Space)对不起,我无济于事。
HalilÖzgür2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.