数值类型

整数类型

整数就是没有小数部分的数字。之前使用过的 i32 类型,表示有符号的 32 位整数( i 是英文单词 integer 的首字母,与之相反的是 u,代表无符号 unsigned 类型)。下表显示了 Rust 中的内置的整数类型: image.png|675

整型溢出

在使用编程的过程中难免会发生数的范围超过了类型范围这时候就会发生溢出的现象。

有趣的是:在debug模式下编译器会检测有没有发生整形溢出的现象,如果有发生整数溢出的现象编译就会发生panic.

但是在release模式下,编译器不会进行整数溢出的的检测。当发生整数溢出的时候会按照补码循环溢出进行处理。简单来讲就是取余

要显式的处理可能的溢出,可以使用标准库提供的类型。

  • 使用warpping_*的方法在所有模式下都按照补码循环溢出的规则进行处理
  • 使用check_*方法发生溢出的时候返回None值。
  • 使用overflowing_*方法返回该值和是否溢出的一个bool值
  • 使用saturating_*的方法返回最大值或者最小值

下面是一个演示warpping_*的一个示例代码

fn main() {
    let a : u8 = 255;
    let b = a.wrapping_add(20);
    println!("{}", b);  // 19
}

最后这个输出结果也没有出乎意料。

image.png|675

浮点类型

浮点类型是带有小数点的数字。在Rust中浮点数类型也有两种表示f32,f64. 分别表示32位和64位。在现代CPU中32位的运算速度几乎和64位是一样的。所以默认是f64.

浮点数陷阱

浮点数在其底层表示上有很大的特殊性,这就导致了如果在使用的时候不够谨慎就会造成危险。主要有以下两个原因。

  1. 浮点数是一种近似的表达。由于所有的生活中的数字我们都是用十进制进行标识但是计算机用二进制在底层进行表示,所以我们很难非常准确的表示十进制的小数。
  2. 浮点数在某一些事情上是反直觉的。例如很多人都觉得浮点数是可以进行比较的对吧。但是实际上如果我们编写如下的代码会发生什么呢?
fn main(){
assert!(0.1+0.2==0.3);
}

程序将会panic,因为二进制精度 的问题,0.1+0.2将会在N位之后与0.3发生偏差。

image.png|675

为了避免掉入陷阱当中,我们需要注意以下两点。

  • 避免在浮点数上测试相等性
  • 当结果在数学上存在未定义的时候我们需要格外的小心

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

但是事情到这还没完。

我们对0.1+0.2的f32f64两种形式进行比较。

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);
}

我们会发现在32位的情况下是可以通过的但是在64位的情况下是会panic的。

image.png|675

NaN

NaN:(not a number) 不是一个合法的数。就会出现这个标识。所有和NaN的交互操作都会返回NaN。

  • NaN不能用于比较
  • 但是NaN可以用于判断是否是NaN。

数字运算

就是一般的四则运算等等。

序列(Range)

..=这个符号表示序列,下面举个例子。

for i in 1..=5{
println!("{}",i);
}

使用As进行类型转换

这里先按下不表,在后续的进阶过程中会进行详细的了解。

有理数和复数

Rust 的标准库相比其它语言,准入门槛较高,因此有理数和复数并未包含在标准库中:

  • 有理数和复数
  • 任意大小的整数和任意精度的浮点数
  • 固定精度的十进制小数,常用于货币相关的场景

好在社区已经开发出高质量的 Rust 数值库:num

按照以下步骤来引入 num 库:

  1. 创建新工程 cargo new complex-num && cd complex-num
  2. 在 Cargo.toml 中的 [dependencies] 下添加一行 num = "0.4.0"
  3. 将 src/main.rs 文件中的 main 函数替换为下面的代码
  4. 运行 cargo run
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 的具体类型

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

布尔类型