有时,Activerecord 数据类型让我感到困惑。错误,经常。我永恒的问题之一是,对于一个特定的案例,
我应该使用 :decimal 还是 :float?
我经常看到这个链接,ActiveRecord: :decimal vs :float?,但答案还不够清楚,我无法确定:
我见过许多线程,人们建议完全不要使用浮点数并始终使用小数。我还看到一些人建议仅将 float 用于科学应用。
以下是一些示例案例:
地理位置/纬度/经度:-45.756688, 120.5777777, ...
比率/百分比:0.9、1.25、1.333、1.4143、...
我过去曾使用过 :decimal
,但我发现在 Ruby 中处理 BigDecimal
对象与浮点数相比是不必要的尴尬。例如,我也知道我可以使用 :integer
来表示货币/美分,但它不太适合其他情况,例如精度可能随时间变化的数量。
使用每种方法的优点/缺点是什么?
知道使用哪种类型有什么好的经验法则?
我记得我的 CompSci 教授说永远不要使用浮点数作为货币。
原因是二进制格式的IEEE specification defines floats如何。基本上,它存储符号、分数和指数来表示浮点数。这就像二进制的科学记数法(类似于 +1.43*10^2
)。因此,不可能将分数和小数精确地存储在 Float 中。
这就是为什么有十进制格式的原因。如果你这样做:
irb:001:0> "%.47f" % (1.0/10)
=> "0.10000000000000000555111512312578270211815834045" # not "0.1"!
而如果你只是这样做
irb:002:0> (1.0/10).to_s
=> "0.1" # the interprer rounds the number for you
因此,如果您正在处理小部分,例如复利,甚至可能是地理位置,我强烈建议使用十进制格式,因为在十进制格式中 1.0/10
正好是 0.1。
但是,应该注意的是,尽管精度较低,但浮点数的处理速度更快。这是一个基准:
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
回答
当您不太关心精度时,请使用浮点数。例如,一些科学模拟和计算最多只需要 3 或 4 位有效数字。这在权衡准确性以换取速度时很有用。由于他们不需要像速度一样多的精度,他们会使用浮点数。
如果您正在处理需要精确并总结为正确数字的数字(例如复利和与金钱相关的事物),请使用小数。请记住:如果您需要精度,那么您应该始终使用小数。
在 Rails 3.2.18 中,使用 SQLServer 时 :decimal 会变成 :integer,但在 SQLite 中可以正常工作。切换到 :float 为我们解决了这个问题。
吸取的教训是“始终使用同构开发和部署数据库!”
在 Rails 4.1.0 中,我遇到了将纬度和经度保存到 MySql 数据库的问题。它不能保存浮点数据类型的大分数。我将数据类型更改为十进制并为我工作。
def change change_column :cities, :latitude, :decimal, :precision => 15, :scale => 13 change_column :cities, :longitude, :decimal, :precision => 15, :scale => 13 end
decimal(13,9)
对于纬度和经度就足够了。 @ScottW:我不记得了,但是如果 Postgres 使用 IEEE 浮点数,它只会“正常工作”,因为您还没有遇到问题……还没有。它是纬度和经度的不充分格式。 Yo 最终将在最低有效数字中出现错误。
+1.43*2^10
而不是+1.43*10^2
吗?