探索 Rust:标量类型、整数溢出与浮点数操作详解

Rust 的标量类型是一种用于表示单一的、不可拆分的值,它包括整数(整数类型)、浮点数(浮点类型)、字符(字符类型)和布尔值(布尔类型)。

这些类型具有固定的大小,通常用于表示基本的原子数据,支持各种数学运算、条件判断和逻辑操作,是 Rust 中的基本数据构建块。标量类型的值是不可变的,可以在栈上分配,具有严格的类型推断,是 Rust 中数据的基本构成单元。

了解和使用这些标量类型是 Rust 编程的基础,它们在处理数值、文本、逻辑和控制流等各种情况下发挥着重要作用。

整数

Rust的整数类型用于表示整数值,分为有符号整数(可以表示正负数)和无符号整数(只能表示非负数)。

有符号整数包括i8、i16、i32、i64和i128,而无符号整数包括u8、u16、u32、u64和u128。

此外,Rust还有与底层架构相关的整数类型,如 isizeusize,它们的大小取决于计算机架构,通常在32位系统上为32位,64位系统上为64位。

类型位数字节大小最小值最大值
i8有符号 8 位1 字节-128127
i16有符号 16 位2 字节-32,76832,767
i32有符号 32 位4 字节-2,147,483,6482,147,483,647
i64有符号 64 位8 字节-9,223,372,036,854,775,8089,223,372,036,854,775,807
i128有符号 128 位16 字节-170,141,183,460,469,231,731,687,303,715,884,105,727170,141,183,460,469,231,731,687,303,715,884,105,727
isize有符号平台相关整数取决于架构取决于架构取决于架构
u8无符号 8 位1 字节0255
u16无符号 16 位2 字节065,535
u32无符号 32 位4 字节04,294,967,295
u64无符号 64 位8 字节018,446,744,073,709,551,615
u128无符号 128 位16 字节0340,282,366,920,938,463,463,374,607,431,768,211,455
usize无符号平台相关整数取决于架构0取决于架构

整数字面量

Rust 中的整数字面量是用来表示整数值的字面值表示法,可以使用十进制、二进制、八进制、十六进制等进制方式,也可使用可选的后缀来明确整数的类型的方式。

  • 十进制整数字面量:最常见的整数表示法,使用十进制数字。
    • 例如:421230-789
  • 二进制整数字面量:以 0b0B 开头,后跟一系列二进制数字(0 或 1)。
    • 例如:0b1101 表示十进制的 13。
  • 八进制整数字面量:以 0o0O 开头,后跟一系列八进制数字(0 到 7)。
    • 例如:0o17 表示十进制的 15。
  • 十六进制整数字面量:以 0x0X 开头,后跟一系列十六进制数字(0-9 和 a-f 或 A-F)。
    • 例如:0x1A 表示十进制的 26。
  • 字节整数字面量:以 bB 后跟单个字节字符(ASCII 字符或 Unicode 转义序列)。
    • 例如:b'A' 表示 ASCII 字符 ‘A’ 的整数值。
  • 整数字面量后缀:可以附加后缀来指定整数的类型。
    • 例如:42u32(无符号 32 位整数),-123i64(有符号 64 位整数)。

Rust 的整数字面量语法非常灵活,允许开发人员使用不同的进制和后缀来表示整数值,以满足不同的需求。

fn main() {
    // 十进制整数字面量
    let decimal: i32 = 65;
    println!("decimal: {}", decimal);
    // 输出:decimal: 65

    // 二进制整数字面量
    let binary: i32 = 0b1101;
    println!("binary: {}", binary);
    // 输出:binary: 13

    // 八进制整数字面量
    let octal: i32 = 0o17;
    println!("octal: {}", octal);
    // 输出:octal: 15

    // 十六进制整数字面量
    let hex: i32 = 0x1A;
    println!("hex: {}", hex);
    // 输出:hex: 26

    // 字节整数字面量
    let byte: u8 = b'A';
    println!("byte: {}", byte);
    // 输出:byte: 65

    // 整数字面量后缀
    let suffix: u8 = 65u8;
    println!("suffix: {}", suffix);
    // 输出:suffix: 65
}

