【问题标题】:DOUBLE vs DECIMAL in MySQLMySQL中的DOUBLE vs DECIMAL
【发布时间】:2011-10-13 11:00:15
【问题描述】:

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

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

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

即使对 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 美元,具体取决于您的语言环境)。
  • 是的,我们在 Java 中计算税款,我们存储商品的价格、四舍五入到小数点后第 4 位的税额和四舍五入到小数点后第 2 位的总价格。因此,在您的示例中,一行将包含 1.47、0.1213 和 1.59。 8.25% 以 0.08250000 的形式存储在其他地方,并且不会在每次销售中重复。

标签: mysql double decimal


【解决方案1】:

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

【讨论】:

  • 嗯,这不是对这个案例的技术性回答,但让我思考最多的一点是 我个人不会相信使用浮点数学的财务数据。 大多数当然,即使我花了一周时间试图在我们的用例中证明它是安全的,许多其他人也不会 100% 信任这些数据,他们的怀疑是正确的。客户不信任审计确实是一个问题,并且是推荐从 DOUBLE 切换到 DECIMAL 的一个很好的论据。
  • 我知道这是一个非常古老的答案,但我只是在我一直在运行的数据库中大量使用类型“double”的情况下做了“哦 *%&”舞蹈规模的公司。问题确实出现了。它让会计师发疯。突然间,我回想起了一些完全不相关的格式、一致性和准确性问题。每次它发生时,我都困惑地处理它。有一次我观察到有时总计会以一分钱的形式结束,但如果我捕获不同的子因素,四舍五入,然后相乘,总计将保持准确。这样的菜鸟错误
  • @SamHughes - “有一次我观察到有时总和会以一分钱的形式结束,但如果我捕捉到不同的子因素,四舍五入,然后相乘,总和会保持准确。” FWIW,这在使用小数时同样是一个问题;只是在不同的情况下。无论哪种方式,都必须由会计师验证如何处理子因素(如果还涉及乘法,例如税率);是否要以更高的精度求和,并且只对总数进行四舍五入?
  • “DOUBLE 会导致舍入问题。” 如果您没有为子因子使用足够多的位数,那么 Decimal 会导致 截断 问题,当对每个应用乘法时(例如税率)。在这种情况下,没有天生正确的答案; “正确”的答案是会计师(或税务机构)所说的累积分数值的适当方法。不过,使用十进制更安全;请注意,中间值中所需的小数位数可能更高。
【解决方案2】:

来自 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。

关于 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 这是一个错字,你是对的。 b的和当然是76.8,我现在更正了。
【解决方案3】:

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

【讨论】:

  • 我刚试过这个: CREATE TABLE IF NOT EXISTS exact ( n decimal(5,2) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;我像这样插入数据:插入exact (n) values(4.389999999993);由于四舍五入,当然有警告。但它存储 4.39 而不是 4.38 就像你写的那样。我正在使用 mySQL 5.5.28-log
  • 不使用exact(),插入的值会被截断,而不是四舍五入。
  • MySQL 手册中的截断行为是documented:“当为此类列分配的值的小数点后位数超过指定比例所允许的位数时,该值将转换为(精确的行为是特定于操作系统的,但通常效果是截断到允许的位数。)”我建议使用ROUND() 明确指定舍入行为
  • 如果要存储 8 位小数精度,请使用 DECIMAL(10, 8) 字段。如果你在你的数据库中有真正的价值,那么在你的应用程序中处理四舍五入会更好。
  • @VincentDecaux - 这个答案的重点是,对于以美元和美分表示的财务数据,一个想要存储 8 位小数精度。有一个正确的会计师指定的行为(通常,四舍五入到最接近的分),这是应该执行的,并保存为小数点后 2 位。
【解决方案4】:

“仅在 DOUBLE 列中存储和检索金额有什么问题吗?”

听起来在您的方案中不会产生舍入错误,如果有,它们将被转换为 BigDecimal 截断。

所以我会说不。

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

【讨论】:

  • "...如果有,它们将被转换为 BigDecimal 截断。" 不,这就是潜在的问题。舍入误差会导致数字比实际值Truncation 会减少一分钱。至少,必须在任何可能从双精度格式转换为十进制格式的步骤中显式Round
【解决方案5】:

来自您的 cmets,

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

使用 cmets 中的示例,我可能会预见到您有 400 次销售额为 1.47 美元的情况。税前销售额为 588.00 美元,税后销售额总计为 636.51 美元(占税费 48.51 美元)。但是,0.121275 美元 * 400 美元的销售税将为 48.52 美元。

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

我会注意到,IRS 有一些工资税表,他们不关心错误是否低于一定金额(如果没记错的话,0.50 美元)。

您的大问题是:有人在乎某些报告是否偏离一分钱吗?如果您的规格说:是的,准确到一分钱,那么您应该努力转换为十进制。

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

唉,有时您只需要进行转换。但我鼓励你 A) 确保它对某人很重要,然后 B) 编写测试以证明你的报告在指定的程度上是准确的。

【讨论】:

  • “你最大的问题是:有人关心某些报告是否有一分钱的差错吗?”如果没有:前往你的盒子写病毒:P(re Office Space)对不起,我没办法。
猜你喜欢
  • 2023-03-06
  • 2021-11-28
  • 2013-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-09
  • 1970-01-01
  • 2021-11-22
相关资源
最近更新 更多