f256/conv/to_str/
dec_repr.rs

1// ---------------------------------------------------------------------------
2// Copyright:   (c) 2022 ff. Michael Amrhein (michael@adrhinum.de)
3// License:     This program is part of a larger application. For license
4//              details please read the file LICENSE.TXT provided together
5//              with the application.
6// ---------------------------------------------------------------------------
7// $Source:     src/conv/to_str/dec_repr.rs $
8// $Revision:   2025-04-20T09:31:28+02:00 $
9
10use alloc::string::ToString;
11use core::{cmp::max, fmt, mem::MaybeUninit};
12
13use super::{
14    common::floor_log10_pow2,
15    formatted::{Formatted, Part},
16    powers_of_five::is_multiple_of_pow5,
17};
18use crate::{
19    big_uint::{HiLo, U128},
20    f256, BigUInt, DivRem, U256, U512,
21};
22
23/// Returns ⌊log₁₀(5ⁱ)⌋ for 0 <= i <= 262380.
24#[inline(always)]
25#[allow(clippy::cast_sign_loss)]
26#[allow(clippy::cast_possible_truncation)]
27fn floor_log10_pow5(i: i32) -> i32 {
28    debug_assert!(i >= 0);
29    debug_assert!(i <= 262380);
30    ((i as u128 * 24592820711491) >> 45) as i32
31}
32
33/// Returns ⌊log₂(5ⁱ)⌋ for 0 <= i <= 225798.
34#[inline(always)]
35#[allow(clippy::cast_sign_loss)]
36fn floor_log2_pow5(i: i32) -> i32 {
37    debug_assert!(i >= 0);
38    debug_assert!(i <= 225798);
39    ((i as u64 * 81695582054029) >> 45) as i32
40}
41
42/// Returns ⌈log₂(5ⁱ)⌉ for 0 <= i <= 225798.
43#[inline(always)]
44fn ceil_log2_pow5(i: i32) -> i32 {
45    floor_log2_pow5(i) + 1
46}
47
48#[inline(always)]
49fn lookup_pow2_div_pow5(idx: usize) -> U512 {
50    let t = f256_pow2_div_pow5_lut::lookup_pow2_div_pow5(idx);
51    U512 {
52        hi: U256::new(t.0, t.1),
53        lo: U256::new(t.2, t.3),
54    }
55}
56
57#[inline(always)]
58fn lookup_pow5_div_pow2(idx: usize) -> U512 {
59    let t = f256_pow5_div_pow2_lut::lookup_pow5_div_pow2(idx);
60    U512 {
61        hi: U256::new(t.0, t.1),
62        lo: U256::new(t.2, t.3),
63    }
64}
65
66// Calculate ⌊(x * y) / 2⁵¹²⌋.
67pub(crate) fn u256_truncating_mul_u512(x: &U256, y: &U512) -> U256 {
68    let mut carry = U128::ZERO;
69    let mut l = (U128::ZERO, U128::ZERO, U128::ZERO, U128::ZERO);
70    let mut h = (U128::ZERO, U128::ZERO, U128::ZERO, U128::ZERO);
71    (_, carry) = y.lo.lo.widening_mul(&x.lo);
72    (l.0, carry) = y.lo.hi.carrying_mul(&x.lo, &carry);
73    (l.1, carry) = y.hi.lo.carrying_mul(&x.lo, &carry);
74    (l.2, carry) = y.hi.hi.carrying_mul(&x.lo, &carry);
75    l.3 = carry;
76    (h.0, carry) = y.lo.lo.widening_mul(&x.hi);
77    (h.1, carry) = y.lo.hi.carrying_mul(&x.hi, &carry);
78    (h.2, carry) = y.hi.lo.carrying_mul(&x.hi, &carry);
79    (h.3, carry) = y.hi.hi.carrying_mul(&x.hi, &carry);
80    let mut hi = carry;
81    let (_, carry) = l.0.overflowing_add(&h.0);
82    let (_, carry) = l.1.carrying_add(&h.1, carry);
83    let (_, carry) = l.2.carrying_add(&h.2, carry);
84    let (lo, carry) = l.3.carrying_add(&h.3, carry);
85    hi.incr_if(carry);
86    U256::from_hi_lo(hi, lo)
87}
88
89/// Internal representation of a finite decimal number d as (s, k, w)
90/// where s ∈ {0, 1}, |k| < 2³¹, 0 <= w < 2²⁵⁶ and d = (-1)ˢ × w × 10ᵏ.
91#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
92pub(crate) struct DecNumRepr {
93    pub(crate) sign: u32,
94    pub(crate) exp10: i32,
95    pub(crate) signif10: U256,
96}
97
98impl DecNumRepr {
99    /// Converts a finite, non-zero `f256` value into its shortest, correctly
100    /// rounded decimal representation.
101    pub(crate) fn shortest_from_f256(f: &f256) -> Self {
102        debug_assert!(f.is_finite() && !f.eq_zero());
103
104        // Step 1: Decode the binary floating-point number.
105        let sign = f.sign();
106        let exp2 = f.quantum_exponent();
107        let signif2 = f.integral_significand();
108
109        // Compute the decimal significand and exponent.
110        let (signif10, exp10) = Self::shortest_from_bin_repr(signif2, exp2);
111
112        Self {
113            sign,
114            exp10,
115            signif10,
116        }
117    }
118
119    /// Converts a finite, non-zero `f256` value into its shortest, correctly
120    /// rounded decimal representation.
121    #[allow(clippy::cast_sign_loss)]
122    #[allow(clippy::cast_possible_wrap)]
123    #[allow(clippy::cognitive_complexity)]
124    pub(crate) fn shortest_from_bin_repr(
125        mut signif2: U256,
126        mut exp2: i32,
127    ) -> (U256, i32) {
128        // This is an implementation of the algorithm presented by Ulf Adams
129        // in his PLDI'18 paper `Ryū: Fast Float-to-String
130        // Conversion`, available at [https://dl.acm.org/doi/pdf/10.1145/3296979.3192369], adapted to
131        // f256.
132
133        // Max number of bits needed to store ⌊2ʰ / 5ᵍ⌋ + 1 or ⌊5⁻ᵉ⁻ᵍ / 2ʰ⌋.
134        const H: i32 = 501;
135
136        let accept_bounds = signif2.is_even();
137
138        // Subtract 2 from exponent and adjust significand in prep of step 2.
139        exp2 -= 2;
140        signif2 <<= 2;
141
142        // Step 2: Compute the halfway points to the next smaller and larger
143        // floating point values.
144        let is_non_integer = exp2 < -(signif2.trailing_zeros() as i32);
145        let lower_signif2 = signif2 - U256::from(1 + is_non_integer as u128);
146        let upper_signif2 = signif2 + U256::TWO;
147
148        // Step 3: Convert the interval to a decimal power base.
149        let mut exp10: i32;
150        let mut signif10 = U256::default();
151        let mut lower_signif10 = U256::default();
152        let mut upper_signif10 = U256::default();
153        let mut rem_zero = false;
154        let mut lower_rem_zero = false;
155        if exp2 >= 0 {
156            // g = max(0, ⌊e₂ × log₁₀(2)⌋ - 1)
157            // ⌊e₂ × log₁₀(2)⌋ = 0 for e₂ ∈ [0..3], therefor the following
158            // expression is equivalent to the one above.
159            let g = floor_log10_pow2(exp2) - (exp2 > 3) as i32;
160            exp10 = g;
161            // Instead of calculating (signif * 2ᵉ⁻ᵍ⁻ʰ * (⌊2ʰ / 5ᵍ⌋ + 1)
162            // we calulate (signif * 2ᵉ⁻ᵍ⁻ʰ⁺⁵¹⁰ * (⌊2ʰ / 5ᵍ⌋ + 1) * 4 / 2⁵¹².
163            let h = floor_log2_pow5(g) + H;
164            let sh = (exp2 - g - h + 510) as u32;
165            let luv = lookup_pow2_div_pow5(g as usize);
166            lower_signif10 =
167                u256_truncating_mul_u512(&(lower_signif2 << sh), &luv);
168            signif10 = u256_truncating_mul_u512(&(signif2 << sh), &luv);
169            upper_signif10 =
170                u256_truncating_mul_u512(&(upper_signif2 << sh), &luv);
171            // exp2 >= 0 => rem_zero = signif10 % 10ᵍ == 0 = signif2 % 5ᵍ == 0
172            // Analog for the lower and upper bound.
173            // exp2 >= 0 => g >= 0
174            // signif2 and its lower and upper bounds have atmost 239 bits.
175            // g > 102 => 5ᵍ > 2²³⁹ => signif2 and its bounds can't be a
176            // multiple of 5ᵍ.
177            if g <= 102 {
178                // Only one of lower_signif10, signif10, upper_signif10 can be
179                // a multiple of 5ᵍ, if any.
180                if (signif2 % U128::from(5_u128)).is_zero() {
181                    rem_zero = is_multiple_of_pow5(&signif2, g as u32);
182                } else if accept_bounds {
183                    lower_rem_zero =
184                        is_multiple_of_pow5(&lower_signif2, g as u32);
185                } else if is_multiple_of_pow5(&upper_signif2, g as u32) {
186                    upper_signif10.decr();
187                }
188            }
189        } else {
190            // e₂ < 0
191            // g = max(0, ⌊-e₂ × log₁₀(5)⌋ - 1)
192            // ⌊-e₂ × log₁₀(5)⌋ = 0 for e₂ = -1, therefor the following
193            // expression is equivalent to the one above.
194            let g = floor_log10_pow5(-exp2) - (exp2 != -1) as i32;
195            exp10 = exp2 + g;
196            let i = -exp2 - g;
197            // Instead of calculating signif * ⌊5⁻ᵉ⁻ᵍ / 2ʰ⌋ / 2ᵍ⁻ʰ we
198            // calculate signif * ⌊5⁻ᵉ⁻ᵍ / 2ʰ⁻²⌋ * 2⁵¹⁰⁻ᵍ⁺ʰ /
199            // 2⁵¹².
200            let h = ceil_log2_pow5(i) - H;
201            let sh = (510 - g + h) as u32;
202            let luv = lookup_pow5_div_pow2(i as usize);
203            lower_signif10 =
204                u256_truncating_mul_u512(&(lower_signif2 << sh), &luv);
205            signif10 = u256_truncating_mul_u512(&(signif2 << sh), &luv);
206            upper_signif10 =
207                u256_truncating_mul_u512(&(upper_signif2 << sh), &luv);
208            // exp2 < 0 => rem_zero = signif10 % 10ᵍ == 0 = signif2 % 2ᵍ == 0
209            // Analog for the lower and upper bound.
210            if g <= 1 {
211                // signif2 = 4 * f.significand, so it has atleast 2 trailing
212                // zero bits.
213                rem_zero = true;
214                if accept_bounds {
215                    // lower_signif2 = signif2 - 1 - is_non_integer, so it has
216                    // a trailing zero bit if f is not an integer.
217                    lower_rem_zero = is_non_integer;
218                } else {
219                    // uppper_signif2 = signif2 + 2, so it always has at least
220                    // one trailing zero bit.
221                    signif10.decr();
222                }
223            } else if g <= 238 {
224                // signif2 has atmost 239 bits, i.e atmost 238 trailing
225                // zeroes.
226                rem_zero = signif2.trailing_zeros() >= g as u32;
227            }
228        }
229
230        // Step 4: Find the shortest, correctly-rounded representation within
231        // this interval.
232        let mut i = 0_i32;
233        let mut round_digit = 0_u64;
234        if lower_rem_zero || rem_zero {
235            let (mut lower_quot, mut lower_rem) =
236                lower_signif10.div_rem(10_u64);
237            let (mut upper_quot, mut upper_rem) =
238                upper_signif10.div_rem(10_u64);
239            while lower_quot < upper_quot {
240                (signif10, round_digit) = signif10.div_rem(10_u64);
241                rem_zero &= round_digit == 0;
242                lower_signif10 = lower_quot;
243                lower_rem_zero &= lower_rem == 0;
244                upper_signif10 = upper_quot;
245                i += 1;
246                (lower_quot, lower_rem) = lower_signif10.div_rem(10_u64);
247                (upper_quot, upper_rem) = upper_signif10.div_rem(10_u64);
248            }
249            if lower_rem_zero {
250                while lower_rem == 0 && !lower_signif10.is_zero() {
251                    (signif10, round_digit) = signif10.div_rem(10_u64);
252                    rem_zero &= round_digit == 0;
253                    lower_signif10 = lower_quot;
254                    (lower_quot, lower_rem) = lower_signif10.div_rem(10_u64);
255                    i += 1;
256                }
257            }
258            if round_digit > 5  // need to round up
259                || (round_digit == 5    // need to round to even
260                && (!rem_zero || signif10.lo.is_odd()))
261            {
262                signif10.incr();
263            }
264        } else {
265            // Can't have a tie.
266            let mut round_up = false;
267            // First, try to remove two digits.
268            let (mut lower_quot, mut lower_rem) =
269                lower_signif10.div_rem(100_u64);
270            let (mut upper_quot, mut upper_rem) =
271                upper_signif10.div_rem(100_u64);
272            if upper_quot > lower_quot {
273                let (quot, rem) = signif10.div_rem(100_u64);
274                round_up = rem >= 50;
275                signif10 = quot;
276                lower_signif10 = lower_quot;
277                upper_signif10 = upper_quot;
278                i += 2;
279            }
280            (lower_quot, lower_rem) = lower_signif10.div_rem(10_u64);
281            (upper_quot, upper_rem) = upper_signif10.div_rem(10_u64);
282            while upper_quot > lower_quot {
283                (signif10, round_digit) = signif10.div_rem(10_u64);
284                round_up = round_digit >= 5;
285                lower_signif10 = lower_quot;
286                upper_signif10 = upper_quot;
287                (lower_quot, lower_rem) = lower_signif10.div_rem(10_u64);
288                (upper_quot, upper_rem) = upper_signif10.div_rem(10_u64);
289                i += 1;
290            }
291            if round_up || signif10 == lower_signif10 {
292                signif10.incr();
293            }
294        }
295        // Adjust exponent by adding number of removed digits
296        exp10 += i;
297
298        (signif10, exp10)
299    }
300
301    #[allow(unsafe_code, trivial_casts)]
302    #[allow(clippy::cast_possible_truncation)]
303    #[allow(clippy::cast_possible_wrap)]
304    pub(crate) fn fmt_scientific(
305        self,
306        exp_mark: char,
307        form: &mut fmt::Formatter<'_>,
308    ) -> fmt::Result {
309        let mut parts: [MaybeUninit<Part<'_>>; 5] =
310            // SAFETY: only parts initialized by MaybeUninit::new are used
311            // later.
312            unsafe { MaybeUninit::uninit().assume_init() };
313        let mut n_parts = 0_usize;
314        let zero_padding: &mut Part;
315        let signif = self.signif10.to_string();
316        let digits = signif.as_str();
317        let n_digits = digits.len();
318        let exp10 = self.exp10 + n_digits as i32 - 1;
319        let exp = exp10.to_string();
320        parts[n_parts] = MaybeUninit::new(Part::Digits(&digits[..1]));
321        n_parts += 1;
322        if n_digits > 1 {
323            parts[n_parts] = MaybeUninit::new(Part::Char('.'));
324            n_parts += 1;
325            parts[n_parts] = MaybeUninit::new(Part::Digits(&digits[1..]));
326            n_parts += 1;
327        }
328        parts[n_parts] = MaybeUninit::new(Part::Char(exp_mark));
329        n_parts += 1;
330        parts[n_parts] = MaybeUninit::new(Part::Digits(&exp));
331        n_parts += 1;
332        let formatted = Formatted {
333            // SAFETY: n_parts elements are initialized.
334            parts: unsafe {
335                &*(&parts[..n_parts] as *const [MaybeUninit<Part<'_>>]
336                    as *const [Part<'_>])
337            },
338        };
339        formatted.pad_parts(self.sign == 1, form)
340    }
341}
342
343impl fmt::Display for DecNumRepr {
344    #[allow(unsafe_code, trivial_casts)]
345    #[allow(clippy::cast_sign_loss)]
346    #[allow(clippy::cast_possible_truncation)]
347    #[allow(clippy::cast_possible_wrap)]
348    fn fmt(&self, form: &mut fmt::Formatter<'_>) -> fmt::Result {
349        let mut parts: [MaybeUninit<Part<'_>>; 4] =
350            // SAFETY: only parts initialized by MaybeUninit::new are used
351            // later.
352            unsafe { MaybeUninit::uninit().assume_init() };
353        let mut n_parts = 0_usize;
354        let zero_padding: &mut Part;
355        let signif = self.signif10.to_string();
356        let digits = signif.as_str();
357        let n_digits = digits.len();
358        let n_int_digits = max(0, (n_digits as i32 + self.exp10)) as usize;
359        let n_frac_digits = max(0, -self.exp10) as usize;
360        // Integer part.
361        if n_int_digits > n_digits {
362            // Add a part for all digits and a part for trailing integer
363            // zeroes.
364            parts[n_parts] = MaybeUninit::new(Part::Digits(digits));
365            n_parts += 1;
366            parts[n_parts] =
367                MaybeUninit::new(Part::Zeroes(n_int_digits - n_digits));
368            n_parts += 1;
369        } else if n_int_digits > 0 {
370            parts[n_parts] =
371                MaybeUninit::new(Part::Digits(&digits[..n_int_digits]));
372            n_parts += 1;
373        } else {
374            parts[n_parts] = MaybeUninit::new(Part::Char('0'));
375            n_parts += 1;
376        }
377        // Fractional part.
378        if n_frac_digits > 0 {
379            parts[n_parts] = MaybeUninit::new(Part::Char('.'));
380            n_parts += 1;
381            if n_frac_digits > n_digits {
382                // Add a part for leading fractional zeroes and a part for all
383                // digits.
384                parts[n_parts] =
385                    MaybeUninit::new(Part::Zeroes(n_frac_digits - n_digits));
386                n_parts += 1;
387                parts[n_parts] = MaybeUninit::new(Part::Digits(digits));
388                n_parts += 1;
389            } else {
390                parts[n_parts] =
391                    MaybeUninit::new(Part::Digits(&digits[n_int_digits..]));
392                n_parts += 1;
393            }
394        }
395        let formatted = Formatted {
396            // SAFETY: n_parts elements are initialized.
397            parts: unsafe {
398                &*(&parts[..n_parts] as *const [MaybeUninit<Part<'_>>]
399                    as *const [Part<'_>])
400            },
401        };
402        formatted.pad_parts(self.sign == 1, form)
403    }
404}
405
406#[cfg(test)]
407#[allow(clippy::decimal_literal_representation)]
408mod tests {
409    use alloc::borrow::ToOwned;
410    use core::str::FromStr;
411
412    use super::*;
413
414    #[test]
415    fn test_one() {
416        let f = f256::ONE;
417        let r = DecNumRepr::shortest_from_f256(&f);
418        assert_eq!(
419            r,
420            DecNumRepr {
421                sign: 0,
422                exp10: 0,
423                signif10: U256::new(0, 1)
424            }
425        );
426    }
427
428    #[test]
429    fn test_ten_pow_72() {
430        let f = f256::from_str("10.0e72").unwrap();
431        let r = DecNumRepr::shortest_from_f256(&f);
432        assert_eq!(
433            r,
434            DecNumRepr {
435                sign: 0,
436                exp10: 73,
437                signif10: U256::new(0, 1)
438            }
439        );
440    }
441
442    #[test]
443    fn test_2_pow_251() {
444        let i = U256::new(1_u128 << 123, 0);
445        let f = f256::encode(0, 0, i);
446        let r = DecNumRepr::shortest_from_f256(&f);
447        assert_eq!(
448            r,
449            DecNumRepr {
450                sign: 0,
451                exp10: 5,
452                signif10: U256::new(
453                    106338239662793269832304564822427,
454                    192627042266604845397347097774975349141
455                )
456            }
457        );
458    }
459
460    #[test]
461    fn test_7e28() {
462        let f = f256::from_str("7e28").unwrap();
463        let r = DecNumRepr::shortest_from_f256(&f);
464        assert_eq!(
465            r,
466            DecNumRepr {
467                sign: 0,
468                exp10: 28,
469                signif10: U256::new(0, 7)
470            }
471        );
472    }
473
474    #[test]
475    fn test_one_half() {
476        let f = f256::encode(0, -1, U256::new(0, 1));
477        let r = DecNumRepr::shortest_from_f256(&f);
478        assert_eq!(
479            r,
480            DecNumRepr {
481                sign: 0,
482                exp10: -1,
483                signif10: U256::new(0, 5)
484            }
485        );
486    }
487
488    #[test]
489    fn test_one_sixteenth() {
490        let f = f256::encode(0, -4, U256::new(0, 1));
491        let r = DecNumRepr::shortest_from_f256(&f);
492        assert_eq!(
493            r,
494            DecNumRepr {
495                sign: 0,
496                exp10: -4,
497                signif10: U256::new(0, 625)
498            }
499        );
500    }
501
502    #[test]
503    fn test_2_pow_237_minus_1() {
504        let i = U256::new(
505            649037107316853453566312041152511,
506            340282366920938463463374607431768211455,
507        );
508        let f = f256::encode(0, 0, i);
509        let r = DecNumRepr::shortest_from_f256(&f);
510        assert_eq!(
511            r,
512            DecNumRepr {
513                sign: 0,
514                exp10: 0,
515                signif10: i,
516            }
517        );
518    }
519
520    #[test]
521    fn test_f256_max() {
522        let f = f256::MAX;
523        let r = DecNumRepr::shortest_from_f256(&f);
524        assert_eq!(
525            r,
526            DecNumRepr {
527                sign: 0,
528                exp10: 78842,
529                signif10: U256::new(
530                    473526069559795162737608364600986,
531                    168794288209602616731974382256735511567
532                )
533            }
534        );
535    }
536}