在 Rust 中,你可以根据你的需求来选择整数类型。通常情况下,如果不明确指定整数类型,Rust 会根据上下文进行类型推导,默认会推导为 i32(有符号 32 位整数)。这是因为 i32 在大多数情况下都足够表达整数值,并且它通常是最高效和常用的整数类型之一。然而,如果你需要更大或更小范围的整数,或者需要无符号整数,你可以显式地选择适合你需求的整数类型,以确保类型安全和性能满足你的预期。

整数溢出

在 Rust 中,整数溢出是指当一个整数超出了其类型所能表示的范围时发生的情况。在 debug 模式下,Rust 默认会进行整数溢出检查,如果出现溢出,程序会触发 panic 并终止。这有助于在开发过程中及早发现潜在的问题。

但在 release 模式下,Rust 默认会启用数值环绕(wrapping),而不会进行溢出检查。这意味着当整数溢出发生时,它们将”环绕”回类型所能表示的最小值或最大值,而不会引发 panic。这样的行为有助于提高性能,但可能导致未预期的结果,因此在编写代码时需要格外小心。

为了明确控制整数溢出的行为,Rust 提供了一些方法,例如使用 wrapping_ 方法执行数值环绕运算,或者使用 checked_ 方法进行溢出检查。开发人员可以根据需求选择适当的方法来处理整数溢出,以确保代码在各种情况下都能安全运行。

我们使用实际的示例来演示下整数溢出的情景。

fn main() {
    let max_u8: u8 = 255; // u8 类型,最大值为 255
    #[allow(arithmetic_overflow)]  // 通过这个编译器指令告诉编译器允许整数溢出
    let overflowed_value = max_u8 + 1; // 在此 +1 即导致整数溢出

    println!("Overflowed Value: {}", overflowed_value);
}

输出的结果

➜ cargo run -r
   Compiling hello_world v0.1.0 (/Users/mac/lanyulei/project/rust/hello_world)
    Finished release [optimized] target(s) in 0.13s
     Running `target/release/hello_world`
Overflowed Value: 0

在这段代码中,我们使用 #[allow(arithmetic_overflow)] 指令告诉编译器允许整数溢出。然后,我们对 max_u8(值为 255)执行加法操作 max_u8 + 1,这会导致整数溢出。由于整数溢出被允许,所以计算的结果是环绕的,即将从 255 环绕到 0,因此输出的结果是 Overflowed Value: 0。这个行为是由整数环绕规则所决定的。

需要注意的是,使用这种方式来 #[allow(arithmetic_overflow)] 关闭整数溢出检查可能会导致程序出现不可预测的行为,因此只应在确定整数溢出是安全的情况下使用,并且需要特别小心处理潜在的问题。 Rust 的默认行为是禁止整数溢出,以确保程序的安全性。

在 Rust 中,处理整数溢出有多种方法,包括标准库中的 Wrapping 以及一些方法。以下是处理整数溢出的方法:

  • std::num::Wrapping<T>:这是标准库提供的一种方式,用于执行整数环绕运算。它可以用于整数类型 T,如 u8i32 等。
  • wrapping_add:执行整数加法运算,导致环绕。
  • wrapping_sub:执行整数减法运算,导致环绕。
  • wrapping_mul:执行整数乘法运算,导致环绕。
  • wrapping_div:执行整数除法运算,导致环绕。
  • wrapping_neg:执行整数的负数运算,导致环绕。
  • wrapping_shl:执行左移运算,导致环绕。
  • wrapping_shr:执行右移运算,导致环绕。
  • overflowing_add:执行整数加法运算,返回结果和溢出标志。
  • overflowing_sub:执行整数减法运算,返回结果和溢出标志。
  • checked_add:执行整数加法运算,如果溢出则返回 None
  • checked_sub:执行整数减法运算,如果溢出则返回 None
  • saturating_add:执行整数加法运算,如果溢出则返回最大值。
  • saturating_sub:执行整数减法运算,如果溢出则返回最小值。

以上方法可用于不同整数类型(例如 u8i32)以及 Wrapping 类型,用于处理整数溢出的不同情况。开发人员可以根据具体的需求选择适当的方法来处理整数溢出。

浮点数

