mesh_loader/utils/float/
mod.rs

1// Rust port of fast_float's float parser.
2//
3// Adapted from core::num::dec2flt with partial parsing support added, and very tricky and unsafe x87 FPU-related hack removed.
4//
5// Source: https://github.com/rust-lang/rust/tree/1.80.0/library/core/src/num/dec2flt
6//
7// Copyright & License of the original code:
8// - https://github.com/rust-lang/rust/blob/1.80.0/COPYRIGHT
9// - https://github.com/rust-lang/rust/blob/1.80.0/LICENSE-APACHE
10// - https://github.com/rust-lang/rust/blob/1.80.0/LICENSE-MIT
11//
12// # References
13//
14// - Daniel Lemire, Number Parsing at a Gigabyte per Second, Software: Practice and Experience 51 (8), 2021.
15//   https://arxiv.org/abs/2101.11408
16// - https://github.com/fastfloat/fast_float
17
18#![allow(
19    clippy::cast_possible_truncation,
20    clippy::cast_possible_wrap,
21    clippy::cast_precision_loss,
22    clippy::cast_sign_loss,
23    clippy::items_after_statements,
24    clippy::module_inception,
25    clippy::redundant_else
26)]
27
28pub(crate) mod common;
29mod decimal;
30mod float;
31mod lemire;
32mod number;
33pub(crate) mod parse;
34mod slow;
35mod table;
36
37use self::{
38    common::{BiasedFp, ByteSlice},
39    float::RawFloat,
40    lemire::compute_float,
41    parse::{parse_inf_nan, parse_partial_number},
42    slow::parse_long_mantissa,
43};
44
45#[inline]
46pub fn parse<T: Float>(bytes: &[u8]) -> Option<T> {
47    T::parse(bytes)
48}
49
50#[inline]
51pub fn parse_partial<T: Float>(bytes: &[u8]) -> Option<(T, usize)> {
52    T::parse_partial(bytes)
53}
54
55pub trait Float: float::RawFloat {
56    #[inline]
57    fn parse(bytes: &[u8]) -> Option<Self> {
58        match Self::parse_partial(bytes) {
59            Some((v, n)) if n == bytes.len() => Some(v),
60            _ => None,
61        }
62    }
63
64    #[inline]
65    fn parse_partial(bytes: &[u8]) -> Option<(Self, usize)> {
66        dec2flt(bytes)
67    }
68}
69
70impl Float for f32 {}
71impl Float for f64 {}
72
73/// Converts a `BiasedFp` to the closest machine float type.
74fn biased_fp_to_float<T: RawFloat>(x: BiasedFp) -> T {
75    let mut word = x.f;
76    word |= (x.e as u64) << T::MANTISSA_EXPLICIT_BITS;
77    T::from_u64_bits(word)
78}
79
80/// Converts a decimal string into a floating point number.
81#[inline]
82pub(crate) fn dec2flt<F: RawFloat>(mut s: &[u8]) -> Option<(F, usize)> {
83    let start = s;
84    let c = if let Some(&c) = s.first() {
85        c
86    } else {
87        return None;
88    };
89    let negative = c == b'-';
90    if negative || c == b'+' {
91        s = &s[1..];
92        if s.is_empty() {
93            return None;
94        }
95    }
96
97    let (mut num, len) = match parse_partial_number(s, start) {
98        Some(r) => r,
99        None => match parse_inf_nan(s, negative) {
100            Some((value, len)) => return Some((value, len + s.offset_from(start) as usize)),
101            None => return None,
102        },
103    };
104    num.negative = negative;
105    if let Some(value) = num.try_fast_path::<F>() {
106        return Some((value, len));
107    }
108
109    // If significant digits were truncated, then we can have rounding error
110    // only if `mantissa + 1` produces a different result. We also avoid
111    // redundantly using the Eisel-Lemire algorithm if it was unable to
112    // correctly round on the first pass.
113    let mut fp = compute_float::<F>(num.exponent, num.mantissa);
114    if num.many_digits && fp.e >= 0 && fp != compute_float::<F>(num.exponent, num.mantissa + 1) {
115        fp.e = -1;
116    }
117    // Unable to correctly round the float using the Eisel-Lemire algorithm.
118    // Fallback to a slower, but always correct algorithm.
119    if fp.e < 0 {
120        fp = parse_long_mantissa::<F>(s);
121    }
122
123    let mut float = biased_fp_to_float::<F>(fp);
124    if num.negative {
125        float = -float;
126    }
127    Some((float, len))
128}
129
130#[cfg(test)]
131#[path = "../tests/float.rs"]
132mod tests;