surrealdb_expr/expr/decimal.rs
1//! Decimal functionality and extension traits.
2//!
3//! This module provides DecimalLexEncoder, a variable-length, lexicographic
4//! byte encoding for fastnum::D128 values. The encoding ensures that
5//! byte-wise ordering preserves numeric ordering, which is critical for key
6//! construction in indexes. The encoder is stream-friendly: it guarantees an
7//! in-band terminator within the mantissa encoding and appends a trailing 0x00
8//! so decoders can read until the first zero when values are concatenated in
9//! composite keys.
10//!
11//! Where it’s used:
12//! - val::number::Number::{as_decimal_buf, from_decimal_buf}
13//! - key::value::StoreKeyNumber serde impls used in index key material
14//!
15//! Ordering overview:
16//! - Finite negatives < zero < finite positives
17//! - −∞ < all finite < +∞ < NaN
18//!
19//! See the struct-level documentation below for the precise byte format.
20
21use anyhow::Result;
22use fastnum::decimal::{Context, Sign};
23use fastnum::{D128, U128};
24use rust_decimal::Decimal;
25
26use crate::expr::Error;
27
28/// Variable-length lexicographic encoding for D128 values that preserves sort
29/// order.
30///
31/// This encoder converts D128 values into byte sequences that maintain the same
32/// lexicographic ordering as the original decimal values. This is crucial for
33/// database indexing where byte-level comparison must match numeric comparison.
34///
35/// ## Encoding Format
36///
37/// The encoding uses a variable-length format optimized for lexicographic
38/// ordering:
39///
40/// ### Zero Values
41/// - Zero is encoded as a single byte: `0x80`
42/// - This ensures zero sorts between negative and positive numbers
43///
44/// ### Non-Zero Values
45/// The format consists of:
46/// 1. **Class/marker byte** (1 byte):
47/// - `0x80` Zero
48/// - `0x40` Finite negative
49/// - `0xA0` Finite positive
50/// - `0x20` Negative infinity
51/// - `0xC0` Positive infinity
52/// - `0xFF` NaN
53///
54/// 2. **Biased scale** (2 bytes, big-endian):
55/// - We bias the "scale" (not the raw exponent). Scale is defined as: `scale = exponent +
56/// (digit_count - 1)`, i.e., the position of the most-significant digit in a
57/// scientific-notation sense.
58/// - Stored as: `biased = scale + EXP_BIAS` (unsigned 16-bit)
59/// - For negative numbers: stored as `0xFFFF - biased` (one's complement) to reverse order
60/// - EXP_BIAS = 6144. With D128, `exponent ∈ [-6143, +6144]` and `digit_count ∈ [1, 34]`, so
61/// `scale ∈ [-6143, 6177]`, which maps into `[1, 12321]` after biasing, well within `u16`.
62///
63/// 3. **Packed digit representation** (variable length):
64/// - Digits are taken from the absolute value's base-10 representation
65/// - Each pair of digits is packed into one byte (4 bits per digit)
66/// - For positive numbers: stored as-is
67/// - For negative numbers: all bytes are bitwise complemented to reverse ordering
68/// - Termination: encoding stops when a nibble equals `0x0`. This naturally handles both odd and
69/// even digit counts: • odd count: the last byte has a low nibble of 0 • even count: an extra
70/// full terminator byte is appended (0x00 for positives, 0xFF for negatives)
71///
72/// Because a terminator is always present within (or immediately after) the
73/// mantissa, any trailing type-marker byte appended by higher layers will never
74/// be consumed by the mantissa decoder.
75///
76/// ## Properties
77/// - Preserves lexicographic ordering: if `a < b` then `encode(a) < encode(b)`
78/// - Variable length encoding (3+ bytes typical: 1 sign + 2 scale + packed digits)
79/// - Handles full D128 range including extreme values
80/// - Uses packed digit encoding for efficient storage (2 digits per byte)
81pub struct DecimalLexEncoder;
82
83impl DecimalLexEncoder {
84 /// We use a 16-bit biased "scale" (not the raw exponent).
85 /// With D128: exponent ∈ [-6143, +6144] and digit_count ∈ [1, 34], so
86 /// scale = exponent + (digit_count - 1) ∈ [-6143, 6177]. Adding EXP_BIAS
87 /// maps this into [1, 12321], comfortably within u16.
88 const EXP_BIAS: i32 = 6144; // bias used for mapping signed scale into u16 space
89
90 const FINITE_NEGATIVE_MARKER: u8 = 0x40;
91 const FINITE_POSITIVE_MARKER: u8 = 0xA0;
92
93 // 0x80 = 128, middle value for proper ordering
94 const ZERO_MARKER: u8 = 0x80;
95
96 const INFINITE_NEGATIVE_MARKER: u8 = 0x20;
97 const INFINITE_POSITIVE_MAKER: u8 = 0xC0;
98 const NAN_MARKER: u8 = 0xFF;
99
100 /// Encodes a D128 value into a lexicographically ordered byte sequence.
101 ///
102 /// The encoding preserves sort order: if `a < b` then `encode(a) <
103 /// encode(b)`. This is essential for database indexing where byte-level
104 /// comparison must match numeric comparison.
105 pub fn encode(dec: D128) -> Vec<u8> {
106 if dec.is_nan() {
107 return vec![Self::NAN_MARKER, 0x00];
108 }
109
110 // Extract sign
111 let is_negative = dec.is_negative();
112
113 if dec.is_infinite() {
114 if is_negative {
115 return vec![Self::INFINITE_NEGATIVE_MARKER, 0x00];
116 } else {
117 return vec![Self::INFINITE_POSITIVE_MAKER, 0x00];
118 }
119 }
120 // Special case: zero gets a fixed encoding that sorts between negative and
121 // positive
122 if dec.is_zero() {
123 return vec![Self::ZERO_MARKER, 0x00];
124 }
125
126 // Work with absolute value
127 let normalized = dec.abs(); // Get absolute value
128 let e = -normalized.fractional_digits_count() as i32; // Exponent: negative of fractional digits
129 let digit_count = normalized.digits_count();
130
131 // Calculate the scale that positions the first digit as the most significant
132 // This normalizes the number to scientific notation form
133 let scale = e + (digit_count as i32 - 1); // Scale for scientific notation
134
135 // Apply bias to map the scale range to unsigned 16-bit space
136 // For D128, scale ∈ [-6143, 6177] → [1, 12321] after adding EXP_BIAS.
137 let biased_exponent = (scale + Self::EXP_BIAS) as u16;
138
139 let encode_exponent = |e: u16| {
140 let q = (e / 255) as u8;
141 let r = (e % 255) as u8;
142 [q + 1, r + 1]
143 };
144
145 // Build the final encoded result
146 // Capacity: 1 sign + 2 exponent + packed digits (2 digits per byte) + potential
147 // terminator
148 let mut result = Vec::with_capacity(5 + (digit_count + 1).div_ceil(2));
149
150 // Convert the mantissa to decimal string representation for digit packing
151 let radix10 = normalized.digits().to_str_radix(10);
152
153 // Encode sign marker and biased scale based on sign
154 if is_negative {
155 // Sign marker: 0x00 ensures negative numbers sort before positive ones
156 result.push(Self::FINITE_NEGATIVE_MARKER);
157 // Complement of biased scale: reverses ordering so that more negative values
158 // sort first This maintains total ordering for negatives when compared
159 // bytewise.
160 result.extend(encode_exponent(0xFFFF - biased_exponent));
161 // Complement all packed digit bytes to reverse their ordering for negative
162 // numbers
163 Self::pack_digits_negative(&radix10, &mut result);
164 } else {
165 // Sign marker: 0xFF ensures positive numbers sort after negative ones
166 result.push(Self::FINITE_POSITIVE_MARKER);
167 // Biased scale: larger scales (greater magnitude) sort later for positives
168 result.extend(encode_exponent(biased_exponent));
169 // Store packed digit bytes directly for positive numbers
170 Self::pack_digits_positive(&radix10, &mut result);
171 }
172 result.push(0x00);
173 //
174 result
175 }
176
177 /// Decodes a lexicographically encoded byte sequence back to a D128 value.
178 ///
179 /// This reverses the encoding process, reconstructing the original D128
180 /// from its byte representation while handling all the encoding
181 /// transformations.
182 pub fn decode(bytes: &[u8]) -> Result<D128> {
183 // Handle empty buffer
184 if bytes.is_empty() {
185 return Err(Error::Serialization("Cannot decode from empty buffer".to_string()).into());
186 }
187
188 // Special cases
189 let is_negative = match bytes[0] {
190 Self::ZERO_MARKER => {
191 return Ok(D128::ZERO);
192 }
193 Self::INFINITE_NEGATIVE_MARKER => return Ok(D128::NEG_INFINITY),
194 Self::INFINITE_POSITIVE_MAKER => return Ok(D128::INFINITY),
195 Self::NAN_MARKER => return Ok(D128::NAN),
196 Self::FINITE_NEGATIVE_MARKER => true,
197 Self::FINITE_POSITIVE_MARKER => false,
198 marker => {
199 return Err(Error::Serialization(format!("Invalid marker byte: {marker}")).into());
200 }
201 };
202
203 // Need at least 3 bytes: marker (1) + exponent (2)
204 if bytes.len() < 3 {
205 return Err(Error::Serialization(format!("Buffer too short: {}", bytes.len())).into());
206 }
207
208 // Extract biased exponent (2 bytes with shift to avoid 0x00)
209 let biased_exponent = (bytes[1] - 1) as u16 * 255 + (bytes[2] - 1) as u16;
210 // Unbias the scale, handling negative number complement
211 let biased_exponent = if is_negative {
212 // For negative numbers, undo the complement applied during encoding
213 0xFFFF - biased_exponent
214 } else {
215 biased_exponent
216 };
217 // Convert back to the original scale by removing the bias
218 let scale = biased_exponent as i32 - Self::EXP_BIAS;
219
220 // Unpack the digit bytes back to decimal string, handling sign-specific
221 // encoding
222 let (mantissa, digit_count) = if is_negative {
223 Self::unpack_digits_negative(&bytes[3..])?
224 } else {
225 Self::unpack_digits_positive(&bytes[3..])?
226 };
227 if digit_count == 0 {
228 return Err(Error::Serialization("Empty mantissa".to_string()).into());
229 }
230 // Calculate the final exponent: scale minus the position adjustment for
231 // scientific notation
232 let exponent = scale - (digit_count - 1);
233
234 Ok(D128::from_parts(
235 mantissa,
236 exponent,
237 if is_negative {
238 Sign::Minus
239 } else {
240 Sign::Plus
241 },
242 Context::default(),
243 ))
244 }
245
246 /// Packs decimal digits for negative numbers with bit inversion for
247 /// lexicographic ordering. Each pair of ASCII digits is packed into a
248 /// single byte (4 bits each) and then inverted. Mapping: '0'..'9' → 1..10
249 /// (we avoid 0 so that 0 nibbles can be used as terminators).
250 /// For odd digit counts, the last byte has a zero low nibble; for even
251 /// counts, an extra 0xFF terminator byte is appended after bit inversion.
252 fn pack_digits_negative(radix10: &str, buf: &mut Vec<u8>) {
253 let mut iter = radix10.as_bytes().chunks_exact(2);
254 for pair in &mut iter {
255 // pair is &[u8; 2]
256 // Convert ASCII digits to numeric values: '0' (48) -> 1, '1' (49) -> 2, etc.
257 // We subtract 47 instead of 48 to map '0'->1, '1'->2, ..., '9'->10
258 // This ensures no digit maps to 0, which we use as termination marker
259 let hi = pair[0] - 47;
260 let lo = pair[1] - 47;
261 let packed = (hi << 4) | lo;
262 buf.push(!packed); // Invert bits for negative number lexicographic ordering
263 }
264 // If the length is odd, the remainder (the last lone byte) is here:
265 if let Some(remainder) = iter.remainder().first() {
266 let hi = remainder - 47;
267 let packed = hi << 4;
268 buf.push(!packed);
269 } else {
270 // Set the termination byte (inverted)
271 buf.push(0xF0); // !0x0F
272 }
273 }
274
275 /// Packs decimal digits for positive numbers into bytes for lexicographic
276 /// ordering. Each pair of ASCII digits is packed into a single byte (4
277 /// bits each). Mapping: '0'..'9' → 1..10. For odd digit counts, the last
278 /// byte has a zero low nibble; for even counts, an extra 0x00 terminator
279 /// byte is appended. This ensures decode will stop before any trailing
280 /// type marker appended by higher layers.
281 fn pack_digits_positive(radix10: &str, buf: &mut Vec<u8>) {
282 let mut iter = radix10.as_bytes().chunks_exact(2);
283 for pair in &mut iter {
284 // pair is &[u8; 2]
285 // Convert ASCII digits to numeric values: '0' (48) -> 1, '1' (49) -> 2, etc.
286 // We subtract 47 instead of 48 to map '0'->1, '1'->2, ..., '9'->10
287 // This ensures no digit maps to 0, which we use as termination marker
288 let hi = pair[0] - 47;
289 let lo = pair[1] - 47;
290 let packed = (hi << 4) | lo;
291 buf.push(packed);
292 }
293
294 // If the length is odd, the remainder (the last lone byte) is here:
295 if let Some(remainder) = iter.remainder().first() {
296 let hi = remainder - 47;
297 let packed = hi << 4;
298 buf.push(packed);
299 } else {
300 // Set the termination byte
301 buf.push(0x0F);
302 }
303 }
304
305 /// Unpacks digits from bytes for positive numbers.
306 /// Reverses the packing process by extracting digit pairs from each byte.
307 /// Stops when a nibble equals 0 (terminator). Accumulates the mantissa
308 /// directly into U128 and returns the total number of decoded digits.
309 fn unpack_digits_positive(buf: &[u8]) -> Result<(U128, i32)> {
310 let mut m = U128::ZERO;
311 let mut l = 0;
312 for pack in buf {
313 let d = Self::unpack_digit(*pack, &mut m)?;
314 l += d as i32;
315 if d < 2 {
316 break;
317 }
318 }
319 Ok((m, l))
320 }
321
322 /// Unpacks digits from bytes for negative numbers.
323 /// First inverts each byte to undo the bit inversion, then extracts digit
324 /// pairs. Stops when a nibble equals 0 (after inversion). Accumulates into
325 /// U128 and returns the number of decoded digits.
326 fn unpack_digits_negative(buf: &[u8]) -> Result<(U128, i32)> {
327 let mut m = U128::ZERO;
328 let mut l = 0i32;
329 for pack in buf {
330 let d = Self::unpack_digit(!*pack, &mut m)?;
331 l += d as i32;
332 if d < 2 {
333 break;
334 }
335 }
336 Ok((m, l))
337 }
338
339 /// Unpacks a single packed byte into one or two digits.
340 /// Returns the number of digits appended (0, 1, or 2). A return of 0 or 1
341 /// indicates that a terminator nibble (0x0) was encountered and the caller
342 /// should stop.
343 ///
344 /// The byte contains two 4-bit values (nibbles): high nibble and low
345 /// nibble. Each nibble represents a digit value (1..=10 mapping to
346 /// '0'..'9'). Values outside 1..=10 are rejected as corrupted input.
347 fn unpack_digit(pack: u8, m: &mut U128) -> Result<u8> {
348 let hi = pack >> 4;
349 let lo = pack & 0x0F;
350 if hi == 0x0 {
351 return Ok(0);
352 }
353 if !(1..=10).contains(&hi) {
354 return Err(anyhow::Error::new(Error::Serialization(format!(
355 "Invalid high nibble: {hi}"
356 ))));
357 }
358 *m = *m * U128::TEN + U128::from(hi - 1);
359 if lo == 0 {
360 return Ok(1);
361 }
362 if !(1..=10).contains(&lo) {
363 return Err(anyhow::Error::new(Error::Serialization(format!(
364 "Invalid low nibble: {lo}"
365 ))));
366 }
367 *m = *m * U128::TEN + U128::from(lo - 1);
368 Ok(2)
369 }
370
371 /// Converts a rust_decimal::Decimal to a fastnum::D128.
372 ///
373 /// This conversion extracts the mantissa, scale, and sign from the Decimal
374 /// and reconstructs them as a D128 value.
375 pub fn to_d128(dec: Decimal) -> D128 {
376 let scale = dec.scale();
377 let mantissa = dec.mantissa(); // i128
378 let sign = if mantissa < 0 {
379 Sign::Minus
380 } else {
381 Sign::Plus
382 };
383 // Investigation of the code of the from_u128 at the time of adding the unwrap shows that
384 // this function cannot panic, weirdly it seems unable to ever return an error.
385 //
386 // The function returns an error when the number has more significant bits then can fit
387 // into the amount of bits of a u128 which seems impossible for a function which takes a
388 // u128.
389 let abs =
390 U128::from_u128(mantissa.unsigned_abs()).expect("u128 conversion should not fail");
391 D128::from_parts(abs, -(scale as i32), sign, Context::default())
392 }
393
394 /// Converts a fastnum::D128 to a rust_decimal::Decimal through the D128's
395 /// text.
396 ///
397 /// The text of a D128 whose exponent is below -6 is in scientific notation,
398 /// which `Decimal::from_str_radix` does not read as a number, and a value
399 /// with more than 28 fractional digits is rounded. It is therefore only
400 /// meaningful for values that print positionally; a caller that must get the
401 /// same value back uses [`Self::to_decimal_exact`].
402 pub fn to_decimal(d128: D128) -> Result<Decimal> {
403 Ok(Decimal::from_str_radix(&d128.to_string(), 10)?)
404 }
405
406 /// Converts a finite fastnum::D128 to the rust_decimal::Decimal of exactly
407 /// the same value.
408 ///
409 /// The inverse of [`Self::to_d128`]: every Decimal it was given converts
410 /// back unchanged, whatever its scale. A D128 that no Decimal can hold
411 /// exactly — a magnitude beyond the 96-bit mantissa, or a significant digit
412 /// past the 28th fractional place — is an error rather than a rounded value.
413 pub fn to_decimal_exact(d128: D128) -> Result<Decimal> {
414 // Largest mantissa a Decimal holds: 2^96 - 1.
415 const MAX_MANTISSA: u128 = (1 << 96) - 1;
416 // Most fractional digits a Decimal holds.
417 const MAX_SCALE: i32 = 28;
418
419 let unrepresentable =
420 || anyhow::Error::new(Error::Serialization(format!("{d128} is not a Decimal")));
421 if d128.is_zero() {
422 return Ok(Decimal::ZERO);
423 }
424 let mut mantissa =
425 d128.digits().to_str_radix(10).parse::<u128>().map_err(|_| unrepresentable())?;
426 // The value is `mantissa * 10^exponent`.
427 let mut exponent = -(d128.fractional_digits_count() as i32);
428 // A positive exponent is trailing zeros the integer has to spell out.
429 while exponent > 0 {
430 mantissa = mantissa.checked_mul(10).ok_or_else(unrepresentable)?;
431 exponent -= 1;
432 }
433 // Past the Decimal's scale, only trailing zeros of the mantissa can go.
434 while exponent < -MAX_SCALE && mantissa % 10 == 0 {
435 mantissa /= 10;
436 exponent += 1;
437 }
438 if exponent < -MAX_SCALE || mantissa > MAX_MANTISSA {
439 return Err(unrepresentable());
440 }
441 let mantissa = mantissa as i128;
442 Ok(Decimal::from_i128_with_scale(
443 if d128.is_negative() {
444 -mantissa
445 } else {
446 mantissa
447 },
448 (-exponent) as u32,
449 ))
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456
457 fn test_cases() -> [D128; 32] {
458 [
459 D128::from(f64::NEG_INFINITY),
460 D128::from(f64::MIN),
461 D128::from_i128(i128::MIN).unwrap(),
462 D128::from(i64::MIN),
463 D128::from(-1001),
464 D128::from(-1000),
465 D128::from(-999),
466 D128::from(-100),
467 -D128::TEN,
468 D128::from(-9),
469 D128::from(-3.15),
470 D128::from(-std::f64::consts::PI),
471 D128::from(-1.5f64),
472 -D128::ONE,
473 D128::ZERO,
474 D128::ONE,
475 D128::from(1.5f64),
476 D128::from(2),
477 D128::from(std::f64::consts::PI),
478 D128::from(3.15),
479 D128::from(9),
480 D128::TEN,
481 D128::from(100),
482 D128::from(999),
483 D128::from(1000),
484 D128::from(1001),
485 D128::from(i64::MAX),
486 D128::from_i128(i128::MAX).unwrap(),
487 D128::from_u128(u128::MAX).unwrap(),
488 D128::from(f64::MAX),
489 D128::from(f64::INFINITY),
490 D128::from(f64::NAN),
491 ]
492 }
493
494 #[test]
495 fn test_encode_decode_roundtrip() {
496 let cases = test_cases();
497 for (i, case) in cases.into_iter().enumerate() {
498 let encoded = DecimalLexEncoder::encode(case);
499 let decoded = DecimalLexEncoder::decode(&encoded).expect("Decode should succeed");
500 if case.is_nan() {
501 assert!(decoded.is_nan(), "Roundtrip failed for {i}: {case} != {decoded}");
502 } else {
503 assert_eq!(case, decoded, "Roundtrip failed for {i}: {case} != {decoded}");
504 }
505 }
506 }
507
508 #[test]
509 fn test_encode_terminate_with_zero() {
510 let cases = test_cases();
511 for (i, case) in cases.into_iter().enumerate() {
512 let encoded = DecimalLexEncoder::encode(case);
513 assert_eq!(
514 encoded.iter().filter(|&b| *b == 0x00).count(),
515 1,
516 "Encoded buffer should contains only one 0x00 - {i}: {case} {encoded:?}"
517 );
518 assert_eq!(
519 encoded.iter().position(|&b| b == 0x00).unwrap(),
520 encoded.len() - 1,
521 "Encoded buffer should terminate with 0x00 - {i}: {case} {encoded:?}"
522 );
523 }
524 }
525
526 #[test]
527 fn test_lexicographic_ordering() {
528 let cases = test_cases();
529 for (i, window) in cases.windows(2).enumerate() {
530 let n1 = &window[0];
531 let n2 = &window[1];
532 assert!(n1 < n2, "#{i} - {n1:?} < {n2:?} (before serialization)");
533 let b1 = DecimalLexEncoder::encode(*n1);
534 let b2 = DecimalLexEncoder::encode(*n2);
535 assert!(b1 < b2, "#{i} - {n1:?} < {n2:?} (after serialization) - {b1:?} < {b2:?}");
536 }
537 }
538
539 #[test]
540 fn test_decode_empty_buffer() {
541 let result = DecimalLexEncoder::decode(&[]);
542 assert!(result.is_err());
543 assert!(result.unwrap_err().to_string().contains("Cannot decode from empty buffer"));
544 }
545
546 #[test]
547 fn test_decode_buffer_too_short() {
548 let result = DecimalLexEncoder::decode(&[0xA0]);
549 assert!(result.is_err());
550 let err = result.unwrap_err();
551 assert!(err.to_string().contains("Buffer too short"), "{err:?}");
552 }
553
554 #[test]
555 fn test_decode_invalid_marker() {
556 let result = DecimalLexEncoder::decode(&[0x42, 0x00, 0x00, 0x00]);
557 assert!(result.is_err());
558 let err = result.unwrap_err();
559 assert_eq!(err.to_string(), "Serialization error: Invalid marker byte: 66", "{err:?}");
560 }
561
562 #[test]
563 fn test_decode_empty_mantissa() {
564 // Create a buffer that starts correctly but is truncated during mantissa
565 // decoding
566 let result = DecimalLexEncoder::decode(&[0xA0, 0x01, 0x01, 0x00]); // Missing mantissa data
567 assert!(result.is_err());
568 let err = result.unwrap_err();
569 assert!(err.to_string().contains("Empty mantissa"), "{err:?}");
570 }
571
572 #[test]
573 fn to_decimal_exact_is_the_inverse_of_to_d128() {
574 for text in [
575 "0",
576 "1",
577 "-1",
578 "1.50",
579 "100.000",
580 "0.0000001",
581 "-0.00000000000000000000000001",
582 "7.9228162514264337593543950335",
583 "79228162514264337593543950335",
584 "-79228162514264337593543950335",
585 ] {
586 let d: Decimal = text.parse().unwrap();
587 let back = DecimalLexEncoder::to_decimal_exact(DecimalLexEncoder::to_d128(d)).unwrap();
588 assert_eq!(back, d, "{text}");
589 assert_eq!(back.scale(), d.scale(), "{text} lost its scale");
590 }
591 }
592
593 #[test]
594 fn to_decimal_exact_reads_any_spelling_of_a_representable_value() {
595 let cases: [(&str, &str); 5] = [
596 // A positive exponent is trailing zeros.
597 ("1E+3", "1000"),
598 ("12E+27", "12000000000000000000000000000"),
599 // Trailing zeros past the 28th place are not significant.
600 ("1200E-30", "0.0000000000000000000000000012"),
601 ("5E-7", "0.0000005"),
602 ("-0E+10", "0"),
603 ];
604 for (d128, decimal) in cases {
605 let d128: D128 = d128.parse().unwrap();
606 let decimal: Decimal = decimal.parse().unwrap();
607 assert_eq!(DecimalLexEncoder::to_decimal_exact(d128).unwrap(), decimal, "{d128}");
608 }
609 }
610
611 #[test]
612 fn to_decimal_exact_refuses_what_a_decimal_cannot_hold() {
613 for text in [
614 // Past the 96-bit mantissa.
615 "1E+29",
616 "79228162514264337593543950336",
617 "9999999999999999999999999999999999",
618 "1E+6144",
619 // A significant digit past the 28th fractional place.
620 "1E-29",
621 "123E-30",
622 "1E-6143",
623 ] {
624 let d128: D128 = text.parse().unwrap();
625 assert!(DecimalLexEncoder::to_decimal_exact(d128).is_err(), "{text} is not a Decimal");
626 }
627 }
628}