Rust 的浮点类型包括 f32f64,在现在计算机中,f32 和 f64 的计算效率相差无几,因此默认推导的浮点类型是 f64,因为它提供更高的精度和范围,适用于大多数数值的计算,而 f32 则用于需要更紧凑内存表示的特定场景。

  • f32:单精度浮点数,占用 32 位,精度约为 7 位有效数字。
  • f64:双精度浮点数,占用 64 位,精度约为 15 位有效数字。

常用操作

使用字面值或通过类型转换来初始化浮点数变量。

fn main() {
    let x: f32 = 3.14;
    let y: f64 = 2.71828;

    println!("x is {} and y is {}", x, y);
    // 输出:x is 3.14 and y is 2.71828
}

标准的数学运算,如加法、减法、乘法和除法。

fn main() {
    let sum = 1.2 + 3.4;
    let difference = 10.0 - 1.0;
    let product = 2.0 * 1.5;
    let quotient = 5.0 / 2.0;

    println!(
        "sum: {}, difference: {}, product: {}, quotient: {}",
        sum, difference, product, quotient
    );
    // 输出:sum: 4.6, difference: 9, product: 3, quotient: 2.5
}

比较运算符进行浮点数的比较,但要注意浮点数精度可能导致意外结果。

fn main() {
    let a = 0.1 + 0.2;
    let b = 0.3;

    println!("a = {}, b = {}", a, b);
    // 输出:a = 0.30000000000000004, b = 0.3

    if a == b {
        println!("Equal");
    } else {
        println!("Not equal"); // 输出 "Not equal",由于浮点数精度限制
    }
    // 输出:Not equal
}

使用 .floor().ceil().round() 方法可以将浮点数舍入到最接近的整数。

fn main() {
    let pi: f32 = 3.14159;

    // .round() 方法将浮点数舍入到最接近的整数。
    let rounded_pi: f32 = pi.round();
    println!("Rounded pi: {}", rounded_pi);
    // 输出:Rounded pi: 3

    // .floor() 方法将浮点数舍入到最接近的较小整数。
    let floor_pi: f32 = pi.floor();
    println!("Floor pi: {}", floor_pi);
    // 输出:Floor pi: 3

    // .ceil() 方法将浮点数舍入到最接近的较大整数。
    let ceil_pi: f32 = pi.ceil();
    println!("Ceil pi: {}", ceil_pi);
    // 输出:Ceil pi: 4
}
  • .round() 方法将浮点数舍入到最接近的整数。
  • .floor() 方法将浮点数舍入到最接近的较小整数。
  • .ceil() 方法将浮点数舍入到最接近的较大整数。

使用 .abs() 方法获取浮点数的绝对值。

fn main() {
    let num: f32 = -5.0;
    let abs_num: f32 = num.abs();

    println!("Absolute value of {} is {}", num, abs_num);
    // 输出:Absolute value of -5 is 5
}

浮点数类型支持的特殊值,如 NaN(Not-a-Number)和 Infinity(无穷大)。

  • NaN(Not-a-Number): NaN 表示一个无效的浮点数值,通常在数学计算中出现错误时使用。可以使用 f32::NANf64::NAN 常量来创建 NaN 值。NaN 不等于任何其他值,包括自身。
  • Infinity(无穷大):无穷大表示一个超过浮点数表示范围的值,通常在除以零等操作时使用。可以使用 f32::INFINITYf64::INFINITY 常量来表示正无穷大,使用 -f32::INFINITY-f64::INFINITY 常量来表示负无穷大。无穷大值在数学运算中的行为是按照标准浮点数规则的,例如加法无穷大与任何有限数相加仍然是无穷大。
fn main() {
    let nan_value = f64::NAN;
    println!("NaN: {}", nan_value);
    // 输出:NaN: NaN

    let result = nan_value + 5.0;
    println!("Result: {}", result);
    // 输出:Result: NaN

    // ---
    let positive_infinity = f64::INFINITY;
    let negative_infinity = f64::NEG_INFINITY;

    println!("Positive Infinity: {}", positive_infinity);
    // 输出:Positive Infinity: inf
    println!("Negative Infinity: {}", negative_infinity);
    // 输出:Negative Infinity: -inf

    let result = positive_infinity + 5.0;
    println!("Result: {}", result);
    // 输出:Result: inf
}

