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

发布于 2024-12-07 07:24:21 字数 401 浏览 5 评论 0原文

据我了解 .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

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

As far as I understand the .round()-functionality in ruby rounds decimals upwards where the last significant number is 5?

For example 1.5.round(0) # => 2 (OK)

but why does 1.025.round(2) # => 1.02, and not 1.03 as I would expect?

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

What can I do to go around this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

浅听莫相离 2024-12-14 07:24:21

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

基本上

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

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

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

This has nothing to do with the last digit being 5 and everything to do with conversion of a decimal value to a double precision floating point value.

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

Basically, the decimal number has to be represented in limited binary format which can only approximate certain decimal values, leading to loss of precision. This can cause some weird behavior, as you have seen.

Best to explain this by showing you... Marshal.dump(1.025) dumps the Float value and shows the value a bit closer to what it really is: 1.0249999999999999. 1.025.to_r will provide you with the fraction which represents the binary value. You can use the arbitrarily precise decimal library, BigDecimal to convert this:

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

When certain decimals are converted to this "approximate" binary number format they will be represented differently, possibly more precisely. So, you might have noticed that 1.085.round(2) results in 1.09 as you'd expect.

This lack of precision with floating point math means it's never, ever appropiate to use floating point values for currency calculations, or even as temporary containers for money values. Arbitrary precision data types should be used at all times for anything involving money.

As a former developer for an extremely large financial company I was constantly shocked by how rarely this advice is heeded and how common the use of floats or doubles is in financial software. Most programmers in that industry I have talked to are not aware that floats and doubles should never store money values. So, don't feel like you are too behind the curve ;-)

tl;dr

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

完美的未来在梦里 2024-12-14 07:24:21

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

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。

I think this is due to the nature of floating point numbers. They're not exact representations of a number:

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

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

So when you enter "1.025" it's represented in the computer as a number that is fractionally less than the value you really wanted. Most of the time it's not a problem but it can throw up the occasional bit of strangeness.

Just to be clear: this isn't a problem with Ruby, it's a problem with floating point numbers in all languages. If it's causing you trouble have a look at BigDecimal.

我们的影子 2024-12-14 07:24:21

使用 Pry 你可以在 Pry 类型中查看 Float#round

So 的底层代码:

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 的round 函数。它符合 IEEE-754

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

Using Pry you can look at the underlying code for Float#round

So in Pry type:

show-method Float#round

which show's the underlying C code:

From: numeric.c in Ruby Core (C Method):
Number of lines: 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);
}

Which show's it's using the C round function. Which is complying to IEEE-754.

Unless you have a very strange edge-case I would recommend that you keep with this type of rounding.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文