二进制、八进制和十六进制(又称 hex)是基数不同的记数系统。 二进制的基数是 2,八进制是 8,十六进制是 16。 在 Crystal 中,普通的整数是十进制的。 二进制、八进制和十六进制都是整数的表示形式。 这意味着它们表示的是正数和负数(包括零),不带分数或小数,并且支持所有我们能用整数完成的运算。
二进制是一种基数为 2 的记数系统,只使用数字 0 和 1。 它通常用来表示计算机中电流经过晶体管和开关时的 0(“关闭”)和 1(“开启”)两种状态,以及磁性存储介质中的正负电荷。 二进制可以表示十进制中使用的所有整数。
二进制系统的一小段看起来像这样,不过它会无限延伸,并不会到 128 就结束:
| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|---|---|---|---|---|---|---|
| 2 ** 7 | 2 ** 6 | 2 ** 5 | 2 ** 4 | 2 ** 3 | 2 ** 2 | 2 ** 1 | 2 ** 0 |
那么,如果我们要表示数字 6,用二进制就是:110
| 位值 | 4 | 2 | 1 |
|---|---|---|---|
| 二进制数 | 1 | 1 | 0 |
运算过程是:4 + 2 + 0 = 6
再比如:19
| 位值 | 16 | 8 | 4 | 2 | 1 |
|---|---|---|---|---|---|
| 二进制数 | 1 | 0 | 0 | 1 | 1 |
这个二进制数是:10011
运算过程是:16 + 0 + 0 + 2 + 1 = 19
在 Crystal 中,我们可以用 0b 前缀来表示二进制字面量。
如果写0b10011,Crystal 会把它解释为二进制数,并转换成十进制。
0b10011
# => 19
typeof(0b10011)
# => Int32
如果给一个数字加上 0b 前缀,但后面的内容不是二进制,就会引发编译错误。
0b10211
# => Error: unexpected token: "211"
由于二进制数就是整数,我们可以对它们进行一切对整数能做的运算。
0b10011 + 0b10011
# => 38
0b10011 * 0b10011
# => 361
0b10011 / 3
# => 6.333333333333333
Crystal 会自动把二进制字面量转换成 Int32。
要把 Int32 转换成二进制表示,可以使用 <int>.to_s(base) 方法,并把 base 实参设为 2。
该方法会返回这个二进制数的字符串表示:
19.to_s(2)
# => "10011"
要把字符串形式的二进制数转换成整数,可以使用 <int>.to_i(base) 方法,把基数(2)作为实参传进去:
"10011".to_i(2)
# => 19
如果给错基数(或者给出不合法的二进制表示),就会引发 ArgumentError:
"011021".to_i(2)
# => Invalid Int32: "011021" (ArgumentError)
有一些方法可以用在整数上,获取该数字二进制表示的相关信息。
<int>.bit_length 会返回表示该数字所需的位数:
19.bit_length
# => 5
<int>.bit(index) 会返回指定下标处的位,从右边开始数(最低有效位):
19.bit(0)
# => 1
0b10011.bit(1)
# => 1
八进制是一种基数为 8 的记数系统。 它使用数字 0、1、2、3、4、5、6 和 7。
在 Crystal 中,我们可以用 0o 前缀表示八进制数。
和二进制一样,Crystal 会自动把八进制表示转换成 Int32。
0o123
# => 83
给数字加上 0o 前缀,但后面的内容不是八进制,就会引发编译错误。
和二进制一样,我们可以用 <int>.to_s(base) 方法把 Int32 转换成八进制表示。
也可以用 <int>.to_i(base) 方法把八进制表示转换成 Int32。
83.to_s(8)
# => "123"
"123".to_i(8)
# => 83
和二进制一样,给错基数会引发 ArgumentError。
十六进制是一种基数为 16 的记数系统。 它使用数字 0 - 9 和字母 A、B、C、D、E 和 F。 A 是 10,B 是 11,C 是 12,D 是 13,E 是 14,F 是 15。
在 Crystal 中,我们可以用 0x 前缀表示十六进制数。
和二进制、八进制一样,Crystal 会自动把十六进制字面量转换成 Int32。
#=> 0x123
291
给一个非十六进制的数字加上 0x 前缀,会引发编译错误。
和二进制一样,我们可以用 <int>.to_s(base) 方法把 Int32 转换成十六进制表示。
也可以用 <int>.to_i(base) 方法把十六进制表示转换成 Int32。
291.to_s(16)
# => "123"
"123".to_i(16)
# => 291
和二进制、八进制一样,给错基数会引发 ArgumentError。
把数字转换成字符串表示时,我们可以指定数字的精度。
也就是用来表示该数字的最少位数。
如果数字的位数少于精度,就会在字符串表示的前面补上前导零。
方法是把 precision 实参传给 <int>.to_s(<base>, precision: <value>) 方法。
291.to_s(16, precision: 5)
# => "00123"
543.to_s(precision: 2)
# => "543"
你的朋友厌倦了为照片编辑软件付费,决定自己写一个。 你的朋友决定把这款新软件命名为 Foto Fusionist。
Foto Fusionist 是一款能完成很多操作的照片编辑软件,不过到目前为止你只实现了其中少数几项功能。 不过,你的朋友对目前的进展非常满意,请你再实现几项功能。
第一项功能是,你的朋友希望能够把数字的二进制表示转换为整数。 这样做是因为软件要能读取颜色通道的二进制表示,并将其转换为整数。
实现方法FotoFusionist.binary_to_int,它接收一个类型为String的形参binary。
该方法应返回这个二进制数的整数表示。
FotoFusionist.binary_to_int("10011")
# => 19
你的朋友想要实现的第二项功能是,能够从整数中获取最低有效位。 最低有效位是最靠右的那一位。 它之所以被称为最低有效位,是因为它的值最小。
借助最低有效位,可以判断能否通过减少表示颜色通道所需的位数来优化图像。
实现方法FotoFusionist.least_significant_bit,它接收一个类型为Int32的形参number。
该方法应返回这个数的最低有效位。
FotoFusionist.least_significant_bit(0b10011)
# => 1
FotoFusionist.least_significant_bit(0b10010)
# => 0
你的朋友想要实现的第三项功能是,能够从颜色通道中获取十六进制值。 需要十六进制值,是为了能把颜色通道转换为十六进制表示,而且这个十六进制表示必须由两位数字组成。
实现方法FotoFusionist.hex_value,它接收一个类型为Int32的形参number。
该方法应以String的形式返回这个数的十六进制值。
FotoFusionist.hex_value(19)
# => "13"
你的朋友想要实现的第四项功能是,能够从 RGB 颜色中获取十六进制值。 这样用户就能得到一种颜色的单个字符串表示。 RGB 颜色是由三个颜色通道表示的颜色:红、绿、蓝。 每个颜色通道都由一个 0 到 255 之间的数字表示。
RGB 颜色的十六进制值是该颜色以十六进制表示的字符串,其中红色通道在前,接着是绿色通道,最后是蓝色通道。
实现方法FotoFusionist.hex_value_from_rgb,它接收三个类型为Int32的形参:red、green和blue。
该方法应以String的形式返回该 RGB 颜色的十六进制值。
FotoFusionist.hex_value_from_rgb(255, 0, 0)
# => "ff0000"