07 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 有 Fixnum 和 Bignum 两个子类,他们所处理的数字大小范围不同。
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
及以后不会使用 Fixnum 和 Bignum,但是内部它们仍然以相同的方式工作,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 个方法ceil
、floor
、round
。
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_i
、to_f
、to_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 中是不准确的,而小数是准确的,了解了常用的数字对象实例方法,例如如何运算、比较、类型转换等等。在实际项目中,我们会不断使用到数字对象,一定要好好学习和总结。