# 数值类型
我朋友有一个领导(读者：你朋友？黑人问号)说过一句话：所有代码就是0和1，简单的很。咱不评价这句话的正确性，但是计算机底层由01组成，倒是真的。

计算机和数值关联在一起的时间，远比我们想象的要长，因此数值类型可以说是有计算机以来就有的类型，下面内容将深入讨论Rust的数值类型以及相关的运算符。

## 整数和浮点数

Rust使用一个相对传统的语法来创建整数(`1`,`2`,...)和浮点数(`1.0`,`1.1`,...)。整数、浮点数的运算和你在其它语言上见过的一致，都是通过常见的运算符来完成。

> 不仅仅是数值类型，Rust也允许在复杂类型上定义运算符，例如在自定义类型上定义`+`运算符，这种行为被称为运算符重载, Rust具体支持的可重载运算符见[这里](../../appendix/operators.md#运算符)

#### 整数类型

**整数**是没有小数部分的数字。之前使用过的`i32`类型, 表示有符号的32位整数（`i` 是英文单词 *integer* 的首字母，与之相反的是 `u`，代表无符号 `unsigned` 类型）。下表显示了 Rust 中的内置的整数类型：


| 长度       | 有符号类型 | 无符号类型 |
|------------|---------|---------|
| 8 位       | `i8`    | `u8`    |
| 16 位      | `i16`   | `u16`   |
| 32 位      | `i32`   | `u32`   |
| 64 位      | `i64`   | `u64`   |
| 128-位     | `i128`  | `u128`  |
| 视架构而定   | `isize` | `usize` |

类型定义的形式统一为：有无符号 + 类型大小(位数)。**无符号数**表示数字只能取正数，而**有符号**则表示数字即可以取正数又可以取负数。就像在纸上写数字一样：当要强调符号时，数字前面可以带上正号或负号；然而，当很明显确定数字为正数时，就不需要加上正号了。有符号数字以[二进制补码](https://en.wikipedia.org/wiki/Two%27s_complement)形式存储。

每个有符号类型规定的数字范围是  -(2<sup>n - 1</sup>) ~ 2<sup>n -
1</sup> - 1，其中 `n` 是该定义形式的位长度。因此 `i8` 可存储数字范围是 -(2<sup>7</sup>) ~ 2<sup>7</sup> - 1，即 -128 ~ 127。无符号类型可以存储的数字范围是 0 ~ 2<sup>n</sup> - 1，所以 `u8` 能够存储的数字为 0 ~ 2<sup>8</sup> - 1，即 0 ~ 255。

此外，`isize` 和 `usize` 类型取决于程序运行的计算机cpu类型： 若cpu是32位的，则这两个类型是32位的，同理，若cpu是64位，那么它们则是64位.

整形字面量可以用下表的形式书写：


| 数字字面量         | 示例          |
|------------------|---------------|
| 十进制            | `98_222`      |
| 十六进制          | `0xff`        |
| 八进制            | `0o77`        |
| 二进制            | `0b1111_0000` |
| 字节 (仅限于 `u8`) | `b'A'`        |


这么多类型，有没有一个简单的使用准则？答案是肯定的，Rust整形默认使用`i32`，例如`let i = 1`，那`i`就是`i32`类型，因此你可以首选它，同时该类型也往往是性能最好的。 `isize` 和 `usize` 的主要应用场景是用作集合的索引。

> ##### 整型溢出
>
> 比方说有一个 `u8` ，它可以存放从 0 到 255 的值。那么当你将其修改为范围之外的值，比如 256，则会发生**整型溢出**。关于这一行为 Rust 有一些有趣的规则：当在 debug 模式编译时，Rust 会检查整型溢出，若存在这些问题，则使程序在编译时 *panic*(崩溃,Rust 使用这个术语来表明程序因错误而退出)。
>
> 在当使用 `--release` 参数进行 release 模式构建时，Rust **不**检测溢出。相反当检测到整型溢出时，Rust 会进行一种被称为二进制补码的方式进行（*two’s complement wrapping*）操作。简而言之，大于该类型最大值的数值会被补码转换成该类型能够支持的对应数字的最小值。比如在 `u8` 的情况下，256 变成 0，257 变成 1，依此类推。程序不会 panic，但是该变量的值可能不是你期望的值。依赖整型溢出的行为不是一种正确的做法。
>
> 要显式处理溢出的可能性，可以使用标准库针对原始数字类型提供的以下的一系列方法：
>
> - 使用 `wrapping_*` 方法在所有模式下进行包裹，例如 `wrapping_add`
> - 如果使用 `checked_*` 方法时发生溢出，则返回 `None` 值
> - 使用 `overflowing_*` 方法返回该值和一个指示是否存在溢出的布尔值
> - 使用 `saturating_*` 方法使值达到最小值或最大值

#### 浮点类型

**浮点类型数字** 是带有小数点的数字，在 Rust 中浮点类型数字也有两种基本类型： `f32` 和 `f64`，分别为 32 位和 64 位大小。默认浮点类型是 `f64`，在现代的 CPU 中它的速度与 `f32` 几乎相同，但精度更高。

下面是一个演示浮点数的示例：

```rust
fn main() {
    let x = 2.0; // f64

    let y: f32 = 3.0; // f32
}
```

浮点数根据 `IEEE-754` 标准表示。`f32` 类型是单精度浮点型，`f64` 为双精度。

#### 数字运算

Rust 支持所有数字类型的基本数学运算：加法、减法、乘法、除法和取模运算。下面代码各使用一条 `let` 语句来说明相应运算的用法：


```rust
fn main() {
    // 加法
    let sum = 5 + 10;

    // 减法
    let difference = 95.5 - 4.3;

    // 乘法
    let product = 4 * 30;

    // 除法
    let quotient = 56.7 / 32.2;

    // 求余
    let remainder = 43 % 5;
}
```

这些语句中的每个表达式都使用了数学运算符，并且计算结果为一个值，然后绑定到一个变量上。[附录](../../appendix/operators.md)中给出了 Rust 提供的所有运算符的列表。


再来看一个综合性的示例:

```rust
fn main() {
  // 编译器会进行自动推导，给予twenty i32的类型
  let twenty = 20;
  // 类型标注
  let twenty_one: i32 = 21;
  // 通过类型后缀的方式进行类型标注：22是i32类型
  let twenty_two = 22i32;
  
  // 只有同样类型，才能运算
  let addition = twenty + twenty_one + twenty_two;
  println!("{} + {} + {} = {}", twenty, twenty_one, twenty_two, addition);
  
  // 对于较长的数字，可以用_进行分割，提升可读性
  let one_million: i64 = 1_000_000;
  println!("{}", one_million.pow(2));
  
  // 定义一个f32数组，其中42.0会自动被推导为f32类型
  let forty_twos = [
    42.0,
    42f32,
    42.0_f32,
  ];

// 打印数组中第一个值，其中控制小数位为2位
println!("{:02}", forty_twos[0]);
}
 ```

#### 浮点数陷阱

浮点数由于底层格式的特殊性，导致了如果在使用浮点数时不够谨慎，就可能造成危险，有两个原因：
1. **浮点数往往是你想要数字的近似表达**
浮点数类型是基于二进制实现的，但是我们想要计算的数字往往是基于十进制，例如`0.1`在二进制上并不存在精确的表达形式，但是在十进制上就存在。这种不匹配性导致一定的歧义性，更多的，虽然浮点数能代表真实的数值，但是由于底层格式问题，它往往受限于定长的浮点数精度，如果你想要表达完全精准的真实数字，只有使用无限精度的浮点数才行

2. **浮点数在某些特性上是反直觉的**
例如大家都会觉得浮点数可以进行比较，对吧？是的，它们确实可以使用`>`,`>=`等进行比较，但是在某些场景下，这种直觉上的比较特性反而会害了你。因为`f32`,`f64`上的比较运算实现的是`std::cmp::PartialEq`特征(类似其他语言的接口), 但是并没有实现`std::cmp::Eq`特征，但是后者在其它数值类型上都有定义，说了这么多，可能大家还是云里雾里，用一个例子来举例：

Rust的HashMap数据结构，是一个KV类型的hash map实现，它对于`K`没有特定类型的限制，但是要求能用作`K`的类型必须实现了`std::cmp::Eq`特征，因此这意味着你无法使用浮点数作为`HashMap`的`Key`，来存储键值对，但是作为对比，Rust的整数类型、字符串类型、布尔类型都实现了该特征，因此可以作为`HashMap`的`Key`。

为了避免上面说的两个陷阱，你需要遵守以下准则：
- 避免在浮点数上测试相等性
- 当结果在数学上可能存在未定义时，需要格外的小心

来看个小例子:
```rust
fn main() {
  // 断言0.1 + 0.2与0.3相等
  assert!(0.1 + 0.2 == 0.3);
}
```

你可能以为，这段代码没啥问题吧，实际上它会panic(程序崩溃，抛出异常)，因为二进制精度问题，导致了0.1 + 0.2并不严格等于0.3，它们可能在小数点N位后存在误差。

那如果非要进行比较呢？可以考虑用这种方式`(0.1 + 0.2 - 0.3).abs() < 0.00001`,具体小于多少，取决于你对精度的需求.

讲到这里，相信大家基本已经明白了，为什么操作浮点数时要格外的小心，但是还不够，下面再来一段代码，直接震撼你的灵魂:

```rust
fn main() {
    let abc: (f32, f32, f32) = (0.1, 0.2, 0.3);
    let xyz: (f64, f64, f64) = (0.1, 0.2, 0.3);

    println!("abc (f32)");
    println!("   0.1 + 0.2: {:x}", (abc.0 + abc.1).to_bits());
    println!("         0.3: {:x}", (abc.2).to_bits());
    println!();

    println!("xyz (f64)");
    println!("   0.1 + 0.2: {:x}", (xyz.0 + xyz.1).to_bits());
    println!("         0.3: {:x}", (xyz.2).to_bits());
    println!();

    assert!(abc.0 + abc.1 == abc.2);
    assert!(xyz.0 + xyz.1 == xyz.2);
}
```

运行该程序，输出如下:

```console
abc (f32)
   0.1 + 0.2: 3e99999a
         0.3: 3e99999a
 
xyz (f64)
   0.1 + 0.2: 3fd3333333333334
         0.3: 3fd3333333333333
 
thread 'main' panicked at 'assertion failed: xyz.0 + xyz.1 == xyz.2',
➥ch2-add-floats.rs.rs:14:5
note: run with `RUST_BACKTRACE=1` environment variable to display
➥a backtrace
```

仔细看，对`f32`类型做加法时，`0.1+0.2`的结果是`3e99999a`，0.3也是`3e99999a`，因此`f32`下的`0.1+0.2=0.3`通过测试，但是到了`f64`类型时，结果就不一样了，因为f64精度高很多，因此在小数点非常后面发生了一点微小的变化，`0.1+0.2`以`4`结尾，但是0.3以`3`结尾，这个细微区别导致`f64`下的测试失败了，并且抛出了异常。

是不是**blow your mind away**? 没关系，在本书的后续章节中类似的直击灵魂的地方还很多，这就是敢号称`Rust语言圣经`的底气!

#### NaN

对于数学上未定义的结果，例如对负数取平方根`-42.1.sqrt()`，会产生一个特殊的结果：Rust的浮点数类型使用`NaN`(not a number)来处理这些情况。

**所有跟`NaN`交互的操作，都会返回一个`NaN`**，而且`NaN`不能用来比较，下面的代码会崩溃:

```rust
fn main() {
  let x = (-42.0_f32).sqrt();
  assert_eq!(x, x);
}
```

出于防御性编程的考虑，可以使用`is_nan()`等方法，可以用来判断一个数值是否是`NaN`:

```rust
fn main() {
    let x = (-42.0_f32).sqrt();
    if x.is_nan() {
        println!("未定义的数学行为")
    }
}
```

## 序列(Range)

Rust提供了一个非常简洁的方式，用来生成连续的数值，例如`1..5`，生成从1到4的连续数字，不包含5; `1..=5`，生成从1到5的连续数字,包含5，它的用途很简单，常常用于循环中：
```rust
for i in 1..=5 {
    println!("{}",i);
}
```

最终程序输出:
```console
1
2
3
4
5
```

序列只允许用于数字或字符类型，因为编译器会在编译时检查序列不为空。如下是一个使用字符类型序列的例子：

```rust
for i in 'a'..='z' {
    println!("{}",i);
}
```

## 有理数和复数

Rust的标准库相比其它语言，对于准入的门槛较高，因此有理数和复数并未包含在标准库中:
- 有理数和复数
- 任意大小的整数和任意精度的浮点数
- 固定精度的十进制小数，常用于货币相关的场景

好在社区已经开发出高质量的Rust数值库：[num](https://crates.io/crates/num).

按照以下步骤来引入`num`库：
1. 创建新工程`cargo new complex-num && cd complex-num`
2. 在`Cargo.toml`中的`[dependencies]`下添加一行`num = 0.4`
3. 将`src/main.rs`文件中的`main`函数替换为下面的代码
4. 运行`cargo run`

```rust
use num::complex::Complex;
 
 fn main() {
   let a = Complex { re: 2.1, im: -1.2 };
   let b = Complex::new(11.1, 22.2);
   let result = a + b;
 
   println!("{} + {}i", result.re, result.im)
 }
```


## 总结

之前提到了过Rust的数值类型和运算跟其他语言较为相似，但是实际上，除了语法上的不同之外，还是存在一些差异点:

- **Rust拥有相当多的数值类型**. 因此你需要熟悉这些类型所占用的字节数，这样就知道该类型允许的大小范围以及你选择的类型是否能表达负数
- **类型转换必须是显式的**. Rust永远也不会偷偷把你的16bit整数转换成32bit整数
- **Rust的数值上可以使用方法**. 例如你可以用以下方法来将`13.14`取整: `13.14_f32.round()`, 在这里我们使用了类型后缀，因为编译器需要知道`13.14`的具体类型

数值类型的讲解已经基本结束，接下来来看看字符和布尔类型。




 