从 Fortran 格式解析 Rust 中的浮点数

apm*_*ney 4 floating-point rust

我正在用 Rust 重写 C++ 解析器以获取旧版 ASCII 数据格式。这种格式的实数值允许以任何 Fortran 识别的格式存储。不幸的是,Fortran 可以识别 Rust(或大多数其他语言)无法识别的一些格式。例如,值 101.01 可能表示为

  • 101.01
  • 1.0101E2
  • 101.01e0
  • 101.01D0
  • 101.01d0
  • 101.01+0
  • 1010.1-1

前三个都是 Rust 原生识别的。剩下的四个构成了挑战。在 C++ 中,我们使用以下例程来解析这些值:

double parse(const std::string& s){
  char* p;
  const double significand = strtod(&s[0], &p);
  const long exponent = (*p == '\0') ? 
                          0 : isalpha(*p) ?
                            strtol(p+1, nullptr) :
                              strtol(p, nullptr);
  return significand * pow(10, exponent);
}
Run Code Online (Sandbox Code Playgroud)

在查看 Rust 文档后,标准库似乎没有以strtod和的方式提供部分字符串解析strtol。出于性能原因,我想避免多次传递字符串或使用正则表达式。

小智 5

这本来是对 Veedrac 答案的评论,但评论有点长。

正如 Veedrac 所解释的,准确解析浮点数是很困难的。标准库中的实现完全准确并且经过合理优化。特别是,对于朴素算法起作用的大多数输入来说,它并不比朴素不准确算法慢多少。你应该使用它。完整免责声明:我写的。

我不同意 Veedrac 的地方是如果您想重用该代码如何继续。从标准库中删除它是一个坏主意。它非常庞大,大约有 2.5k 行代码,并且偶尔仍然会进行更改/改进 - 尽管很少且大多是在非常小的方面。但有一天我会找到时间重写缓慢的路径,使其变得更好更快,这是我承诺的。如果您删除此代码,则必须获取该core::num::dec2flt模块并修改子parse模块以识别其他指数。当然,您不会自动从未来的改进中受益,如果您对性能感兴趣,这将是一种耻辱。

最明智的方法是将其他格式转换为 Rust 支持的格式。如果它是 a d,D或裸露的,+您可以简单地将其替换为 ane并将其传递给 string 。仅在这种情况下,1010.1-1您需要插入e并移动字符串的指数部分。这应该不会花费太多性能。浮点字符串很短(最多 20 个字节左右,通常要少得多),并且实际的转换工作在每个字节上完成了大量工作。对于 C++ 代码也是如此,因为strtod在 glibc 中也是准确的。或者至少它试图做到这一点,它无法修复围绕它构建的临时算法。无论如何,它正在努力。

另一种可能性是使用 FFI 调用 C 的strtod. 使用libc 箱并调用libc::strtod. &str这需要一些扭曲才能从原始指针转换为c_char,并且它将严重处理内部 0 字节,但是您显示的代码无论如何都不是非常健壮。这将允许您将算法转换为 Rust,具有相同的性能、语义和(不)准确度。