使用 as 运算符进行浮点数类型之间的转换。

fn main() {
    let a: f32 = 3.14;
    let b: f64 = a as f64;

    println!("b = {}", b);
    // 输出:b = 3.140000104904175
}

更多有关浮点类型的操作可以在 Rust 相关文档中找到详细信息。由于涉及的操作众多,无法一一演示,建议查阅相关文档以深入了解浮点数操作的各种用法和功能。 Rust 的官方文档和社区资源提供了丰富的信息,以帮助您掌握浮点数的使用和处理。

布尔值

Rust 的布尔类型(bool)是一种简单的数据类型,它只有两个值:truefalse,用于表示逻辑上的真和假。布尔类型在内存中通常占用一个字节的空间大小(8比特),用于存储逻辑条件的结果,常用于控制程序流程、条件判断和逻辑运算。

fn main() {
    let is_true: bool = true;
    println!("is_true: {}", is_true);
    // 输出:is_true: true

    let is_false: bool = false;
    println!("is_false: {}", is_false);
    // 输出:is_false: false
}

字符

Rust 的字符类型 char 用于表示单个 Unicode 字符,它占用 4 个字节的内存空间(32 位)。由于 Unicode 可以表示世界上几乎所有的字符,char 允许在 Rust 中处理各种语言和符号,使其非常强大。

由于 Rust 强调安全性,因此字符类型确保每个字符都是有效的 Unicode 字符,避免了一些常见的文本处理错误。

char 字符串以单引号括起来,例如 'A''😀',并且支持许多与字符相关的操作,例如索引、迭代和比较。这使得 Rust 在处理文本和字符数据时非常灵活和国际化。

可以使用单引号 ' 来初始化字符变量,且字符必须用单引号括起来。

fn main() {
    let my_char: char = 'A';

    println!("my_char: {}", my_char);
    // 输出:my_char: A
}

char 类型支持表示任意 Unicode 字符,包括特殊字符、表情符号和非拉丁字母等。

fn main() {
    let heart_char: char = '❤';
    let smiley_char: char = '😃';
    let chinese_char: char = '你';

    println!(
        "heart_char: {}, smiley_char: {}, chinese_char: {}",
        heart_char, smiley_char, chinese_char
    );
    // 输出:heart_char: ❤, smiley_char: 😃, chinese_char: 你
}

某些字符需要通过转义序列表示,例如反斜杠 \ 后跟字符。

let tab_char: char = '\t'; // 制表符
let newline_char: char = '\n'; // 换行符

字符字面值可以包含转义字符或 Unicode 转义序列。

fn main() {
    let escape_char: char = '\x41'; // 十六进制表示字符 'A'
    let unicode_char: char = '\u{1F604}'; // Unicode 表情符号 😄

    println!(
        "escape_char: {}, unicode_char: {}",
        escape_char, unicode_char
    );
    // 输出:escape_char: A, unicode_char: 😄
}

可以使用比较运算符来比较字符。

fn main() {
    let a: char = 'A';
    let b: char = 'B';

    let is_equal = a == b;
    println!("is_equal: {}", is_equal);
    // 输出:is_equal: false

    let is_less_than = a < b;
    println!("is_less_than: {}", is_less_than);
    // 输出:is_less_than: true
}

如果你想了解更多有关 Rust 字符类型的操作,请查阅 Rust 的官方文档以获得更多深入的知识。

结束语

至此,你已经对 Rust 的标量类型,如整数、浮点数、布尔值和字符有了深入的了解。这些类型是 Rust 的基本数据构建块,用于表示单一、不可拆分的值。在你的学习旅程中,我们将继续探讨 Rust 的其他数据类型,包括数组、元组等。

如果你在这个旅程中遇到了问题或需要帮助,请随时向我提问。祝愿你在学习 Rust 和编程的过程中一切顺利,取得巨大的成功!

本文为原创文章,未经授权禁止转载本站文章。
原文出处:兰玉磊的个人博客
原文链接:https://www.fdevops.com/2023/09/06/rust-31300
版权:本文采用「署名-非商业性使用-相同方式共享 4.0 国际」知识共享许可协议进行许可。

(4)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
兰玉磊兰玉磊
上一篇 2023年9月3日
下一篇 2023年9月8日

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注