关于Ruby on Rails:ActiveRecord中的浮点与十进制Float vs Decimal

Float vs Decimal in ActiveRecord

有时,ActiveRecord数据类型会让我感到困惑。经常出错。我永恒的问题之一是,对于一个特定的案例,

Should I use :decimal or :float?

我经常看到这个链接,activeRecord::decimal vs:float?但答案还不够清楚,我无法确定:

I've seen many threads where people recommend flat out to never use
float and always use decimal. I've also seen suggestions by some
people to use float for scientific applications only.

以下是一些示例案例:

  • 地理位置/经纬度:-45.756688120.5777777……
  • 比率/百分比:0.91.251.3331.4143……

我以前使用过:decimal,但我发现在ruby中处理BigDecimal对象与float相比不必要。我也知道我可以用:integer来表示金钱/美分,例如,但它不太适合其他情况,例如,当精度随时间而变化的数量。

  • 使用每种方法的优点/缺点是什么?
  • 要知道使用哪种类型,有什么好的经验法则?

我记得我的康普西语教授说过永远不要用花车兑换货币。

原因是IEEE规范如何定义二进制格式的浮动。基本上,它存储符号、分数和指数来表示一个浮点。它就像是一个科学的二进制符号(类似于+1.43*10^2)。因此,不可能精确地将分数和小数存储在浮点中。

这就是为什么有十进制格式。如果你这样做:

1
2
irb:001:0>"%.47f" % (1.0/10)
=>"0.10000000000000000555111512312578270211815834045" # not"0.1"!

但是如果你只是

1
2
irb:002:0> (1.0/10).to_s
=>"0.1" # the interprer rounds the number for you

因此,如果您处理的是小分数,例如复合利息,甚至地理位置,我强烈建议使用十进制格式,因为在十进制格式中,1.0/10正好是0.1。

然而,应该注意的是,尽管浮点数的精度较低,但处理浮点数的速度却更快。以下是一个基准:

1
2
3
4
5
6
7
8
9
10
11
12
13
require"benchmark"
require"bigdecimal"

d = BigDecimal.new(3)
f = Float(3)

time_decimal = Benchmark.measure{ (1..10000000).each { |i| d * d } }
time_float = Benchmark.measure{ (1..10000000).each { |i| f * f } }

puts time_decimal
#=> 6.770960 seconds
puts time_float
#=> 0.988070 seconds

回答

当您不太关心精度时,请使用float。例如,一些科学模拟和计算最多只需要3或4个有效数字。这对于以准确度换取速度很有用。因为它们不需要像速度那样高的精度,所以它们将使用浮动。

如果您处理的数字需要精确,并求和到正确的数字(如复利和与金钱相关的事情),请使用十进制。记住:如果您需要精确性,那么您应该始终使用十进制。


在Rails 3.2.18中,当使用sqlserver时,:decimal将变为:integer,但在sqlite中可以正常工作。切换到:float为我们解决了这个问题。

经验教训是"始终使用同构开发和部署数据库!"


在Rails4.1.0中,我遇到了将纬度和经度保存到MySQL数据库的问题。它不能用float数据类型保存大分数。我把数据类型改为十进制,然后为我工作。

1
2
3
4
  def change
    change_column :cities, :latitude, :decimal, :precision => 15, :scale => 13
    change_column :cities, :longitude, :decimal, :precision => 15, :scale => 13
  end