ChatGPT解决这个技术问题 Extra ChatGPT

ActiveRecord 中的浮点数与小数

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

我应该使用 :decimal 还是 :float?

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

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

以下是一些示例案例:

地理位置/纬度/经度:-45.756688, 120.5777777, ...

比率/百分比:0.9、1.25、1.333、1.4143、...

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

使用每种方法的优点/缺点是什么?

知道使用哪种类型有什么好的经验法则?


J
Jonathan Allard

我记得我的 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 位有效数字。这在权衡准确性以换取速度时很有用。由于他们不需要像速度一样多的精度,他们会使用浮点数。

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


因此,如果我理解正确,浮点数以 2 为底,而小数以 10 为底?浮动有什么用处?你的例子做了什么,并展示了什么?
你不是说+1.43*2^10而不是+1.43*10^2吗?
对于未来的访问者,货币的最佳数据类型是整数,而不是小数。如果字段的精度为便士,则该字段将以便士为单位的整数(而不是美元的小数)。我在一家银行的 IT 部门工作,这就是那里的工作方式。有些字段的精度更高(例如百分之一便士),但它们仍然是整数。
@adg 是对的:bigdecimal 也是货币的糟糕选择。
@adg 你是对的。在过去的几年里,我一直在使用一些会计和金融应用程序,我们将所有货币字段存储在整数列中。对于这种情况,它更安全。
r
ryan0

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

吸取的教训是“始终使用同构开发和部署数据库!”


好点,在做 Rails 3 年后,我完全同意。
“始终使用同构开发和部署数据库!”
这个线索帮助了我。感谢那!
R
Rokibul Hasan

在 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

我将 :latitude 和 :longitude 保存为 Postgres 中的浮点数,效果很好。
@Robikul:是的,这很好,但是矫枉过正。 decimal(13,9) 对于纬度和经度就足够了。 @ScottW:我不记得了,但是如果 Postgres 使用 IEEE 浮点数,它只会“正常工作”,因为您还没有遇到问题……还没有。它是纬度和经度的不充分格式。 Yo 最终将在最低有效数字中出现错误。
@LonnyEachus 是什么让 IEEE 浮点数不足以满足纬度/经度?
@AlexanderSuraphel 如果您使用十进制纬度和经度,IEEE 浮点数容易受到最低有效数字错误的影响。因此,例如,您的纬度和经度可能具有 1 米的精度,但您可能会有 100 米或更多的误差。如果您在计算中使用它们,则尤其如此。