lexical_write_float/
hex.rs

1//! Optimized float serializer for hexadecimal floats.
2//!
3//! This actually works for any case where we can exactly represent
4//! any power of the mantissa radix using the exponent base. For example,
5//! given a mantissa radix of `16`, and an exponent base of `8`,
6//! `16^2` cannot be exactly represented in octal. In short:
7//! `⌊log2(r) / log2(b)⌋ == ⌈log2(r) / log2(b)⌉`.
8//!
9//! This gives us the following mantissa radix/exponent base combinations:
10//!
11//! - 4, 2
12//! - 8, 2
13//! - 16, 2
14//! - 32, 2
15//! - 16, 4
16
17#![cfg(feature = "power-of-two")]
18#![doc(hidden)]
19
20use lexical_util::algorithm::rtrim_char_count;
21use lexical_util::constants::{FormattedSize, BUFFER_SIZE};
22use lexical_util::format::NumberFormat;
23use lexical_util::num::{Float, Integer};
24use lexical_write_integer::write::WriteInteger;
25
26use crate::binary::{
27    calculate_shl,
28    fast_ceildiv,
29    fast_log2,
30    truncate_and_round,
31    write_float_negative_exponent,
32    write_float_positive_exponent,
33};
34use crate::options::Options;
35use crate::shared;
36
37/// Optimized float-to-string algorithm for hexadecimal strings.
38///
39/// This assumes the float is:
40///     1). Non-special (NaN or Infinite).
41///     2). Non-negative.
42///
43/// # Safety
44///
45/// Safe as long as the float isn't special (NaN or Infinity), and `bytes`
46/// is large enough to hold the significant digits.
47///
48/// # Panics
49///
50/// Panics if the radix for the significant digits is not 16, if
51/// the exponent base is not 2, or if the radix for the exponent
52/// digits is not 10.
53pub fn write_float<F: Float, const FORMAT: u128>(
54    float: F,
55    bytes: &mut [u8],
56    options: &Options,
57) -> usize
58where
59    <F as Float>::Unsigned: WriteInteger + FormattedSize,
60{
61    // PRECONDITIONS
62
63    // Assert no special cases remain, no negative numbers,
64    // and a valid format.
65    let format = NumberFormat::<{ FORMAT }> {};
66    assert!(format.is_valid());
67    debug_assert!(!float.is_special());
68    debug_assert!(float >= F::ZERO);
69    debug_assert!(matches!(
70        (format.radix(), format.exponent_base()),
71        (4, 2) | (8, 2) | (16, 2) | (32, 2) | (16, 4)
72    ));
73
74    // Quickly calculate the number of bits we would have written.
75    // This simulates writing the digits, so we can calculate the
76    // scientific exponent. Since this number is often constant
77    // (except for denormal values), and doesn't describe
78    // the actual shift or digits we use...
79    //
80    // Note:
81    //      Except for denormal floats, this will always
82    //      be `F::MANTISSA_SIZE`, unless we have special
83    //      formatting write control.
84    let mantissa = float.mantissa();
85    let radix = format.mantissa_radix();
86    let (mantissa, mantissa_bits) = truncate_and_round(mantissa, radix, options);
87
88    // See if we should use an exponent if the number was represented
89    // in scientific notation, AKA, `I.FFFF^EEE`. If the exponent is above
90    // a certain value, then use scientific notation. We therefore have
91    // to adjust the exponent by the number of mantissa bits, and shift
92    // by 1 (since a scientific exponent of 0 should have 1 digit ahead).
93    // This is a binary exp, so we need to how large our
94    // adjusted exp to the radix is.
95    //
96    // The scientific exponent is always this way: it's the float exponent
97    // (binary) + mantissa bits (binary) - 1 (for the first bit, binary),
98    // since 1.0 is scientific exponent 0. We need to check the scientific
99    // exponent relative to the number of leading or trailing 0s
100    // it would introduce, that is, scaled to bits/digit. The min exp must
101    // be less than, and the max must be above 0.
102    let exp = float.exponent();
103    let mut sci_exp = exp + mantissa_bits as i32 - 1;
104
105    // Normalize the exponent if we have an actual zero.
106    if mantissa == <F as Float>::Unsigned::ZERO {
107        sci_exp = 0;
108    }
109
110    // SAFETY: Safe, just other API methods need to be migrated in.
111    write_float!(
112        float,
113        FORMAT,
114        sci_exp,
115        options,
116        write_float_scientific,
117        write_float_positive_exponent,
118        write_float_negative_exponent,
119        generic => _,
120        bytes => bytes,
121        args => mantissa, exp, sci_exp, options,
122    )
123}
124
125/// Write float to string in scientific notation.
126///
127/// # Safety
128///
129/// Safe as long as `bytes` is large enough to hold the number of digits
130/// and the scientific notation's exponent digits.
131///
132/// # Preconditions
133///
134/// The mantissa must be truncated and rounded, prior to calling this,
135/// based on the number of maximum digits.
136#[cfg_attr(not(feature = "compact"), inline(always))]
137pub fn write_float_scientific<M, const FORMAT: u128>(
138    bytes: &mut [u8],
139    mantissa: M,
140    exp: i32,
141    sci_exp: i32,
142    options: &Options,
143) -> usize
144where
145    M: WriteInteger + FormattedSize,
146{
147    // Just decent size bounds checks to ensure we have a lot of space.
148    assert!(M::FORMATTED_SIZE < BUFFER_SIZE - 2);
149    debug_assert!(bytes.len() >= BUFFER_SIZE);
150
151    // Config options
152    let format = NumberFormat::<{ FORMAT }> {};
153    let bits_per_digit = fast_log2(format.mantissa_radix());
154    let bits_per_base = fast_log2(format.exponent_base());
155    let decimal_point = options.decimal_point();
156
157    // Write our value, then trim trailing zeros, before we check the exact
158    // bounds of the digits, to avoid accidentally choosing too many digits.
159    // shl is the powers of two we have missing from our exponent that nee
160    // to be transferred to our significant digits. Since the all mantissa
161    // radix powers can be **exactly** represented by exponent bases,
162    // we can just shift this into the mantissa.
163    let shl = calculate_shl(exp, bits_per_digit);
164    let value = mantissa << shl;
165
166    let count = value.write_mantissa::<FORMAT>(&mut bytes[1..]);
167    bytes[0] = bytes[1];
168    bytes[1] = decimal_point;
169    let zeros = rtrim_char_count(&bytes[2..count + 1], b'0');
170    let digit_count = count - zeros;
171    // Extra 1 since we have the decimal point.
172    let mut cursor = digit_count + 1;
173
174    // Determine if we need to add more trailing zeros.
175    let exact_count = shared::min_exact_digits(digit_count, options);
176
177    // Write any trailing digits to the output.
178    // Won't panic safe if the buffer is large enough to hold the significant
179    // digits.
180    if !format.no_exponent_without_fraction() && cursor == 2 && options.trim_floats() {
181        // Need to trim floats from trailing zeros, and we have only a decimal.
182        cursor -= 1;
183    } else if exact_count < 2 {
184        // Need to have at least 1 digit, the trailing `.0`.
185        bytes[cursor] = b'0';
186        cursor += 1;
187    } else if exact_count > digit_count {
188        // NOTE: Neither `exact_count >= digit_count >= 2`.
189        // We need to write `exact_count - (cursor - 1)` digits, since
190        // cursor includes the decimal point.
191        let digits_end = exact_count + 1;
192        bytes[cursor..digits_end].fill(b'0');
193        cursor = digits_end;
194    }
195
196    // Now, write our scientific notation.
197    // Won't panic safe if bytes is large enough to store all digits.
198    let scaled_sci_exp = scale_sci_exp(sci_exp, bits_per_digit, bits_per_base);
199    shared::write_exponent::<FORMAT>(bytes, &mut cursor, scaled_sci_exp, options.exponent());
200
201    cursor
202}
203
204// ALGORITHM
205// ---------
206
207/// We need to scale the scientific exponent for writing.
208///
209/// This is similar to [`binary::scale_sci_exp`](crate::binary::scale_sci_exp),
210/// however, we need to effectively have the same algorithm with `bits_per_base`
211/// instead of `bits_per_digit`. However, `bits_per_base` is smaller, and
212/// will not properly floor the values, so we add in an extra step.
213#[inline(always)]
214pub fn scale_sci_exp(sci_exp: i32, bits_per_digit: i32, bits_per_base: i32) -> i32 {
215    if sci_exp < 0 {
216        let neg_sci_exp = sci_exp.wrapping_neg();
217        let floor = fast_ceildiv(neg_sci_exp, bits_per_digit);
218        (floor * bits_per_digit / bits_per_base).wrapping_neg()
219    } else {
220        let floor = sci_exp / bits_per_digit;
221        floor * bits_per_digit / bits_per_base
222    }
223}