Ruby:为什么1.025.round(2)舍入到1.02?

时间:2011-09-29 08:38:59

标签: ruby floating-point decimal rounding

据我所知,.round() - ruby​​中的功能向上舍入小数点,其中最后一个有效数字为5

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

但为什么1.025.round(2) # => 1.02而不是1.03正如我所期望的那样?

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

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

3 个答案:

答案 0 :(得分:20)

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

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

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

最好通过向您展示... Marshal.dump(1.025)转储Float值并显示更接近实际值的值: 1.0249999999999999 1.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"

答案 1 :(得分:4)

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

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。

答案 2 :(得分:2)

使用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

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