数值
数值类
https://www.kancloud.cn/imxieke/ruby-base/107300
数值类的构成
在数值类中,有像 -1、0、1、10 这样的表示整数的 Integer
类,也有像 0.1、3.141592 这样的具有精度的、表示浮点小数的 Float
类。
这些数值类都被定义为了 Numeric
类的子类。另外,Integer
类又可以分为两种,一种是表示计算机硬件可以处理的数值的 Fixnum
类,另外一种是表示比 Fixnum
更大的数值的 Bignum
类。
程序中用到的整数一般都是 Fixnum
类范围内的整数。如果使用的整数超过了 Fixnum
的范围,Ruby 就会自动将其转换为 Bignum
类。因此,在写程序的时候,我们几乎可以忽略上述整数类的区别。下面是计算 2 的 10 次幂以及 2 的 1000 次幂的例子,**
是表示乘方的运算符。
执行示例
Ruby 也可以处理有理数和复数。表示有理数用 Rational
类,表示复数用 Complex
类。
Rational
对象用“Rational( 分子 , 分母 )
”的形式定义
上述这样的分数计算,可以用 Rational
对象改写成下面那样。我们还可以使用 Rational.to_f
方法将其转换为 Float
对象。
Complex
对象用“Complex( 实数 , 虚数 )
”的形式定义。以下是计算复数 2i 的 2 次幂的例子:
数值的字面量
数值对象的字面量
字面量 | 作用(括号内为 10 进制的值)
| - 123 | 表示10 进制整数 0123 | 表示8 进制整数(83) 0o123 | 表示8 进制整数(83) 0d123 | 表示10 进制整数(123) 0x123 | 表示16 进制整数(291) 0b1111011 | 表示2 进制整数(123) 123.45 | 浮点小数 1.23e4 | 浮点小数的指数表示法(1.23×10 的4 次幂=12300.0) 1.23e-4 | 浮点小数的指数表示法(1.23×10 的-4 次幂=0.000123)
单纯的数字罗列表示 10 进制整数。以 0b
开头的数值表示 2 进制数,以 0 或者 0o
开头的数值表示 8 进制数,以 0d
开头的数值表示 10 进制数,以 0x
开头的数值表示 16 进制数。字面量中的 _
会被自动忽略。因此,在使用每 3 位数字间隔一下这样的数值表示方法时会十分方便。
包含小数点的数值为浮点小数。我们还可以采用有效数字与指数配合的科学计数法来表示浮点小数。格式为“有效数字”+“英文字母 e(或者 E)”+“表示指数的整数”。
算数运算
算数运算的运算符
运算符 | 运算
| -
/ | 除法运算 % | 取余运算 ** | 乘方运算
Integer
对象与 Float
对象的运算结果为 Float
对象。
Integer
对象之间、Float
对象之间的运算结果分别为 Integer
对象、Float
对象。
这里需要注意的是,指数为负整数的乘方返回的结果是表示有理数的 Rational
对象。
除法
除了 / 和 % 以外,数值对象中还有一些与除法相关的方法。
x.div(y)
返回 x 除以 y 后的商的整数。
x.quo(y)
返回 x 除以 y 后的商,如果 x、y 都是整数则返回
Rational
对象。x.modulo(y)
与 x % y 等价。
x.divmod(y)
将 x 除以 y 后的商和余数作为数组返回。商是将 x / y 的结果去掉小数点后的部分而得到的值。余数的符号与 y 的符号一致,余数的值为 x % y 的结果。假设有运算式如下,
这时,下面的等式是成立的。
x.remainder(y)
返回 x 除以 y 的余数,结果的符号与 x 的符号一致。
另外,除数为 0 时,
Integer
类会返回错误,而Float
类则会返回Infinity
(无限大)或者NaN
(Not a Number)。如果再用这两个值进行运算,那么结果只会返回Infinity
或者NaN
。程序把输入的数据直接用于运算的时候,除数有可能会为 0,我们应当注意避免这样的情况发生。
Math 模块
Math
模块提供了三角函数、对数函数等常用的函数运算的方法。该模块中定义了模块函数和常量,例如,求平方根时,可以采用下述方法。
Math 模块定义的方法
方法名 | 作用
| - acos(x) | 反余弦函数 acosh(x) | 反双曲余弦函数 asin(x) | 反正弦函数 asinh(x) | 反双曲正弦函数 atan(x) | 反正切函数 atan2(x, y) | 表示 4 个象限的反正切函数 atanh(x) | 反双曲正切函数 cbrt(x) | 立方根 cos(x) | 余弦函数 cosh(x) | 双曲余弦函数 erf(x) | 误差函数 erfc(x) | 余补误差函数 exp(x) | 指数函数 frexp(x) | 把一个浮点数分解为尾数和指数 gamma(x) | 伽玛函数 hypot(x, y) | 计算三角形的斜边长度 ldexp(x, y) | 返回 x 乘以 2 的 y 次幂的值 lgamma(x) | 伽马函数的自然对数 log(x) | 底数为 e 的对数(自然对数) log10(x) | 底数为 10 的对数(常用对数) log2(x) | 底数为 2 的对数 sin(x) | 正弦函数 sinh(x) | 双曲正弦函数 sqrt(x) | 平方根 tan(x) | 正切函数 tanh(x) | 双曲正切函数
Math 模块定义的常量
常量名 | 作用
| - PI | 圆周率(3.141592653589793) E | 自然对数的底数(2.718281828459045)
数值类型转换
将 Integer
对象转换为 Float
对象时,可以使用 to_f
方法。相反,使用 to_i
方法则可以将 Float
对象转换为 Integer
对象(Integer.to_i
方法和 Float.to_f
方法返回与接收者一样的值)。另外,也可以把字符串转换为数值。
Float.to_i
方法返回的结果会把小数点以后的值去掉。我们用 round
方法对小数进行四舍五入的处理。
返回比接收者大的最小整数用 ceil
方法,返回比接收者小的最大整数用 floor
方法。
我们还可以将数值转换为 Rational
对象和 Complex
对象,分别使用 to_r
和 to_c
方法,如下所示。
位运算
Integer
类的位运算符
运算符 | 运算
~
| 按位取反(一元运算符) &
| 按位与 |
| 按位或 ^
| 按位异或 ((a&~b|~a&b)) >>
| 位右移 <<
| 位左移
随机数
有时候随机性可能会帮助我们解决一些问题。随机性一般有以下特质。
没有规则和法则依据
一定范围内的数会均等地出现
拿掷骰子为例,我们不能预测下一个投出的是哪一面,但骰子各个面投出的几率都是一样的。我们把这样的情况称为随机,随机得到的数值称为随机数。在掷骰子或者洗扑克牌那样需要偶然性的情况下,或者像加密后的密码那样希望得到一些难以被预测的数据时,一般都会用到随机数。
我们可以用
Random.rand
方法得到随机数。不指定参数时,Random.rand
方法返回比 1 小的浮点小数。参数为正整数时,返回 0 到该正整数之间的数值。程序不能生成真正的随机数,只能通过某种算法生成看起来像随机数的值,这样的随机数称为模拟随机数。生成模拟随机数需要以某个值为基础,这个值称为随机数的种子。模拟随机数终究只是通过计算得到的数值,只要随机数的种子一样,那么得到值就有可能重复出现。使用
Random.new
方法初始化随机数生成器,然后再使用Random.rand
方法,就可以对Random
对象指定随机数种子,从而生成随机数。Random.new
方法不指定参数的情况下,则会用随机生成的随机数种子初始化Random
对象,因此每次得到的随机数组也会不一样。在信息安全领域中,“优质的随机”是一个重要的课题。生成用于加密 key 的随机数时,不能重复出现是非常重要的,因此就需要我们慎重地选择难以被预测的随机种子。在一些特殊的情况下可能会需要初始化
Random
对象,而一般情况下直接用最开始介绍的Random.rand
方法就足够了。
计数
除了数值计算外,Integer
类还能计算处理的次数、数组的元素个数等。接下来介绍的方法就是按照数值指定的次数执行循环处理的迭代器。
n.times{|i| … }
循环 n 次,从 0 到 n-1 的值会被依次赋值给块变量。
from.upto(to){|i| … }
从 from 开始循环对 i 进行加 1 处理,直到 i 等于 to。from 比 to 大时不会执行循环处理。
from.downto(to){…}
从 from 开始循环对 i 进行减 1 处理,直到 i 等于 to。from 比 to 小时不会执行循环处理。
from.step(to, step){…}
从 from 开始循环对 i 进行加 step 处理,直到 i 等于 to。step 为正数时,from 比 to 大时不会执行循环处理。step 为负数时,from 比 to 小时不会执行循环处理。
如果不对
times
、upto
、downto
、step
的各方法指定块,则会返回Enumerator
对象。这样,之前通过step
方法的块获取的一连串数值,就同样也可以通过Enumerator.collect
方法获取。
近似值误差
处理浮点小数时很容易因误差产生问题。这里我们来看看具体的例子,执行下面的程序后会产生意想不到的结果。
虽然我们期待 0.1 + 0.2 与 0.3 的比较结果为 true
,但实际结果却是 false
。为什么会这样呢?
在 10 进制中,就像 1/10、1/100、1/1000……这样,我们会用 10 取幂后的倒数来表示数值。而另一方面,Float
类的浮点小数则是用 2 取幂后的倒数来表示,如 1/2、1/4、1/8……。因此,在处理 1/5、1/3 这种用 2 进制无法正确表示的数值时,结果就会产生误差。而如果要用 2 进制的和来表示这类数值的话,计算机就必须在适当的位置截断计算结果,这样就产生了近似值误差。
如果可以把小数转换为两个整数相除的形式,那么通过使用 Rational
类进行运算,就可以避免近似值误差。
另外,Ruby 还提供了 bigdecimal
库,可以有效处理拥有更多小数位的 10 进制数。
Comparable 模块
Ruby 的比较运算符(==
、<=
等)实际上都是方法。Comparable
模块里封装了比较运算符,将其 Mix-in
到类后,就可以实现实例间互相比较的方法。Comparable 在英语中就是“可以比较”的意思。
Comparable 模块封装的方法
<>
==
>=
>
between?
Comparable 模块中的各运算符都会使用 <=>
运算符的结果。<=>
运算符如果能像下表那样定义的话,上表中的各个方法就都可以使用。
状态 | 结果
| - a <> 时 | -1(比 0 小) a == b 时 | 0 a > b 时 | 1(比 0 大)
下面的 Vector
类表示拥有 x 和 y 两个坐标的向量。为了比较向量间的坐标,这里定义了 <=>
运算符。然后,通过包含(include)Comparable
模块,就可以实现上表中的比较方法。