在Ruby on Rails 3中处理小数

11
我想计算一个产品的平均净价。在我的产品模型中,我有:total_sold和:total_net_revenue。在方法中直接进行除法运算似乎总是得到0。我转而使用BigDecimal,因为我认为这是问题所在...但是在下面代码的最新迭代中,当答案是小数时,我仍然得到0。
def avg_price
  BigDecimal(total_sold.to_s) / (BigDecimal(total_net_revenue.to_s) / 100)
end  

净收入是以美分为单位的,这就是为什么我要除以100。有人能指出我做错了什么或者该怎么做吗?


2
为什么要用 BigDecimal?这些值末尾加上 to_f 不足以吗? - Ryan Bigg
5个回答

13
total_net_revenue / total_sold
或者
total_net_revenue / total_sold / 100.0
或者
total_net_revenue.to_f / total_sold / 100

这三种方法会提供越来越精确的结果,如果你需要的话。请记住,“平均价格”是“平均价格 / 销售量”。这是每个项目的货币数,所以您需要按照特定的顺序进行除法运算。


12

首先:你使用了错误的除法方式。

100个物品/150美元 = 每美元0.667个物品

相比较:

150美元/ 100个物品=每个物品1.50美元

其次:和其他编程语言一样,你需要强制转换数学公式中的一个数字为小数,以便结果也被视为小数。由于你的收入是整数,这意味着所有三个值都是整数,这就导致你得到的结果是整数。要获取小数,请将其中一个数字作为浮点数进行转换。

换句话说,要获得你需要的结果,请执行以下操作:

price_per_item = (total_net_revenue.to_f / 100) / total_sold

8

你正在进行的是整数除法,它会舍弃任何余数,因为它在整数中无法表达,例如:

1 / 3 # == 0

正如其他回答者所提到的,您可以强制进行浮点数除法。您需要通过调用.to_f强制第一个参数为浮点数(这里是1)。第二个参数将自动转换为浮点数,即:

1.to_f / 3 # ~ 0.3333...

请注意,一旦你使用浮点数,通常情况下结果就不再精确。这就是为什么我写了 ~ 0.333。
具体细节更加复杂。在二进制浮点运算中,这在当今微处理器中很常见,2的幂仍然是精确的。但是,例如整数3不再被精确表示,而只能在浮点表示的精度范围内表示(通常为“双精度”约为1E-16左右)。
长话短说,这里有一个经验法则:如果你正在处理需要精度的货币值(是否曾经注意到电话账单上的1美分差异?),不要存储计算结果,也不要将值存储为浮点数。相反,使用整数或十进制数据类型(在内部存储字符串)。仅在显示和按需时计算浮点结果。避免在浮点数中添加大型和小型值,并避免在浮点数中进行链接计算。重新设计你的代数以避免除法,直到最后。Ruby还支持Rational数据类型,可以精确表示分数,可能会有用。
这些问题属于“浮点误差传播”的科学范畴,如果需要,可以查找更多信息。

感谢详细的解释。我通常将货币值存储为分,并基于分进行计算,以避免小数,因为我确实需要不时地存储这些值。 - Slick23
这通常也是我发现最好的方法。还有一些宝石,例如money gem,可以为该方法增添一些便利。 - Wolfram Arnold

3

您需要将整数形式的金额转换为浮点数(带小数点的数字),这样计算结果才会得到一个浮点数而不是整数。

一般来说,它的工作原理如下:

some_integer.to_f / some_other_integer.to_f  # returns a float

0

即使其中一个值是浮点数,结果也将是浮点数。

如果您使用Rails并且数据存储在特定模型中,ActiveRecord有特殊的方法,您不需要自己计算。

Model.average("field_with_data")

还提供了最小值、最大值、计数和求和方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接