Skip to main content

rucc_base/
dfp.rs

1//! Decimal floating point, as IEEE 754 lays it out in the binary integer decimal encoding.
2//!
3//! `_Decimal32`, `_Decimal64` and `_Decimal128` are C23 Annex H, and gcc implements them on every
4//! x86-64 and AArch64 target by storing the coefficient as a binary integer, which is the BID
5//! encoding, and calling the `__bid_*` routines in libgcc for the arithmetic. A constant is the
6//! only place the compiler itself has to produce one, so this module is the conversion from the
7//! text of a constant to the bits of the encoding, and back again for anything that wants to
8//! read one, and nothing else. The arithmetic stays in libgcc, where gcc keeps it too.
9//!
10//! A decimal number is a sign, a coefficient of at most [`Width::digits`] decimal digits and an
11//! exponent `q`, with the value `coefficient * 10^q`. Unlike a binary format the same value has
12//! more than one encoding, and which one a constant gets is part of what it means: `1.20dd` is
13//! the coefficient 120 with exponent -2, `1.2dd` is 12 with exponent -1, and the two compare
14//! equal but print differently. The exponent a constant keeps is the one its text wrote, which is
15//! IEEE's preferred exponent and what gcc gives it, and only a coefficient with too many digits
16//! or an exponent out of range changes it.
17//!
18//! Rounding is to nearest with ties to even, the only mode a translation time constant uses.
19//!
20//! ```
21//! use rucc_base::dfp::{self, Width};
22//!
23//! let (bits, status) = dfp::parse("1.20", Width::D64).expect("a number");
24//! assert_eq!(dfp::decode(bits, Width::D64), dfp::Decoded::Finite { sign: false, coefficient: 120, exponent: -2 });
25//! assert!(status.is_none());
26//! ```
27
28use crate::float::{ParseError, Status};
29
30/// One of the three decimal interchange formats.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub enum Width {
33    /// `_Decimal32`, seven digits.
34    D32,
35    /// `_Decimal64`, sixteen digits.
36    D64,
37    /// `_Decimal128`, thirty four digits.
38    D128,
39}
40
41impl Width {
42    /// The size of the encoding in bits.
43    #[must_use]
44    pub const fn bits(self) -> u32 {
45        match self {
46            Width::D32 => 32,
47            Width::D64 => 64,
48            Width::D128 => 128,
49        }
50    }
51
52    /// The most decimal digits a coefficient has, IEEE's `p`.
53    #[must_use]
54    pub const fn digits(self) -> u32 {
55        match self {
56            Width::D32 => 7,
57            Width::D64 => 16,
58            Width::D128 => 34,
59        }
60    }
61
62    /// What is added to the exponent to store it, which makes the smallest exponent zero.
63    #[must_use]
64    pub const fn bias(self) -> i32 {
65        match self {
66            Width::D32 => 101,
67            Width::D64 => 398,
68            Width::D128 => 6176,
69        }
70    }
71
72    /// The smallest exponent `q` a coefficient can have.
73    #[must_use]
74    pub const fn min_exponent(self) -> i32 {
75        -self.bias()
76    }
77
78    /// The largest exponent `q` a coefficient can have, IEEE's `emax - p + 1`.
79    #[must_use]
80    pub const fn max_exponent(self) -> i32 {
81        match self {
82            Width::D32 => 90,
83            Width::D64 => 369,
84            Width::D128 => 6111,
85        }
86    }
87
88    /// The bits the biased exponent is stored in.
89    const fn exponent_bits(self) -> u32 {
90        match self {
91            Width::D32 => 8,
92            Width::D64 => 10,
93            Width::D128 => 14,
94        }
95    }
96
97    /// The bits a coefficient is stored in when its top bits are not `100`, which is every
98    /// coefficient below `2^(this)`.
99    const fn coefficient_bits(self) -> u32 {
100        self.bits() - 1 - self.exponent_bits()
101    }
102
103    /// The largest coefficient, `10^p - 1`.
104    #[must_use]
105    pub const fn max_coefficient(self) -> u128 {
106        pow10(self.digits()) - 1
107    }
108}
109
110/// `10^n`, for the `n` a coefficient of this module ever needs, which is at most thirty eight.
111const fn pow10(n: u32) -> u128 {
112    let mut value = 1u128;
113    let mut i = 0;
114    while i < n {
115        value *= 10;
116        i += 1;
117    }
118    value
119}
120
121/// What a decimal encoding holds.
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum Decoded {
124    /// A number, zero included: `coefficient * 10^exponent`, negative when `sign` is set.
125    Finite {
126        /// The sign, which a zero has as well.
127        sign: bool,
128        /// The coefficient, at most [`Width::max_coefficient`].
129        coefficient: u128,
130        /// The exponent `q`.
131        exponent: i32,
132    },
133    /// An infinity.
134    Infinite {
135        /// Which infinity.
136        sign: bool,
137    },
138    /// A NaN, quiet or signaling.
139    Nan {
140        /// The sign bit, which a NaN has but which means nothing.
141        sign: bool,
142        /// Whether it is the signaling kind.
143        signaling: bool,
144    },
145}
146
147/// The sign bit of an encoding of this width.
148#[must_use]
149pub const fn sign_bit(width: Width) -> u128 {
150    1u128 << (width.bits() - 1)
151}
152
153/// The bits of `sign * coefficient * 10^exponent`, which the caller has already brought into
154/// range: the coefficient at most [`Width::max_coefficient`] and the exponent between
155/// [`Width::min_exponent`] and [`Width::max_exponent`].
156///
157/// # Panics
158///
159/// If either is out of range, which is a caller that skipped the rounding.
160#[must_use]
161pub fn encode(sign: bool, coefficient: u128, exponent: i32, width: Width) -> u128 {
162    assert!(coefficient <= width.max_coefficient(), "the coefficient was not rounded");
163    assert!(
164        (width.min_exponent()..=width.max_exponent()).contains(&exponent),
165        "the exponent was not brought into range"
166    );
167    let biased = u128::try_from(exponent + width.bias()).expect("in range, so not negative");
168    let small = width.coefficient_bits();
169    let sign = if sign { sign_bit(width) } else { 0 };
170    if coefficient < 1u128 << small {
171        sign | (biased << small) | coefficient
172    } else {
173        // The coefficient's top three bits are `100`, which is not stored: the two bits after
174        // the sign are `11`, the exponent follows them, and the rest of the coefficient after
175        // that. Only the two narrower widths get here, since ten to the thirty fourth is below
176        // two to the hundred and thirteenth.
177        let rest = small - 2;
178        sign | (0b11 << (width.bits() - 3))
179            | (biased << rest)
180            | (coefficient & ((1u128 << rest) - 1))
181    }
182}
183
184/// An infinity of this width.
185#[must_use]
186pub const fn infinity(sign: bool, width: Width) -> u128 {
187    let bits = 0b11110u128 << (width.bits() - 6);
188    if sign { bits | sign_bit(width) } else { bits }
189}
190
191/// A quiet NaN of this width with nothing in its payload.
192#[must_use]
193pub const fn nan(width: Width) -> u128 {
194    0b11_1110u128 << (width.bits() - 7)
195}
196
197/// What the bits of an encoding of this width mean.
198///
199/// A coefficient larger than [`Width::max_coefficient`] is a non-canonical encoding, which IEEE
200/// says is read as zero, and it is read that way here.
201#[must_use]
202pub fn decode(bits: u128, width: Width) -> Decoded {
203    let top = width.bits();
204    let sign = bits & sign_bit(width) != 0;
205    let combination = (bits >> (top - 6)) & 0b11111;
206    if combination == 0b11110 {
207        return Decoded::Infinite { sign };
208    }
209    if combination == 0b11111 {
210        let signaling = (bits >> (top - 7)) & 1 != 0;
211        return Decoded::Nan { sign, signaling };
212    }
213    let small = width.coefficient_bits();
214    let field = (1u128 << width.exponent_bits()) - 1;
215    let (biased, coefficient) = if (bits >> (top - 3)) & 0b11 == 0b11 {
216        let rest = small - 2;
217        ((bits >> rest) & field, (0b100 << rest) | (bits & ((1u128 << rest) - 1)))
218    } else {
219        ((bits >> small) & field, bits & ((1u128 << small) - 1))
220    };
221    // Fourteen bits at most, so the conversion cannot fail and the default is never read.
222    let exponent = i32::try_from(biased).unwrap_or(0) - width.bias();
223    let coefficient = if coefficient > width.max_coefficient() { 0 } else { coefficient };
224    Decoded::Finite { sign, coefficient, exponent }
225}
226
227/// The bits of the decimal constant `text` in this width, and what had to be done to it to fit.
228///
229/// `text` is what a decimal floating constant has in front of its `df`, `dd` or `dl`: digits
230/// with at most one point among them and an optional `e` exponent, and a sign in front if the
231/// caller has one to give. A hexadecimal constant has no decimal meaning, and C23 says so, so
232/// one is refused here as having a character a decimal number does not have.
233///
234/// # Errors
235///
236/// When the text has no digits, has an exponent marker with no digits after it, or has
237/// anything else in it.
238pub fn parse(text: &str, width: Width) -> Result<(u128, Status), ParseError> {
239    let bytes = text.as_bytes();
240    let (sign, rest) = match bytes.first() {
241        Some(b'-') => (true, &bytes[1..]),
242        Some(b'+') => (false, &bytes[1..]),
243        _ => (false, bytes),
244    };
245    let (mantissa, exponent_text) = match rest.iter().position(|&b| b | 32 == b'e') {
246        Some(at) => (&rest[..at], Some(&rest[at + 1..])),
247        None => (rest, None),
248    };
249
250    // The significant digits, which start at the first one that is not zero, and how many
251    // were written after the point, which is what makes `1.20` a different encoding from `1.2`.
252    let mut digits = Vec::new();
253    let mut seen_digit = false;
254    let mut after_point: i64 = 0;
255    let mut point = false;
256    for &b in mantissa {
257        match b {
258            b'0'..=b'9' => {
259                seen_digit = true;
260                if point {
261                    after_point += 1;
262                }
263                if b != b'0' || !digits.is_empty() {
264                    digits.push(b - b'0');
265                }
266            }
267            b'.' if !point => point = true,
268            _ => return Err(ParseError::Invalid),
269        }
270    }
271    if !seen_digit {
272        return Err(ParseError::NoDigits);
273    }
274
275    let written = match exponent_text {
276        None => 0,
277        Some(text) => exponent(text)?,
278    };
279    // Clamped, since a written exponent of a billion and one of ten thousand both leave every
280    // coefficient out of range, and the arithmetic below then cannot overflow.
281    let limit = i64::from(width.max_exponent()) + 200;
282    let mut exponent = (written - after_point).clamp(-limit * 2, limit * 2);
283
284    let precision = width.digits() as usize;
285    let mut status = Status::NONE;
286    if digits.len() > precision {
287        let dropped = digits.len() - precision;
288        exponent += i64::try_from(dropped).unwrap_or(i64::MAX / 4).min(limit * 4);
289        let up = rounds_up(&digits[..precision], &digits[precision..]);
290        if digits[precision..].iter().any(|&d| d != 0) {
291            status = status.with(Status::INEXACT);
292        }
293        digits.truncate(precision);
294        let mut coefficient = value(&digits);
295        if up {
296            coefficient += 1;
297            if coefficient > width.max_coefficient() {
298                coefficient /= 10;
299                exponent += 1;
300            }
301        }
302        return Ok(fit(sign, coefficient, exponent, width, status));
303    }
304    Ok(fit(sign, value(&digits), exponent, width, status))
305}
306
307/// The number an exponent's text writes, saturated well past any exponent a format has.
308fn exponent(text: &[u8]) -> Result<i64, ParseError> {
309    let (negative, digits) = match text.first() {
310        Some(b'-') => (true, &text[1..]),
311        Some(b'+') => (false, &text[1..]),
312        _ => (false, text),
313    };
314    if digits.is_empty() {
315        return Err(ParseError::NoExponentDigits);
316    }
317    let mut value: i64 = 0;
318    for &b in digits {
319        if !b.is_ascii_digit() {
320            return Err(ParseError::Invalid);
321        }
322        value = (value * 10 + i64::from(b - b'0')).min(1 << 40);
323    }
324    Ok(if negative { -value } else { value })
325}
326
327/// The integer a run of at most thirty eight digits writes.
328fn value(digits: &[u8]) -> u128 {
329    digits.iter().fold(0u128, |value, &d| value * 10 + u128::from(d))
330}
331
332/// Whether dropping `dropped` off the end of `kept` rounds the kept digits up, to nearest with
333/// ties to even.
334fn rounds_up(kept: &[u8], dropped: &[u8]) -> bool {
335    match dropped.first() {
336        Some(&first) if first > 5 => true,
337        Some(5) => dropped[1..].iter().any(|&d| d != 0) || kept.last().is_some_and(|&d| d % 2 == 1),
338        _ => false,
339    }
340}
341
342/// The encoding of `coefficient * 10^exponent`, whose coefficient already has few enough
343/// digits, with the exponent brought into range.
344///
345/// An exponent above the range is brought down by adding zeros to the coefficient while there
346/// is room for them, which is IEEE's clamping and changes nothing about the value, and past that
347/// the number is too large and is an infinity. An exponent below the range drops digits off the
348/// coefficient, rounding, until it is in range, which is where subnormal numbers and underflow
349/// to zero come from. A zero keeps its sign and takes the nearest exponent in range.
350fn fit(
351    sign: bool,
352    mut coefficient: u128,
353    mut exponent: i64,
354    width: Width,
355    mut status: Status,
356) -> (u128, Status) {
357    let min = i64::from(width.min_exponent());
358    let max = i64::from(width.max_exponent());
359    if coefficient == 0 {
360        let exponent = i32::try_from(exponent.clamp(min, max)).expect("clamped into range");
361        return (encode(sign, 0, exponent, width), status);
362    }
363    while exponent > max && coefficient * 10 <= width.max_coefficient() {
364        coefficient *= 10;
365        exponent -= 1;
366    }
367    if exponent > max {
368        return (infinity(sign, width), status.with(Status::OVERFLOW).with(Status::INEXACT));
369    }
370    if exponent < min {
371        let mut remainder_nonzero = false;
372        let mut last = 0u128;
373        let mut first = true;
374        let mut steps = 0;
375        while exponent < min {
376            if !first && last != 0 {
377                remainder_nonzero = true;
378            }
379            first = false;
380            last = coefficient % 10;
381            coefficient /= 10;
382            exponent += 1;
383            steps += 1;
384            if coefficient == 0 && steps > 40 {
385                exponent = min;
386            }
387        }
388        // `last` is the most significant digit dropped and `remainder_nonzero` says whether
389        // anything below it was not zero.
390        let up = last > 5 || (last == 5 && (remainder_nonzero || coefficient % 2 == 1));
391        if last != 0 || remainder_nonzero {
392            status = status.with(Status::INEXACT).with(Status::UNDERFLOW);
393        }
394        if up {
395            coefficient += 1;
396        }
397    }
398    let exponent = i32::try_from(exponent).expect("brought into range");
399    (encode(sign, coefficient, exponent, width), status)
400}
401
402#[cfg(test)]
403mod tests {
404    use super::*;
405
406    fn bits(text: &str, width: Width) -> u128 {
407        parse(text, width).expect("a number").0
408    }
409
410    fn finite(text: &str, width: Width) -> (bool, u128, i32) {
411        match decode(bits(text, width), width) {
412            Decoded::Finite { sign, coefficient, exponent } => (sign, coefficient, exponent),
413            other => panic!("{text} is {other:?}"),
414        }
415    }
416
417    /// The encodings gcc 16 gives these constants on x86-64, printed from a program it compiled.
418    #[test]
419    fn the_bits_are_the_ones_gcc_writes() {
420        assert_eq!(bits("0.", Width::D64), 0x31c0_0000_0000_0000);
421        assert_eq!(bits("-0.", Width::D64), 0xb1c0_0000_0000_0000);
422        assert_eq!(bits("1", Width::D64), 0x31c0_0000_0000_0001);
423        assert_eq!(bits("1.5", Width::D32), 0x3200_000f);
424        assert_eq!(bits("1", Width::D128), 0x3040_0000_0000_0000_0000_0000_0000_0001);
425        assert_eq!(bits("9999999", Width::D32), 0x6cb8_967f);
426        assert_eq!(bits("1.20", Width::D64), 0x3180_0000_0000_0078);
427        assert_eq!(bits("12345675", Width::D32), 0x3312_d688);
428        assert_eq!(bits("99999995", Width::D32), 0x338f_4240);
429        assert_eq!(bits("1e96", Width::D32), 0x5f8f_4240);
430        assert_eq!(bits("15e-102", Width::D32), 0x0000_0002);
431        assert_eq!(bits("0e999", Width::D64), 0x5fe0_0000_0000_0000);
432        assert_eq!(bits("0.000", Width::D32), 0x3100_0000);
433    }
434
435    #[test]
436    fn a_constant_keeps_the_exponent_its_text_wrote() {
437        assert_eq!(finite("1.20", Width::D64), (false, 120, -2));
438        assert_eq!(finite("1.2", Width::D64), (false, 12, -1));
439        assert_eq!(finite("12e3", Width::D64), (false, 12, 3));
440        assert_eq!(finite("0.000", Width::D32), (false, 0, -3));
441        assert_eq!(finite("-00012.5E-1", Width::D128), (true, 125, -2));
442    }
443
444    #[test]
445    fn too_many_digits_round_to_nearest_with_ties_to_even() {
446        assert_eq!(finite("12345675", Width::D32), (false, 1_234_568, 1));
447        assert_eq!(finite("12345665", Width::D32), (false, 1_234_566, 1));
448        assert_eq!(finite("123456650001", Width::D32), (false, 1_234_567, 5));
449        assert_eq!(finite("99999995", Width::D32), (false, 1_000_000, 2));
450        let (_, status) = parse("12345675", Width::D32).expect("a number");
451        assert!(status.has(Status::INEXACT));
452        let (_, status) = parse("12345670", Width::D32).expect("a number");
453        assert!(status.is_none(), "only zeros were dropped");
454    }
455
456    #[test]
457    fn an_exponent_out_of_range_is_clamped_overflows_or_underflows() {
458        assert_eq!(finite("1e96", Width::D32), (false, 1_000_000, 90));
459        let (value, status) = parse("1e97", Width::D32).expect("a number");
460        assert_eq!(value, infinity(false, Width::D32));
461        assert!(status.has(Status::OVERFLOW));
462        assert_eq!(finite("1e-101", Width::D32), (false, 1, -101));
463        let (value, status) = parse("15e-102", Width::D32).expect("a number");
464        assert_eq!(
465            decode(value, Width::D32),
466            Decoded::Finite { sign: false, coefficient: 2, exponent: -101 }
467        );
468        assert!(status.has(Status::UNDERFLOW));
469        let (value, _) = parse("1e-200", Width::D32).expect("a number");
470        assert_eq!(
471            decode(value, Width::D32),
472            Decoded::Finite { sign: false, coefficient: 0, exponent: -101 }
473        );
474        assert_eq!(finite("0e999999999999", Width::D64), (false, 0, 369));
475    }
476
477    #[test]
478    fn every_width_round_trips_its_largest_coefficient() {
479        for width in [Width::D32, Width::D64, Width::D128] {
480            for exponent in [width.min_exponent(), 0, width.max_exponent()] {
481                for sign in [false, true] {
482                    let max = width.max_coefficient();
483                    let bits = encode(sign, max, exponent, width);
484                    assert_eq!(
485                        decode(bits, width),
486                        Decoded::Finite { sign, coefficient: max, exponent }
487                    );
488                }
489            }
490        }
491    }
492
493    #[test]
494    fn infinities_and_nans_read_back_as_themselves() {
495        for width in [Width::D32, Width::D64, Width::D128] {
496            assert_eq!(decode(infinity(true, width), width), Decoded::Infinite { sign: true });
497            assert_eq!(decode(nan(width), width), Decoded::Nan { sign: false, signaling: false });
498        }
499        assert_eq!(infinity(false, Width::D64), 0x7800_0000_0000_0000);
500        assert_eq!(nan(Width::D64), 0x7c00_0000_0000_0000);
501    }
502
503    #[test]
504    fn text_that_is_not_a_decimal_number_is_refused() {
505        assert_eq!(parse("", Width::D64), Err(ParseError::NoDigits));
506        assert_eq!(parse(".", Width::D64), Err(ParseError::NoDigits));
507        assert_eq!(parse("1e", Width::D64), Err(ParseError::NoExponentDigits));
508        assert_eq!(parse("0x1p3", Width::D64), Err(ParseError::Invalid));
509        assert_eq!(parse("1.2.3", Width::D64), Err(ParseError::Invalid));
510    }
511}