decstr/
convert.rs

1/*!
2Conversions between text-encoded and binary-encoded decimal numbers.
3*/
4
5use crate::{
6    binary::{
7        decode_combination_finite,
8        decode_significand_trailing_declets,
9        encode_combination_finite,
10        encode_combination_infinity,
11        encode_combination_nan,
12        encode_significand_trailing_digits,
13        is_finite,
14        is_infinite,
15        is_nan,
16        is_quiet_nan,
17        is_sign_negative,
18        BinaryBuf,
19        BinaryExponent,
20    },
21    num::Integer,
22    text::{
23        ParsedDecimal,
24        ParsedDecimalPoint,
25        ParsedFinite,
26        ParsedInfinity,
27        ParsedNan,
28        ParsedNanHeader,
29        ParsedSignificand,
30        TextBuf,
31    },
32    OverflowError,
33};
34use core::{
35    fmt,
36    str,
37};
38
39mod from_binary_float;
40mod from_int;
41mod from_str;
42
43pub(crate) use self::{
44    from_binary_float::*,
45    from_int::*,
46    from_str::*,
47};
48
49/**
50Convert a decimal parsed from text into its binary form.
51*/
52pub(crate) fn decimal_from_parsed<D: BinaryBuf, T: TextBuf>(
53    parsed: ParsedDecimal<T>,
54) -> Result<D, OverflowError> {
55    match parsed {
56        // ±1.234e±5
57        ParsedDecimal::Finite(ParsedFinite {
58            finite_buf,
59            finite_significand:
60                ParsedSignificand {
61                    significand_is_negative,
62                    significand_range,
63                    decimal_point,
64                },
65            finite_exponent,
66        }) => {
67            let buf = finite_buf.get_ascii();
68
69            // First, get a starting point for the exponent.
70            let unbiased_exponent = match finite_exponent {
71                // If the number has an explicit exponent, like `1.23e4`, then parse it.
72                //
73                // The value will already have been validated, so this is more of a conversion
74                // than a regular parse.
75                Some(exponent) => D::try_exponent_from_ascii(
76                    exponent.exponent_is_negative,
77                    buf[exponent.exponent_range].iter().copied(),
78                )?,
79                // If the number doesn't have an explicit exponent, like `1.23`, then use 0.
80                None => D::default_exponent(),
81            };
82
83            match decimal_point {
84                // ±1.234e5
85                Some(ParsedDecimalPoint {
86                    decimal_point_range,
87                }) => {
88                    let integer_range = significand_range.start..decimal_point_range.start;
89                    let fractional_range = decimal_point_range.end..significand_range.end;
90
91                    let integer_digits = &buf[integer_range];
92                    let fractional_digits = &buf[fractional_range];
93
94                    // Account for the fractional part of the number
95                    // This is where the exponent range that an end-user sees may
96                    // be different than what's actually encoded. For example, the
97                    // exponent range of a decimal64 is -383 to 384, as in
98                    // 10e-383 and 10e384. However, for each fractional digit the
99                    // exponent range is decreased by 1. This doesn't change the
100                    // actual range of what's encoded, it just lets you specify
101                    // the same values in different ways. 1.0e-382 and 1.0e385
102                    // are both equivalent to the values mentioned before.
103                    let unbiased_integer_exponent =
104                        unbiased_exponent.lower(fractional_digits.len());
105
106                    // Get a decimal buffer with enough space to fit all the digits
107                    // and the exponent
108                    let mut buf = D::try_with_at_least_precision(
109                        integer_digits.len() + fractional_digits.len(),
110                        Some(&unbiased_integer_exponent),
111                    )?;
112
113                    let msd = encode_significand_trailing_digits(
114                        &mut buf,
115                        [integer_digits, fractional_digits],
116                    );
117
118                    encode_combination_finite(
119                        &mut buf,
120                        significand_is_negative,
121                        unbiased_integer_exponent,
122                        msd,
123                    );
124
125                    Ok(buf)
126                }
127                // ±123e4
128                None => {
129                    let integer_range = significand_range;
130                    let integer_digits = &buf[integer_range];
131
132                    // Get a decimal buffer with enough space to fit all the digits
133                    // and the exponent
134                    let mut buf = D::try_with_at_least_precision(
135                        integer_digits.len(),
136                        Some(&unbiased_exponent),
137                    )?;
138
139                    let msd = encode_significand_trailing_digits(&mut buf, [integer_digits]);
140
141                    encode_combination_finite(
142                        &mut buf,
143                        significand_is_negative,
144                        unbiased_exponent,
145                        msd,
146                    );
147
148                    Ok(buf)
149                }
150            }
151        }
152        // ±inf
153        ParsedDecimal::Infinity(ParsedInfinity {
154            is_infinity_negative,
155        }) => {
156            // Infinity doesn't encode any special information, so we can ask for a buffer
157            // with the minimum size supported
158            let mut buf = D::try_with_at_least_storage_width_bytes(4)
159                .expect("infinity will always fit in the minimal sized buffer");
160
161            encode_combination_infinity(&mut buf, is_infinity_negative);
162
163            Ok(buf)
164        }
165        // ±nan(123)
166        ParsedDecimal::Nan(ParsedNan {
167            nan_buf,
168            nan_header:
169                ParsedNanHeader {
170                    is_nan_signaling,
171                    is_nan_negative,
172                },
173            nan_payload,
174        }) => {
175            // If the NaN was parsed with a payload then encode it.
176            //
177            // This process is the same as finite integers.
178            if let Some(ParsedSignificand {
179                significand_range, ..
180            }) = nan_payload
181            {
182                let payload_buf = nan_buf.get_ascii();
183
184                let mut buf = D::try_with_at_least_precision(significand_range.len() + 1, None)?;
185
186                encode_significand_trailing_digits(&mut buf, [&payload_buf[significand_range]]);
187
188                encode_combination_nan(&mut buf, is_nan_negative, is_nan_signaling);
189
190                Ok(buf)
191            }
192            // If the NaN doesn't have a payload then just ask for the minimum size buffer,
193            // just like we do for infinities.
194            else {
195                let mut buf = D::try_with_at_least_storage_width_bytes(4)
196                    .expect("a NaN with no payload will always fit in the minimal sized buffer");
197
198                encode_combination_nan(&mut buf, is_nan_negative, is_nan_signaling);
199
200                Ok(buf)
201            }
202        }
203    }
204}
205
206/**
207Convert a decimal in its binary form into text.
208*/
209pub(crate) fn decimal_to_fmt<D: BinaryBuf>(
210    decimal: &D,
211    mut out: impl fmt::Write,
212) -> Result<(), fmt::Error> {
213    // Write the sign
214    if is_sign_negative(decimal) {
215        out.write_char('-')?;
216    }
217
218    // ±1.234e±5
219    if is_finite(decimal) {
220        let mut written = 0;
221
222        let (exponent, msd) = decode_combination_finite(decimal);
223        let msd = msd.get_ascii();
224
225        // The formatter works in declets, which are 3 digits at a time,
226        // in order from most to least significant.
227        //
228        // The precision of a decimal is always some number of declets plus
229        // the lonely most-significant-digit. We create a dummy declet for it here.
230        // Calculations that depend on the precision of the number need to have these
231        // extra two zeroes accounted for.
232        let mut declets = decode_significand_trailing_declets(decimal);
233
234        match exponent.to_i32() {
235            // ±123
236            Some(0) => {
237                write_all_as_integer(
238                    skip_leading_zeroes(msd, &mut declets),
239                    declets,
240                    &mut written,
241                    &mut out,
242                )?;
243            }
244            // ±123.456
245            Some(exponent) if exponent.is_negative() => {
246                // Skip leading zeroes
247                let skipped = skip_leading_zeroes(msd, &mut declets);
248
249                // Work out how many digits are remaining after skipping leading zeroes.
250                //
251                // The extra two precision accounts for the dummy declet created for the
252                // most-significant-digit.
253                let non_zero_digits =
254                    adjusted_precision_digits_with_msd_declet(decimal) - skipped.skipped;
255
256                // Work out where the exponent falls in the decimal
257                // It might either be somewhere in the middle, as in `123.456`, or before it, as in `0.00123456`.
258                match non_zero_digits
259                    .try_into()
260                    .map(|non_zero_digits: i32| {
261                        // The `exponent` is negative, so adding it to the non-zero number of digits will work out how
262                        // many digits we need to write before the decimal point.
263                        //
264                        // This value itself may end up negative. If that happens it means there aren't enough
265                        // digits to put the decimal point between. In that case, we'll write `0.` and then
266                        // some number of leading zeroes first.
267                        non_zero_digits + exponent
268                    })
269                    .ok()
270                {
271                    // ±123.456
272                    Some(integer_digits) if integer_digits > 0 => {
273                        let total_integer_digits = integer_digits as usize;
274
275                        // Write digits up to the decimal point
276                        let mut written_decimal_point = false;
277
278                        if let Some((declet, idx)) = skipped.partial_declet {
279                            written_decimal_point = write_decimal_digits(
280                                &declet[idx..],
281                                total_integer_digits,
282                                &mut written,
283                                &mut out,
284                            )?;
285                        }
286
287                        while !written_decimal_point {
288                            written_decimal_point = write_decimal_digits(
289                                &declets
290                                    .next()
291                                    .expect("ran out of digits before the decimal point"),
292                                total_integer_digits,
293                                &mut written,
294                                &mut out,
295                            )?;
296                        }
297
298                        // Write any remaining fractional digits
299                        for declet in declets {
300                            write_declet(declet, &mut written, &mut out)?;
301                        }
302                    }
303                    // ±0.0123
304                    Some(leading_zeroes) => {
305                        debug_assert!(leading_zeroes == 0 || leading_zeroes.is_negative());
306
307                        // This buffer determines how many leading zeroes we'll try write
308                        // before falling back to exponential notation
309                        const DECIMAL_ZEROES: &str = "0.00000";
310
311                        let leading_zeroes = leading_zeroes.unsigned_abs() as usize;
312
313                        // If the decimal point is before the non-zero digits, and there
314                        // aren't too many leading zeroes then write them directly.
315                        if leading_zeroes + "0.".len() <= DECIMAL_ZEROES.len() {
316                            // Write the leading zeroes along with the decimal point
317                            write_content(
318                                &DECIMAL_ZEROES[..leading_zeroes + "0.".len()],
319                                leading_zeroes,
320                                &mut written,
321                                &mut out,
322                            )?;
323
324                            // Write the declets as an integer following the leading fractional zeroes
325                            write_all_as_integer(skipped, declets, &mut written, &mut out)?;
326                        }
327                        // If there are too many leading zeroes then write the number in scientific notation
328                        else {
329                            write_all_as_scientific(
330                                skipped,
331                                declets,
332                                exponent,
333                                &mut written,
334                                &mut out,
335                            )?;
336                        }
337                    }
338                    // If the exponent is too large for an `i32` then write the number in scientific notation
339                    None => {
340                        write_all_as_scientific(
341                            skipped,
342                            declets,
343                            exponent,
344                            &mut written,
345                            &mut out,
346                        )?;
347                    }
348                }
349            }
350            // ±1.234e±5
351            _ => {
352                write_all_as_scientific(
353                    skip_leading_zeroes(msd, &mut declets),
354                    declets,
355                    exponent,
356                    &mut written,
357                    &mut out,
358                )?;
359            }
360        }
361
362        Ok(())
363    }
364    // ±inf
365    else if is_infinite(decimal) {
366        out.write_str("inf")
367    }
368    // ±nan(123)
369    else {
370        debug_assert!(is_nan(decimal));
371
372        if is_quiet_nan(decimal) {
373            out.write_str("nan")?;
374        } else {
375            out.write_str("snan")?;
376        }
377
378        // NaNs may include an integer payload.
379        //
380        // If the payload is non-zero then it'll also be written to the output.
381        let mut payload = decode_significand_trailing_declets(decimal)
382            .flatten()
383            .peekable();
384
385        // Skip over leading zeroes in the payload
386        while let Some(b'0') = payload.peek() {
387            let _ = payload.next();
388        }
389
390        // If there are any non-zero digits, then write them between braces
391        if payload.peek().is_some() {
392            out.write_char('(')?;
393
394            for digit in payload {
395                out.write_char(digit as char)?;
396            }
397
398            out.write_char(')')?;
399        }
400
401        Ok(())
402    }
403}
404
405fn adjusted_precision_digits_with_msd_declet(decimal: &impl BinaryBuf) -> usize {
406    decimal.precision_digits() + 2
407}
408
409fn skip_leading_zeroes(msd_ascii: u8, declets: impl Iterator<Item = [u8; 3]>) -> LeadingZeroes {
410    let mut skipped = 0;
411
412    // Check the most-significant-digit
413    if msd_ascii == b'0' {
414        skipped += 3;
415    } else {
416        return LeadingZeroes {
417            skipped: 2,
418            partial_declet: Some(([b'0', b'0', msd_ascii], 2)),
419        };
420    }
421
422    for declet in declets {
423        // If the declet contains just zeroes then skip them entirely
424        if declet == [b'0', b'0', b'0'] {
425            skipped += 3;
426            continue;
427        }
428        // Find the first non-zero slice of the declet
429        else {
430            let mut i = 0;
431            for digit in declet {
432                if digit != b'0' {
433                    break;
434                }
435
436                i += 1;
437            }
438
439            skipped += i;
440
441            return LeadingZeroes {
442                skipped,
443                partial_declet: Some((declet, i)),
444            };
445        }
446    }
447
448    LeadingZeroes {
449        skipped,
450        partial_declet: None,
451    }
452}
453
454fn write_content(
455    content: &str,
456    digits: usize,
457    written: &mut usize,
458    mut out: impl fmt::Write,
459) -> Result<(), fmt::Error> {
460    *written += digits;
461    out.write_str(content)
462}
463
464fn write_digits(
465    digits: &[u8],
466    written: &mut usize,
467    out: impl fmt::Write,
468) -> Result<(), fmt::Error> {
469    write_content(
470        str::from_utf8(digits).map_err(|_| fmt::Error)?,
471        digits.len(),
472        written,
473        out,
474    )
475}
476
477fn write_declet(
478    declet: [u8; 3],
479    written: &mut usize,
480    out: impl fmt::Write,
481) -> Result<(), fmt::Error> {
482    write_content(
483        str::from_utf8(&declet).map_err(|_| fmt::Error)?,
484        declet.len(),
485        written,
486        out,
487    )
488}
489
490fn write_decimal_digits(
491    digits: &[u8],
492    total_integer_digits: usize,
493    written: &mut usize,
494    mut out: impl fmt::Write,
495) -> Result<bool, fmt::Error> {
496    // If the decimal point doesn't intersect these digits then write it and continue
497    if *written + digits.len() <= total_integer_digits {
498        write_digits(digits, written, &mut out)?;
499
500        Ok(false)
501    }
502    // If the decimal point falls at the start of these digits then print it first
503    else if *written == total_integer_digits {
504        out.write_char('.')?;
505        write_digits(digits, written, &mut out)?;
506
507        Ok(true)
508    }
509    // If the decimal point falls within these digits then print it in the middle and break
510    else {
511        let decimal_point = total_integer_digits - *written;
512
513        write_digits(&digits[..decimal_point], written, &mut out)?;
514        out.write_char('.')?;
515        write_digits(&digits[decimal_point..], written, &mut out)?;
516
517        Ok(true)
518    }
519}
520
521fn write_all_as_integer(
522    leading_zeroes: LeadingZeroes,
523    declets: impl Iterator<Item = [u8; 3]>,
524    written: &mut usize,
525    mut out: impl fmt::Write,
526) -> Result<(), fmt::Error> {
527    if let LeadingZeroes {
528        partial_declet: Some((declet, idx)),
529        ..
530    } = leading_zeroes
531    {
532        write_digits(&declet[idx..], written, &mut out)?;
533    }
534
535    // Write the remaining digits
536    for declet in declets {
537        write_declet(declet, written, &mut out)?;
538    }
539
540    // If no digits were written then write a zero
541    if *written == 0 {
542        out.write_char('0')?;
543    }
544
545    Ok(())
546}
547
548fn write_all_as_scientific(
549    leading_zeroes: LeadingZeroes,
550    mut declets: impl Iterator<Item = [u8; 3]>,
551    exponent: impl BinaryExponent,
552    written: &mut usize,
553    mut out: impl fmt::Write,
554) -> Result<(), fmt::Error> {
555    // Write the first declet along with a decimal point
556    if let LeadingZeroes {
557        partial_declet: Some((declet, idx)),
558        ..
559    } = leading_zeroes
560    {
561        write_decimal_digits(&declet[idx..], 1, written, &mut out)?;
562    } else if let Some(declet) = declets.next() {
563        write_content(
564            str::from_utf8(&[declet[0], b'.', declet[1], declet[2]]).map_err(|_| fmt::Error)?,
565            1,
566            written,
567            &mut out,
568        )?;
569    }
570
571    // Write the remaining digits
572    for declet in declets {
573        write_declet(declet, written, &mut out)?;
574    }
575
576    // If no digits were written, then write a zero
577    if *written == 0 {
578        out.write_str("0e")?;
579    } else {
580        out.write_char('e')?;
581    }
582
583    // Adjust the integer exponent to the form `1.23e4`.
584    //
585    // This means raising it to account for the number of fractional digits written.
586    let exponent = exponent.raise(written.saturating_sub(1));
587
588    // Write the exponent into the buffer
589    exponent.to_fmt(&mut out)?;
590
591    Ok(())
592}
593
594struct LeadingZeroes {
595    skipped: usize,
596    partial_declet: Option<([u8; 3], usize)>,
597}