lexical_write_float/
shared.rs

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