小编典典

ActiveRecord 中的浮点数与小数

all

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

我应该使用:decimalor:float吗?

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

我见过许多线程,人们建议完全不要使用浮点数并始终使用小数。我还看到一些人建议仅将 float 用于科学应用。

以下是一些示例案例:

  • 地理位置/纬度/经度:-45.756688, 120.5777777, …
  • 比率/百分比:0.9, 1.25, 1.333, 1.4143, …

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

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

阅读 78

收藏
2022-04-19

共1个答案

小编典典

我记得我的 CompSci 教授说永远不要使用浮点数作为货币。

原因是IEEE
规范如何以二进制格式定义浮点数
。基本上,它存储符号、分数和指数来表示浮点数。这就像二进制的科学记数法(类似于+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
位有效数字。这在权衡准确性以换取速度时很有用。由于他们不需要像速度一样多的精度,他们会使用浮点数。

如果您正在处理需要精确并总结为正确数字的数字(例如复利和与金钱相关的事物),请使用 小数。 请记住:如果您需要精度,那么您应该始终使用小数。

2022-04-19