34 个发布版
0.8.5 | 2022年5月18日 |
---|---|
0.8.3 | 2022年3月10日 |
0.8.2 | 2021年10月4日 |
0.7.6 | 2021年4月21日 |
0.3.1 | 2018年12月23日 |
#1768 在 解析器实现
1,790,618 每月下载量
在 1,015 个 仓库中使用 (直接使用 32 个)
750KB
12K SLoC
lexical
用于在 no_std
环境中的高性能数字转换例程。这不需要任何标准库特性或系统分配器。
类似项目
如果您需要 Lexical 浮点解析算法的最小化、稳定和编译时友好的版本,请参阅 minimal-lexical。如果您需要最小化、高性能的浮点解析器,Rust 标准库的较新版本应该足够使用。
目录
入门
将 lexical 添加到您的 Cargo.toml
[dependencies]
lexical = "^6.0"
并开始使用 lexical
// Number to string
use lexical_core::BUFFER_SIZE;
let mut buffer = [b'0'; BUFFER_SIZE];
lexical_core::write(3.0, &mut buffer); // "3.0", always has a fraction suffix,
lexical_core::write(3, &mut buffer); // "3"
// String to number.
let i: i32 = lexical_core::parse("3")?; // Ok(3), auto-type deduction.
let f: f32 = lexical_core::parse("3.5")?; // Ok(3.5)
let d: f64 = lexical_core::parse("3.5")?; // Ok(3.5), error checking parse.
let d: f64 = lexical_core::parse("3a")?; // Err(Error(_)), failed to parse.
为了在泛型代码中使用 lexical,提供了 FromLexical
(用于 parse
) 和 ToLexical
(用于 to_string
) 的 trait 约束。
/// Multiply a value in a string by multiplier, and serialize to string.
fn mul_2<T>(value: &str, multiplier: T)
-> Result<String, lexical_core::Error>
where
T: lexical_core::ToLexical + lexical_core::FromLexical,
{
let value: T = lexical_core::parse(value.as_bytes())?;
let mut buffer = [b'0'; lexical_core::BUFFER_SIZE];
let bytes = lexical_core::write(value * multiplier, &mut buffer);
Ok(std::str::from_utf8(bytes).unwrap())
}
部分/完整解析器
Lexical 具有部分和完整解析器:完整解析器确保在解析时使用整个缓冲区,而不会忽略尾随字符,部分解析器尽可能多地解析字符,并返回解析值和解析数字的数量。在遇到错误时,lexical 会返回一个错误,指示错误类型和缓冲区内错误发生的索引。
完整解析器
// This will return Err(Error::InvalidDigit(3)), indicating
// the first invalid character occurred at the index 3 in the input
// string (the space character).
let x: i32 = lexical_core::parse(b"123 456")?;
部分解析器
// This will return Ok((123, 3)), indicating that 3 digits were successfully
// parsed, and that the returned value is `123`.
let (x, count): (i32, usize) = lexical_core::parse_partial(b"123 456")?;
无 std
lexical-core
不依赖于标准库或系统分配器。要在 no_std
环境中使用 lexical-core
,请将以下内容添加到 Cargo.toml
[dependencies.lexical-core]
version = "0.8.5"
default-features = false
# Can select only desired parsing/writing features.
features = ["write-integers", "write-floats", "parse-integers", "parse-floats"]
并开始使用 lexical
// A constant for the maximum number of bytes a formatter will write.
use lexical_core::BUFFER_SIZE;
let mut buffer = [b'0'; BUFFER_SIZE];
// Number to string. The underlying buffer must be a slice of bytes.
let count = lexical_core::write(3.0, &mut buffer);
assert_eq!(buffer[..count], b"3.0");
let count = lexical_core::write(3i32, &mut buffer);
assert_eq!(buffer[..count], b"3");
// String to number. The input must be a slice of bytes.
let i: i32 = lexical_core::parse(b"3")?; // Ok(3), auto-type deduction.
let f: f32 = lexical_core::parse(b"3.5")?; // Ok(3.5)
let d: f64 = lexical_core::parse(b"3.5")?; // Ok(3.5), error checking parse.
let d: f64 = lexical_core::parse(b"3a")?; // Err(Error(_)), failed to parse.
特性
Lexical 为每个数字转换例程提供功能门控,如果启用了某些数字转换,则编译时间更快。这些功能可以为 lexical-core
(不需要系统分配器) 和 lexical
启用/禁用。默认情况下,所有转换都已启用。
- parse-floats: 启用字符串到浮点数的转换。
- parse-integers: 启用字符串到整数的转换。
- write-floats: 启用浮点到字符串的转换。
- write-integers: 启用整数到字符串的转换。
Lexical 具有高度可定制性,并包含许多其他可选功能。
- std: 启用 Rust 标准库的使用(默认启用)。
- power-of-two: 启用与非十进制字符串之间的转换。
启用 power_of_two 后,以下基数有效:2、4、8、10、16 和 32。否则,只有 10 有效。这允许常见的十六进制整数/浮点数之间的转换,无需为其他基数预先计算大量表。
- radix: 启用与非十进制字符串之间的转换。
启用 radix 后,2 到 36(包含)之间的任何基数都有效,否则,只有 10 有效。
- format: 自定义数字解析和写入中可接受的数字格式。
启用 format 后,数字格式通过位标志和掩码打包到一个
u128
中。这些定义了解析和写入的数字的有效语法,包括启用数字分隔符、要求整数或分数数字,以及切换大小写敏感的指数字符。 - compact: 优化二进制大小,以牺牲性能为代价。
这最小化了预计算表的使用,生成了显著更小的二进制文件。
- safe: 要求所有数组索引都进行边界检查。
对于数字解析器,这实际上是一个无操作,因为它们除了在可以简单地证明无边界检查的索引是正确的情况外,都使用安全索引。数字写入器频繁使用不安全的索引,因为我们容易高估输出中的数字数量,因为输入是固定长度的。
- f16: 添加对 16 位浮点数的数字转换支持。
添加了
f16
,一个半精度 IEEE-754 浮点类型,以及bf16
,Brain Float 16 类型,并提供了这些浮点数之间的数字转换。请注意,由于这些是存储格式,因此没有本机算术运算,所有转换都使用中间f32
完成。
为了在禁用边界检查时确保安全性,我们对所有数字转换例程进行了广泛的模糊测试。有关更多信息,请参阅下面的 安全性 部分。
Lexical 还非常重视代码膨胀:算法既优化了性能又优化了大小。默认情况下,这侧重于性能,但是,使用 compact
功能,您也可以选择在牺牲性能的情况下减少代码大小。紧凑算法在牺牲性能的情况下最小化了预计算表和其他优化的使用。
自定义
⚠ 警告:如果更改写入的显著数字的数量、禁用指数表示法或更改指数表示法阈值,
BUFFER_SIZE
可能不足以容纳结果输出。WriteOptions::buffer_size
将提供写入的字节数的正确上限。如果提供了长度不足的缓冲区,lexical-core 将引发恐慌。
每种语言都有针对有效数值输入的竞争性规范,这意味着 Rust 的数字解析器将错误地接受或拒绝来自不同编程或数据语言的输入。例如
// Valid in Rust strings.
// Not valid in JSON.
let f: f64 = lexical_core::parse(b"3.e7")?; // 3e7
// Let's only accept JSON floats.
const JSON: u128 = lexical_core::format::JSON;
let options = ParseFloatOptions::new();
let f: f64 = lexical_core::parse_with_options::<JSON>(b"3.0e7", &options)?; // 3e7
let f: f64 = lexical_core::parse_with_options::<JSON>(b"3.e7", &options)?; // Errors!
由于不同编程和数据语言中数字语法的差异很大,我们提供了两个不同的API来简化具有不同语法要求的数字转换。
- 数字格式API(通过
format
或power-of-two
启用功能)。这是一个包含标志的打包结构,用于指定编译时数字解析或写入的语法规则。这包括以下特性:数字字符串的基数、数字分隔符、大小写敏感的指数字符、可选的基数前缀/后缀等。
- 选项API。
这包含解析和写入数字的运行时规则。这包括指数断点、舍入模式、指数和十进制点字符,以及NaN和Infinity的字符串表示。
以下示例中记录了功能的一部分,但是完整的规范可以在API参考文档中找到。
数字格式 API
数字格式类提供了许多标志,用于在解析或写入时指定数字语法。当启用power-of-two
功能时,还会添加额外的标志
- 有效数字的基数(默认为
10
)。 - 指数基数的基数(默认为
10
)。 - 指数数字的基数(默认为
10
)。
当启用format
功能时,还会启用许多其他语法和数字分隔符标志,包括
- 一个数字分隔符字符,用于将数字分组以提高可读性。
- 是否允许前导、尾随、内部和连续的数字分隔符。
- 切换所需的小数部分,如小数点前的数字。
- 切换是否允许特殊浮点数或它们是否大小写敏感。
因此存在许多预定义的常量,用于简化常见用例,包括
- JSON、XML、TOML、YAML、SQLite等。
- Rust、Python、C#、FORTRAN、COBOL字面量和字符串等。
以下是一个构建自定义数字格式的示例
const FORMAT: u128 = lexical_core::NumberFormatBuilder::new()
// Disable exponent notation.
.no_exponent_notation(true)
// Disable all special numbers, such as Nan and Inf.
.no_special(true)
.build();
// Due to use in a `const fn`, we can't panic or expect users to unwrap invalid
// formats, so it's up to the caller to verify the format. If an invalid format
// is provided to a parser or writer, the function will error or panic, respectively.
debug_assert!(lexical_core::format_is_valid::<FORMAT>());
选项 API
选项API允许在运行时自定义数字解析和写入,例如指定最大有效数字、指数字符等。
以下是一个构建自定义选项结构的示例
use std::num;
let options = lexical_core::WriteFloatOptions::builder()
// Only write up to 5 significant digits, IE, `1.23456` becomes `1.2345`.
.max_significant_digits(num::NonZeroUsize::new(5))
// Never write less than 5 significant digits, `1.1` becomes `1.1000`.
.min_significant_digits(num::NonZeroUsize::new(5))
// Trim the trailing `.0` from integral float strings.
.trim_floats(true)
// Use a European-style decimal point.
.decimal_point(b',')
// Panic if we try to write NaN as a string.
.nan_string(None)
// Write infinity as "Infinity".
.inf_string(Some(b"Infinity"))
.build()
.unwrap();
文档
Lexical的API参考可以在docs.rs上找到,同样可以在lexical-core上找到。算法的详细描述可以在这里找到
此外,还记录了Lexical如何处理数字分隔符以及实现大整数算术的描述。
验证
浮点数解析
正确执行浮点数解析很困难,从libstdc++的strtod到Python的实现中发现了重大错误。为了验证lexical的准确性,我们采用了以下外部测试
- Hrvoje Abraham的strtod测试用例。
- Rust的test-float-parse单元测试。
- Testbase的压力测试,用于将十进制转换为二进制。
- Nigel Tao的测试是从Freetype、Google的double-conversion库、IBM的IEEE-754R合规性测试以及其他许多精心编排的示例中提取的。
- 各种 复杂 案例 在博客上被报道。
尽管词法分析可能包含导致舍入错误的错误,但它已通过一套全面的随机数据和近似中点表示进行测试,应该适用于大多数用例的速度和正确性。
度量
这里展示了各种基准、二进制大小和编译时间。
构建时间
启用所有数字转换时的编译时间。对于更详细的分解,请参阅 构建时间。
二进制大小
以优化级别 "2" 编译的剥离二进制文件的大小。对于更详细的分解,请参阅 二进制大小。
基准 -- 解析整数
对整个范围上均匀分布的随机生成的整数的基准测试。对于更详细的分解,请参阅 基准测试。
基准 -- 解析浮点数
对来自各种现实世界数据集的浮点数进行解析的基准测试。对于更详细的分解,请参阅 基准测试。
基准 -- 写入整数
对整个范围上均匀分布的随机生成的整数的写入基准测试。对于更详细的分解,请参阅 基准测试。
基准 -- 写入浮点数
对通过随机数生成器和从JSON文档解析生成的浮点数进行写入的基准测试。对于更详细的分解,请参阅 基准测试。
安全性
由于整数和浮点数写入器使用了内存不安全代码,我们对浮点数写入器和解析器进行了广泛的模糊测试。模糊测试工具可以在 模糊测试 下找到,并且正在持续运行。到目前为止,我们已经解析和写入了超过720亿个浮点数。
由于整数写入器的简单逻辑和整数解析器中内存不安全性的缺乏,我们对两者都进行了最小程度的模糊测试,并使用边缘情况进行了测试,迄今为止没有发现内存安全问题。
平台支持
lexical-core 在各种平台上进行了测试,包括大端和小端系统,以确保代码的可移植性。支持的架构包括
- x86_64 Linux、Windows、macOS、Android、iOS、FreeBSD 和 NetBSD。
- x86 Linux、macOS、Android、iOS 和 FreeBSD。
- aarch64 (ARM8v8-A) Linux、Android 和 iOS。
- armv7 (ARMv7-A) Linux、Android 和 iOS。
- arm (ARMv6) Linux 和 Android。
- mips (MIPS) Linux。
- mipsel (MIPS LE) Linux。
- mips64 (MIPS64 BE) Linux。
- mips64el (MIPS64 LE) Linux。
- powerpc (PowerPC) Linux。
- powerpc64 (PPC64) Linux。
- powerpc64le (PPC64LE) Linux。
- s390x (IBM Z) Linux。
lexical-core 也应在各种其他架构和指令集中运行。如果您在任何架构上编译 lexical-core 时有任何问题,请提交错误报告。
版本和版本支持
版本支持
目前支持以下版本
- v0.8.x
- v0.7.x(维护)
- v0.6.x(维护)
Rustc 兼容性
- v0.8.x 支持 1.51+,包括稳定版、测试版和夜间版。
- v0.7.x 支持 1.37+,包括稳定版、测试版和夜间版。
- v0.6.x 支持 Rustc 1.24+,包括稳定版、测试版和夜间版。
请报告任何在兼容的 Rustc 版本上编译支持的 lexical-core 版本时出现的错误。
版本控制
Lexical 使用 语义版本控制。移除对最新稳定版 Debian 或 Ubuntu 以上的 Rustc 版本的支持被视为不兼容的 API 变更,需要增加主版本号。
变更日志
所有更改均在 CHANGELOG 中进行了记录。
许可
Lexical 同时受 Apache 2.0 许可证和 MIT 许可证的约束。有关完整的许可证详细信息,请参阅 LICENSE.md 文件。
贡献
除非您明确声明,否则根据 Apache-2.0 许可证定义的,您有意提交给 Lexical 的任何贡献都应按照上述方式双重许可,不附加任何额外条款或条件。向存储库贡献意味着遵守 行为准则。
有关如何向 Lexical 贡献的流程,请参阅 开发 快速入门指南。