【问题标题】:Ruby: Why is 1.025.round(2) rounded to 1.02?Ruby:为什么 1.025.round(2) 舍入到 1.02?
【发布时间】:2011-09-29 08:38:59
【问题描述】:

据我了解,ruby 中的.round()-功能会将小数向上舍入,其中最后一个有效数字是5

例如1.5.round(0) # => 2(确定)

但为什么是1.025.round(2) # => 1.02,而不是我所期望的1.03

irb(main):037:0> 1.025.round(2)
=> 1.02

我能做些什么来解决这个问题?

【问题讨论】:

  • 可能是rounding half to even
  • 不管你不做什么,don't 提交一份关于它的错误报告!
  • 哇,太搞笑了,安德鲁……必须发生很多事情才能准备好预设回复。
  • 在 RubyKaigi 2011 上,我听说过一句话“Float is legacy”。这是村田健太演讲的名字,但我还没有看过它的幻灯片。

标签: ruby floating-point decimal rounding


【解决方案1】:

这与最后一位为 5 无关,而与将十进制值转换为双精度浮点值有关。

http://en.wikipedia.org/wiki/Double_precision_floating-point_format

基本上,十进制数必须以有限的二进制格式表示,只能近似某些十进制值,从而导致精度损失。如您所见,这可能会导致一些奇怪的行为。

最好通过向您展示来解释这一点...Marshal.dump(1.025) 转储浮点值并显示该值更接近实际值:1.02499999999999991.025.to_r 将为您提供代表二进制值的分数。您可以使用任意精确的十进制库 BigDecimal 进行转换:

ruby-1.9.2-p180 :060 > (BigDecimal.new("2308094809027379.0") / BigDecimal.new("2251799813685248.0")).to_s('F')
=> "1.024999999999999911182158029987476766"

当某些小数转换为这种“近似”二进制数格式时,它们的表示方式会有所不同,可能更精确。因此,您可能已经注意到 1.085.round(2) 的结果与您预期的一样1.09

浮点数学缺乏精度意味着永远不适合将浮点值用于货币计算,甚至作为货币值的临时容器。任何涉及金钱的事情都应始终使用任意精度的数据类型。

作为一家超大型金融公司的前开发人员,我经常感到震惊的是,这条建议很少被采纳,而浮点数或双精度数在金融软件中的使用如此普遍。与我交谈过的那个行业的大多数程序员都不知道浮点数和双精度数永远不应该存储货币值。所以,不要觉得你太落后了;-)

tl;博士

使用 BigDecimal:BigDecimal.new("1.025").round(2) => "1.03"

【讨论】:

  • “甚至不能作为货币价值的临时容器。”救了我!
【解决方案2】:

我认为这是由于浮点数的性质造成的。它们不是数字的精确表示:

printf("%f", 1.025)     # rounded to 6 decimal places
=> 1.025000

printf("%.16f", 1.025)  # rounded to 16 decimal places
=> 1.0249999999999999

因此,当您输入“1.025”时,它在计算机中表示为一个比您真正想要的值略小的数字。大多数时候这不是问题,但偶尔会引发一些奇怪的事情。

要明确一点:这不是 Ruby 的问题,而是所有语言中的浮点数的问题。如果它给您带来麻烦,请查看 BigDecimal。

【讨论】:

  • printf 比我的 Marhsal.dump 示例更好地展示了精度损失
【解决方案3】:

使用Pry可以查看Float#round的底层代码

所以在 Pry 类型中:

show-method Float#round

显示底层 C 代码:

来自:Ruby Core 中的 numeric.c(C 方法): 行数:36

static VALUE
flo_round(int argc, VALUE *argv, VALUE num)
{
    VALUE nd;
    double number, f;
    int ndigits = 0, i;
    long val;

    if (argc > 0 && rb_scan_args(argc, argv, "01", &nd) == 1) {
    ndigits = NUM2INT(nd);
    }
    number  = RFLOAT_VALUE(num);
    f = 1.0;
    i = abs(ndigits);
    while  (--i >= 0)
    f = f*10.0;

    if (isinf(f)) {
    if (ndigits < 0) number = 0;
    }
    else {
    if (ndigits < 0) number /= f;
    else number *= f;
    number = round(number);
    if (ndigits < 0) number *= f;
    else number /= f;
    }

    if (ndigits > 0) return DBL2NUM(number);

    if (!FIXABLE(number)) {
    return rb_dbl2big(number);
    }
    val = (long)number;
    return LONG2FIX(val);
}

哪个节目用了C轮函数。符合IEEE-754

除非你有一个非常奇怪的边缘情况,否则我建议你保持这种类型的舍入。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-09
    • 1970-01-01
    • 1970-01-01
    • 2015-07-23
    • 2013-03-22
    • 2010-09-08
    • 2011-06-08
    相关资源
    最近更新 更多