【问题标题】:Weird rounding issue奇怪的四舍五入问题
【发布时间】:2016-11-25 15:02:40
【问题描述】:

小数和浮点数发生了一些非常奇怪的事情,我不明白为什么或在哪里(ruby/rails/postgresql)。

给定一个带有小数列的 purchases 表 - total

p1 = Purchase.where(total: 5.99).first_or_create
p2 = Purchase.where(total: 5.99).first_or_create

[p1.id, p2.id] # => [1, 2]

p3 = Purchase.where(total: 5.99.to_d).first_or_create
p4 = Purchase.where(total: 5.99.to_d).first_or_create

[p3.id, p4.id] # => [1, 1]

无论是小数还是浮点数,Ruby 和 postgresql 都可以准确表示 5.99:

5.99.to_s         # => "5.99"
5.99.to_d.to_s    # => "5.99"
5.99 == 5.99.to_d # => true

SELECT CAST(5.99 AS DECIMAL) AS decimal, CAST(5.99 AS FLOAT) AS float;
  #  decimal | float 
  # ---------+-------
  #     5.99 |  5.99
  # (1 row)

SELECT CAST(5.99 AS DECIMAL) = CAST(5.99 AS FLOAT) AS equal;
  #  equal 
  # -------
  #  t
  # (1 row)

最重要的是,其他一些值不会发生这种情况:

p5 = Purchase.where(total: 5.75).first_or_create
p6 = Purchase.where(total: 5.75).first_or_create
p7 = Purchase.where(total: 5.75.to_d).first_or_create

[p5.id, p6.id, p7.id] # => [3, 3, 3]

【问题讨论】:

  • 基本上,你永远不希望你的 total 在任何地方成为浮点值,你会想要说像 '5.99'.to_d 这样的东西。
  • @muistooshort,实用的解决方案很棒,但用 “不要这样做” 回答 为什么 问题会适得其反。你甚至不知道他们是否是同一个问题。首先,另一个问题以两个不同的数字开头。其次,两位数的精度应该是浮点数可以毫无问题地处理的东西。第三,您可以看到问题出在 Ruby 那里,而在这里我们无法检测到问题(如示例所示)。
  • @muistooshort,请重新打开。我创建这个问题的原因是因为我想更深入地了解正在发生的事情。如果我想要一种让事情正常工作的方法 - 我已经在问题中提供了解决方案。
  • 当然,但它几乎肯定是完全相同的潜在问题:BigDecimal 用一些浮点数做了奇怪的事情。 why 部分包含在各种其他浮点问题中。我认为您对“两位数精度”的东西有误,浮点数在内部以二进制表示,并且无论 十进制 精度如何,某些十进制表示都不能以完全精度的二进制表示,您的5.75例子可能是因为3/4的属性,5.99并不是那么简单。
  • 没有二进制浮点系统可以准确地表示 5.99。最接近的 IEEE 754 64 位二进制浮点值是 5.9900000000000002131628207280300557613372802734375

标签: ruby-on-rails postgresql activerecord floating-point decimal


【解决方案1】:

结果证明这是 Rails 的回归。它可以用 5.0.0.1 重现???并在 5.1.0.0 前消失???。


我将它一分为二,发现this commit 是解决问题的那个。这是相关的issue

修复似乎是停止使用 pg gem 的浮点编码器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-11
    • 1970-01-01
    • 2018-04-29
    • 1970-01-01
    相关资源
    最近更新 更多