lexical_write_float/
shared.rs

1//! Shared utilities for writing floats.
2
3use lexical_util::digit::{char_to_valid_digit_const, digit_to_char_const};
4use lexical_util::format::NumberFormat;
5use lexical_write_integer::write::WriteInteger;
6
7use crate::options::{Options, RoundMode};
8
9/// Get the exact number of digits from a minimum bound.
10#[inline(always)]
11pub fn min_exact_digits(digit_count: usize, options: &Options) -> usize {
12    let mut exact_count: usize = digit_count;
13    if let Some(min_digits) = options.min_significant_digits() {
14        exact_count = min_digits.get().max(exact_count);
15    }
16    exact_count
17}
18
19/// Round-up the last digit, from a buffer of digits.
20///
21/// Round up the last digit, incrementally handling all subsequent
22/// digits in case of overflow.
23#[cfg_attr(not(feature = "compact"), inline(always))]
24pub fn round_up(digits: &mut [u8], count: usize, radix: u32) -> (usize, bool) {
25    debug_assert!(count <= digits.len(), "rounding up requires digits.len() >= count");
26
27    let mut index = count;
28    let max_char = digit_to_char_const(radix - 1, radix);
29    while index != 0 {
30        let c = digits[index - 1];
31        if c < max_char {
32            let digit = char_to_valid_digit_const(c, radix);
33            let rounded = digit_to_char_const(digit + 1, radix);
34            // Won't panic since `index > 0 && index <= digits.len()`.
35            digits[index - 1] = rounded;
36            return (index, false);
37        }
38        // Don't have to assign `b'0'` otherwise, since we're just carrying
39        // to the next digit.
40        index -= 1;
41    }
42
43    // Means all digits were max digit: we need to round up.
44    digits[0] = b'1';
45
46    (1, true)
47}
48
49/// Round the number of digits based on the maximum digits, for decimal digits.
50///
51/// `digits` is a mutable buffer of the current digits, `digit_count` is the
52/// length of the written digits in `digits`, and `exp` is the decimal exponent
53/// relative to the digits. Returns the digit count, resulting exp, and if
54/// the input carried to the next digit.
55#[cfg_attr(not(feature = "compact"), inline(always))]
56#[allow(clippy::comparison_chain)] // reason="conditions are different logical concepts"
57pub fn truncate_and_round_decimal(
58    digits: &mut [u8],
59    digit_count: usize,
60    options: &Options,
61) -> (usize, bool) {
62    debug_assert!(digit_count <= digits.len());
63
64    let max_digits = if let Some(digits) = options.max_significant_digits() {
65        digits.get()
66    } else {
67        return (digit_count, false);
68    };
69    if max_digits >= digit_count {
70        return (digit_count, false);
71    }
72
73    // Check if we're truncating, if so, shorten the digits in the input.
74    if options.round_mode() == RoundMode::Truncate {
75        // Don't round input, just shorten number of digits emitted.
76        return (max_digits, false);
77    }
78
79    // We need to round-nearest, tie-even, so we need to handle
80    // the truncation **here**. If the representation is above
81    // halfway at all, we need to round up, even if 1 digit.
82
83    // Get the last non-truncated digit, and the remaining ones.
84    // Won't panic if `digit_count < digits.len()`, since `max_digits <
85    // digit_count`.
86    let truncated = digits[max_digits];
87    let (digits, carried) = if truncated < b'5' {
88        // Just truncate, going to round-down anyway.
89        (max_digits, false)
90    } else if truncated > b'5' {
91        // Round-up always.
92        round_up(digits, max_digits, 10)
93    } else {
94        // Have a near-halfway case, resolve it.
95        let to_round = &digits[max_digits - 1..digit_count];
96        let is_odd = to_round[0] % 2 == 1;
97        let is_above = to_round[2..].iter().any(|&x| x != b'0');
98        if is_odd || is_above {
99            // Won't panic `digit_count <= digits.len()`, because `max_digits <
100            // digit_count`.
101            round_up(digits, max_digits, 10)
102        } else {
103            (max_digits, false)
104        }
105    };
106
107    (digits, carried)
108}
109
110/// Write the sign for the exponent.
111#[cfg_attr(not(feature = "compact"), inline(always))]
112pub fn write_exponent_sign<const FORMAT: u128>(
113    bytes: &mut [u8],
114    cursor: &mut usize,
115    exp: i32,
116) -> u32 {
117    let format = NumberFormat::<{ FORMAT }> {};
118    if exp < 0 {
119        bytes[*cursor] = b'-';
120        *cursor += 1;
121        exp.wrapping_neg() as u32
122    } else if cfg!(feature = "format") && format.required_exponent_sign() {
123        bytes[*cursor] = b'+';
124        *cursor += 1;
125        exp as u32
126    } else {
127        exp as u32
128    }
129}
130
131/// Write the symbol, sign, and digits for the exponent.
132#[cfg_attr(not(feature = "compact"), inline(always))]
133pub fn write_exponent<const FORMAT: u128>(
134    bytes: &mut [u8],
135    cursor: &mut usize,
136    exp: i32,
137    exponent_character: u8,
138) {
139    bytes[*cursor] = exponent_character;
140    *cursor += 1;
141    let positive_exp: u32 = write_exponent_sign::<FORMAT>(bytes, cursor, exp);
142    *cursor += positive_exp.write_exponent_signed::<FORMAT>(&mut bytes[*cursor..]);
143}
144
145/// Detect the notation to use for the float formatter and call the appropriate
146/// function.
147///
148/// The float must be positive. This doesn't affect the safety guarantees but
149/// all algorithms assume a float >0 or that is not negative 0.
150///
151/// - `float` - The float to write to string.
152/// - `format` - The formatting specification for the float.
153/// - `sci_exp` - The scientific exponents describing the float.
154/// - `options` - Options configuring how to serialize the float.
155/// - `write_scientific` - The callback to write scientific notation numbers.
156/// - `write_positive` - The callback to write non-scientific, positive numbers.
157/// - `write_negative` - The callback to write non-scientific, negative numbers.
158/// - `bytes` - The output buffer to write to.
159/// - `args` - Additional arguments to pass to our internal writers.
160macro_rules! write_float {
161    (
162        $float:ident,
163        $format:ident,
164        $sci_exp:ident,
165        $options:ident,
166        $write_scientific:ident,
167        $write_positive:ident,
168        $write_negative:ident,
169        $(generic => $generic:tt,)?
170        bytes => $bytes:ident,
171        args => $($args:expr,)*
172    ) => {{
173        use lexical_util::format::NumberFormat;
174
175        debug_assert!($float.is_sign_positive());
176
177        let format = NumberFormat::<{ $format }> {};
178        let min_exp = $options.negative_exponent_break().map_or(-5, |x| x.get());
179        let max_exp = $options.positive_exponent_break().map_or(9, |x| x.get());
180
181        let outside_break = $sci_exp < min_exp || $sci_exp > max_exp;
182        let require_exponent = format.required_exponent_notation() || outside_break;
183        if !format.no_exponent_notation() && require_exponent {
184            // Write digits in scientific notation.
185            $write_scientific::<$($generic,)? FORMAT>($bytes, $($args,)*)
186        } else if $sci_exp < 0 {
187            // Write negative exponent without scientific notation.
188            $write_negative::<$($generic,)? FORMAT>($bytes, $($args,)*)
189        } else {
190            // Write positive exponent without scientific notation.
191            $write_positive::<$($generic,)? FORMAT>($bytes, $($args,)*)
192        }
193    }};
194}