ActiveRecord中的浮点数与小数


283

有时,Activerecord数据类型使我感到困惑。经常犯错。对于给定的情况,我永恒的问题之一是

我应该使用:decimal还是:float

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

我见过很多线程,人们建议平整地使用永不使用float并始终使用十进制。我也看到一些人建议仅将浮点数用于科学应用。

这是一些示例情况:

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

我过去使用:decimal过,但是与BigDecimal浮点数相比,我发现在Ruby中处理对象不必要地麻烦。我也知道:integer,例如,我可以用来表示金钱/美分,但是它并不完全适合其他情况,例如当精度可能随时间变化的数量时。

  • 使用它们的优点/缺点是什么?
  • 知道使用哪种类型的最佳经验法则是什么?

Answers:


427

我记得我的CompSci教授说过不要使用浮动货币。

这样做的原因是IEEE规范如何定义二进制格式的浮点数。基本上,它存储符号,分数和指数来表示Float。这就像是二进制的科学记法(类似+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

回答

当您不太在乎精度时,请使用float。例如,某些科学模拟和计算最多只需要3或4个有效数字。这对于权衡速度的准确性很有用。由于他们并不需要速度,因此他们会使用float。

如果要处理的数字需要精确并加总为正确的数字(例如,复利和与金钱相关的事物),请使用小数。请记住:如果需要精度,则应始终使用十进制。


因此,如果我理解正确,则float以2为底,而十进制以10为底?浮动有什么好用?您的示例做了什么并演示了什么?
乔纳森·阿拉德

1
你不是说+1.43*2^10不是+1.43*10^2
卡梅隆·马丁

46
对于将来的访问者,货币的最佳数据类型是整数,而不是十进制。如果字段的精度是几美分,则该字段将是几美分的整数(而不是美元的小数)。我曾在一家银行的IT部门工作过,这就是那里的工作方式。有些字段的精度更高(例如百分之一分钱),但它们仍然是整数。
adg

1
@adg是正确的:bigdecimal也是货币的不佳选择。
埃里克·杜米尼尔

1
@adg,您是对的。在过去的几年中,我一直在处理一些会计和财务应用程序,我们将所有货币字段存储在整数列中。对于这种情况,这要安全得多。
Guilherme Lages Santos

19

在Rails 3.2.18中,当使用SQLServer时,:decimal转换为:integer,但在SQLite中可以正常使用。切换到:float为我们解决了这个问题。

获得的教训是“始终使用同质开发和部署数据库!”


3
好点,三年后做Rails,我完全同意。
乔纳森·阿拉德

3
“始终使用同质的开发和部署数据库!”
zx1986 '16

15

在Rails 4.1.0中,我遇到了将纬度和经度保存到MySql数据库的问题。它不能使用浮点数据类型保存大分数。我将数据类型更改为十进制并为我工作。

  清晰度变化
    change_column:cities,:latitude,:decimal,:precision => 15,:scale => 13
    change_column:cities,:longitude,:decimal,:precision => 15,:scale => 13
  结束

我将:latitude和:longitude保存为Postgres中的浮点数,效果很好。
Scott W

3
@Robikul:是的,那很好,但是矫kill过正。 decimal(13,9) 经纬度就足够了。@ScottW:我不记得了,但是如果Postgres使用IEEE浮点数,那么它只能“正常工作”,因为您还没有遇到问题……是的。对于纬度和经度而言,格式不足。最终,您的最低有效位数会有错误。
Lonny Eachus

@LonnyEachus是什么使IEEE浮点数不足以容纳经/纬度?
亚历山大·苏拉普

3
@AlexanderSuraphel如果您使用的是十进制纬度和经度,则IEEE浮点数很容易出现最低有效位数的错误。因此,例如,您的经度和纬度可能具有1米的精度,但是您可能会有100米或更高的误差。如果在计算中使用它们,则尤其如此。
朗尼·艾弗斯
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.