Ruby 数字对象

人们所熟知的数字类型有整数、小数、分数等等,今天让我们学习在 Ruby 中学习数字对象,了解在 Ruby 中数字是如何进行运算的。

1. 为什么要使用数字对象

自然界的每个事物,我们通常根据其特征将数字分为不同的集合,开发的时候我们一共能接触到的数字按照特征可以分为自然数、整数、有理数、无理数。为了能让我们对数字进行我们熟知的运算操作(例如:加减乘除),Ruby 使用了数字对象。

2. Ruby 中数字对象

在不同的编程语言中拥有各式各样的数字类型。在 Ruby 中我们将数字对象分为整型(Integer)有理数(Rational)浮点数(Float)小数(BigDecimal)四种。

2.1 整型(Integer

自然数是指从1开始按顺序加1的自然数。例如:1、2、3、4、5…。整数是相同的数字,但也包含与此对应的负数以及0,即0,-1,-2,-3,-4,…。Ruby对此集合有一个表示形式:抽象类 Integer

注意事项:在 Ruby2.4 版本之前,Integer 有 FixnumBignum 两个子类,他们所处理的数字大小范围不同。

Fixnum 的范围是在 -2^62 ~ 2^62-1 之间,超出范围则自动变为 Bignum。

实例:

# Ruby2.4之前
# 以下 > 均代表irb的输入模式
# => 后面是返回值
> integer = 2 ** 62 - 1
=> 4611686018427387903
> integer.class
=> Fixnum
> integer = (integer + 1).class
=> Bignum
> (-integer - 2).class
=> Bignum

解释:当前代码运行环境是在 Ruby 2.2.4,我们可以看到 2^62-1 所表示的整数类是 Fixnum,当把 integer 增加 1 后,类名变成了 Bignum。

Ruby 2.4及以后不会使用 FixnumBignum,但是内部它们仍然以相同的方式工作,Ruby 会自动从一种类型切换到另一种类型。意思是较小的 Integer 数字仍然以与 Fixnum 相同的方式运行。

# Ruby2.4及之后的版本
> integer = 2 ** 62 - 1
=> 4611686018427387903
> integer.class
=> Integer
> integer = (integer + 1).class
=> Integer
> (-integer - 2).class
=> Integer

为了能让 Integer 更好地被读取,您可以在数字之间增加下划线,比如 188_000 就会比 188000 更好读懂。

实例:

> 188_000
=> 188000

对于常见的基本的运算您需要注意除法,当除数与被除数均为 Integer 的时候,返回的结果仍然是 Integer,小数点及后面的值会被省略。

实例:

> 1000000/6
=> 166666

2.2 有理数(Rational

在现实中,我们不能用整数代表一切。比如整数 1 除以 2,您可以用两种方式查看结果,一种是 1/2,另一种是 0.5。而这种类似比率或除法的表现形式,被称作 有理数(Rational)

下面是有理数的创建形式:

实例:

> Rational(1, 2) # 第一个数为分子,第二个数为分母
=> (1/2)

除此之外您还可以使用硬编码的模式:使用十进制的数字并在后面加上r

实例:

> 0.5r
=> (1/2)

当有理数之间或者有理数与整数进行运算的时候,得到的结果都是有理数类型。

实例:

> rational = Rational(1, 2) + Rational(1, 2)
=> (1/1)
> rational.class # 查看这个变量的类
=> Rational
> rational.to_i # to_i意思为转换成Integer
=> 1

Tips : 如果您对获取结果的可读性和精度有极高的要求,而且整型不满足结果的需求,请使用有理数,

2.3 浮点数(Float

不是所有的数字都可以使用比例的方式来表示,比如 π 。为了在 Ruby 中表示 无理数(Irrationals),我们使用了浮点数(Float)

下面举一个 π 的例子,我们使用 Math::PI 来获取π。

实例:

> Math::PI
=> 3.141592653589793
> Math::PI.class
Float

我们在 Ruby 中所定义的带小数点的数字也都是浮点数。

> 1.2.class
=> Float
> 0.00001.class
=> Float

Tips:浮点数在 Ruby 中是不精确的

实例:

> 0.2 + 0.1 == 0.3
=> false
> 0.2 + 0.1
=> 0.30000000000000004
> (0.2 + 0.1 + 0.7) == 1.0
=> true

您会发现在浮点数的运算中,2.0 - 1.1和0.9并不相等,发生这种情况是因为1985年由IEEE定义的标准(以及 Ruby在其内部使用的标准)以有限的精度存储数字(这个可以不深究)。如果需要始终正确的十进制数,则需要使用小数(BigDecimal)

当 Float 的结果非常大超出了其精度范围,我们使用 Infinity

实例:

> 500.0e1000 # 500.0的1000次方
=> Infinity

超出范围的计算也是同样的结果,比如除数为 0 的情况。

实例:

> 1 / 0.0
=> Infinity
> -1 / 0.0
=> -Infinity

Tips:也可以直接使用Float::INFINITY来直接调用。

为了显示非数字的结果,Ruby 引入了特殊值 NaN。

实例:

> 0 / 0.0
=> NaN
> Float::INFINITY / Float::INFINITY
=> NaN
> 0 * Float::INFINITY
=> NaN

2.4 小数(BigDecimal

在 Ruby 中 小数(BIgDecimal)可以为您提供一个任意精度的十进制数字。

在使用小数前,我们要引入一个bigdecimal,定义小数的时候我们要使用一个字符串String)(内容用双引号或单引号括起来,在Ruby字符串对象的章节中会详细讲到)。

> require 'bigdecimal'
=> true
> BigDecimal("0.2") + BigDecimal("0.1") == 0.3
=> true

解释:为了能使用BigDecimal方法,要执行require 'bigdecimal'来引用这个库。

经验:在开发中对用户设置余额或者金融计算的时候,一定要使用小数,因为这种情况不允许我们出现小数点精度不准确的问题。

注意事项:我们创建 BigDecimal 的时候一定要使用字符串作为参数,使用浮点数同样会造成精度缺失的问题。

既然小数是准确的那为什么 Ruby 默认的带小数点的数字是Float类型呢?

答案是:Float会 比 BigDecimal 快很多,大约快了12 倍

Calculating -------------------------------------
          bigdecimal    21.559k i/100ms
               float    79.336k i/100ms
-------------------------------------------------
          bigdecimal    311.721k (± 7.4%) i/s -      1.552M
               float      3.817M (±11.7%) i/s -     18.803M

Comparison:
               float:  3817207.2 i/s
          bigdecimal:   311721.2 i/s - 12.25x slower

这是因为 BigDecimal 为了精确地表达精度,将整数部分和小数部分分开运算,所以花费了很多时间。

3. 常见的数字对象的实例方法

数字对象是一个对象,它拥有很多实例方法,下面会讲到一些常见的实例方法,如果是某个类型专用,我会使用括号标记出来。

3.1 基本数学运算

基本数学运算就是我们常见的加(+)减(-)乘(*)除(/)以及取余(%)。

经验:

  • 整型之间进行运算结果返回整型;

  • 如果有浮点数参与运算结果返回浮点数;

  • 整型的除法会返回商的整数部分。

实例:

1 + 1           # 2
1 + 1.0         # 2.0
10 / 4          # 2
10 / 4.0        # 2.5
10.0 / 4.0      # 2.5
10 % 3          # 1

3.2 值大小比较

常见的有等于(==)、不等于(!=)、大于(>)、小于(<)、大于等于(>=)、小于等于(<=)。

实例:

1 == 1.0  # true
2 > 1     # true
1 <= 0    # false

也可以对算数表达式结果进行判断。

实例:

1 + 1 == 2  #true

注意事项:

因为浮点数不准确,不建议使用浮点数进行精确的比较。精确的比较请使用 小数(BigDecimal)

实例:

5.01 == 5.0 + 1.01  # false

3.3 判断值与数字类型是否均相等

eql? 方法則可以判断值和类型是否均相同。

实例:

1 == 1.0      # true
1.eql?(1.0)   # false 1是Integer,1.0是Float

3.4 奇偶性的判断(整型)

odd?是奇数的判断,even?是偶数的判断。

3.odd?   # true
2.even?  # true

3.5 小数点位数保留

这里我们有 3 个方法ceilfloorround

  • ceil返回不小于该数字的最大整数;

  • round返回该数字四舍五入后的整数;

  • floor返回不大于该数字的最大整数。

实例:

2.5.ceil   # 3
2.5.round  # 3
2.5.floor  # 2

我们也可以通过传递参数,来调整位数,默认参数为0,往小数点右边为正,左边为负。

实例:

2.555.ceil(1)   # 2.6
2.555.round(1)  # 2.6
2.555.floor(1)  # 2.5
2.555.ceil(-1)  # 10
2.555.round(-1) # 0
2.555.floor(-1) # 0

3.6 类别转换

常用的有to_ito_fto_s

  • to_i转换为整型;

  • to_f转换为浮点型;

  • to_s转换为字符串。

实例:

1.0.to_i  # 1
1.to_f    # 1.0
1.0.to_s  # "1.0"

3.7 最大公因数(整型)

使用 gcd(),例如:求 10 和 5 的最大公因数。

实例:

10.gcd(5)  # 5

3.8 最小公倍数(整型)

使用 lcm(),例如:取 10 和 5 的最小公倍数。

实例:

10.lcm(5)  # 10

3.9 绝对值

使用 abs,例如:取 -1 和 1.0 的绝对值。

-1.abs   # 1
1.0.abs  # 1.0

3.10 幂

有两种方式,第一种为**

2**10  # 1024

第二种为pow()

2.pow(10)

除此之外 pow 还可以传递第二个参数,意思为在取幂之后再求余数。

2.pow(10, 100)  # 24,相当于 2**10 % 100

3.11 判断是否为 0

使用zero?

实例:

0.zero? # true

4. 小结

本章节我们学习了整型、有理数、浮点数、小数,知道了浮点数在 Ruby 中是不准确的,而小数是准确的,了解了常用的数字对象实例方法,例如如何运算、比较、类型转换等等。在实际项目中,我们会不断使用到数字对象,一定要好好学习和总结。