Skip to main content

rustev_contract/
decimal.rs

1//! Fixed-point decimal with nine fractional digits.
2//!
3//! A [`Decimal`] is an `i128` count of units of `10^-9`. It is exact: parsing
4//! never rounds, and every arithmetic operation either returns an exact or
5//! explicitly rounded result or a typed [`ArithError`]. Nothing saturates,
6//! wraps or clamps.
7
8use std::fmt;
9
10use serde::de::{self, Visitor};
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12
13/// Number of fractional digits a [`Decimal`] carries.
14pub const SCALE: u32 = 9;
15
16/// `10^SCALE`.
17const ONE_UNITS: i128 = 1_000_000_000;
18
19/// An exact decimal value `units / 10^9`.
20#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
21pub struct Decimal {
22    units: i128,
23}
24
25/// Rounding applied when a result has more than [`SCALE`] fractional digits,
26/// or when [`Decimal::round_to_scale`] drops digits.
27#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
28pub enum Rounding {
29    /// Round to nearest; an exact tie goes to the even last digit.
30    HalfEven,
31    /// Discard the excess digits (truncate toward zero).
32    TowardZero,
33}
34
35/// Why a decimal string was refused.
36#[derive(Clone, Copy, PartialEq, Eq, Debug)]
37pub enum DecimalError {
38    /// The input was empty.
39    Empty,
40    /// The input does not match `-?(0|[1-9][0-9]*)(\.[0-9]{1,9})?`; `offset`
41    /// is the byte where matching failed.
42    Syntax { offset: usize },
43    /// More than nine fractional digits. Refused, never rounded.
44    TooPrecise,
45    /// The value does not fit in the `i128` unit range.
46    OutOfRange,
47}
48
49impl fmt::Display for DecimalError {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        match self {
52            DecimalError::Empty => write!(f, "empty decimal"),
53            DecimalError::Syntax { offset } => {
54                write!(f, "malformed decimal at byte {offset}")
55            }
56            DecimalError::TooPrecise => {
57                write!(f, "decimal has more than {SCALE} fractional digits")
58            }
59            DecimalError::OutOfRange => write!(f, "decimal out of range"),
60        }
61    }
62}
63
64impl std::error::Error for DecimalError {}
65
66/// Why an arithmetic operation produced no value.
67#[derive(Clone, Copy, PartialEq, Eq, Debug)]
68pub enum ArithError {
69    /// The result, or a declared intermediate, does not fit in `i128` units.
70    Overflow,
71    /// The divisor was zero.
72    DivisionByZero,
73    /// A requested scale was greater than [`SCALE`].
74    InvalidScale,
75}
76
77impl fmt::Display for ArithError {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        match self {
80            ArithError::Overflow => write!(f, "decimal arithmetic overflow"),
81            ArithError::DivisionByZero => write!(f, "decimal division by zero"),
82            ArithError::InvalidScale => write!(f, "scale exceeds {SCALE}"),
83        }
84    }
85}
86
87impl std::error::Error for ArithError {}
88
89/// `n / d` with the given rounding. `d` must be non-zero.
90fn div_round(n: i128, d: i128, rounding: Rounding) -> Result<i128, ArithError> {
91    let q = n.checked_div(d).ok_or(ArithError::Overflow)?;
92    let rem = n.checked_rem(d).ok_or(ArithError::Overflow)?;
93    if rem == 0 || rounding == Rounding::TowardZero {
94        return Ok(q);
95    }
96    // |rem| < |d| <= 2^127, so 2 * |rem| < 2^128 fits in u128.
97    let twice_rem = rem.unsigned_abs() * 2;
98    let abs_d = d.unsigned_abs();
99    let away = twice_rem > abs_d || (twice_rem == abs_d && q % 2 != 0);
100    if !away {
101        return Ok(q);
102    }
103    if (n < 0) != (d < 0) {
104        q.checked_sub(1).ok_or(ArithError::Overflow)
105    } else {
106        q.checked_add(1).ok_or(ArithError::Overflow)
107    }
108}
109
110impl Decimal {
111    /// Zero.
112    pub const ZERO: Decimal = Decimal { units: 0 };
113
114    /// The decimal `units / 10^9`.
115    pub const fn from_units(units: i128) -> Decimal {
116        Decimal { units }
117    }
118
119    /// The raw unit count (`value * 10^9`).
120    pub const fn units(&self) -> i128 {
121        self.units
122    }
123
124    /// The integer `v`. Always fits: `|i64| * 10^9 < 2^127`.
125    pub const fn from_i64(v: i64) -> Decimal {
126        Decimal {
127            units: v as i128 * ONE_UNITS,
128        }
129    }
130
131    /// True when the value is strictly below zero.
132    pub const fn is_negative(&self) -> bool {
133        self.units < 0
134    }
135
136    /// Parse `-?(0|[1-9][0-9]*)(\.[0-9]{1,9})?` exactly.
137    ///
138    /// No whitespace, `+`, exponent, leading zero, bare or trailing `.` is
139    /// accepted. Trailing fractional zeros are accepted (`1.50` equals `1.5`),
140    /// and `-0` equals zero. More than nine fractional digits is
141    /// [`DecimalError::TooPrecise`]; a value outside the `i128` unit range is
142    /// [`DecimalError::OutOfRange`].
143    pub fn parse(s: &str) -> Result<Decimal, DecimalError> {
144        let b = s.as_bytes();
145        if b.is_empty() {
146            return Err(DecimalError::Empty);
147        }
148        let mut i = 0;
149        let negative = b[0] == b'-';
150        if negative {
151            i = 1;
152        }
153        let int_start = i;
154        while i < b.len() && b[i].is_ascii_digit() {
155            i += 1;
156        }
157        let int_digits = &b[int_start..i];
158        if int_digits.is_empty() {
159            return Err(DecimalError::Syntax { offset: int_start });
160        }
161        if int_digits.len() > 1 && int_digits[0] == b'0' {
162            return Err(DecimalError::Syntax {
163                offset: int_start + 1,
164            });
165        }
166        let mut frac_digits: &[u8] = &[];
167        if i < b.len() {
168            if b[i] != b'.' {
169                return Err(DecimalError::Syntax { offset: i });
170            }
171            i += 1;
172            let frac_start = i;
173            while i < b.len() && b[i].is_ascii_digit() {
174                i += 1;
175            }
176            frac_digits = &b[frac_start..i];
177            if frac_digits.is_empty() {
178                return Err(DecimalError::Syntax { offset: frac_start });
179            }
180            if i < b.len() {
181                return Err(DecimalError::Syntax { offset: i });
182            }
183            if frac_digits.len() > SCALE as usize {
184                return Err(DecimalError::TooPrecise);
185            }
186        }
187
188        // Magnitude in u128 so that i128::MIN's magnitude (2^127) is reachable.
189        let mut mag: u128 = 0;
190        for &d in int_digits {
191            mag = mag
192                .checked_mul(10)
193                .and_then(|m| m.checked_add(u128::from(d - b'0')))
194                .ok_or(DecimalError::OutOfRange)?;
195        }
196        mag = mag
197            .checked_mul(ONE_UNITS as u128)
198            .ok_or(DecimalError::OutOfRange)?;
199        let mut frac: u128 = 0;
200        for &d in frac_digits {
201            frac = frac * 10 + u128::from(d - b'0');
202        }
203        frac *= 10u128.pow(SCALE - frac_digits.len() as u32);
204        mag = mag.checked_add(frac).ok_or(DecimalError::OutOfRange)?;
205
206        let limit = i128::MAX as u128;
207        let units = if negative {
208            if mag == limit + 1 {
209                i128::MIN
210            } else if mag > limit {
211                return Err(DecimalError::OutOfRange);
212            } else {
213                -(mag as i128)
214            }
215        } else if mag > limit {
216            return Err(DecimalError::OutOfRange);
217        } else {
218            mag as i128
219        };
220        Ok(Decimal { units })
221    }
222
223    /// The canonical form: no trailing fractional zeros, no `.` when
224    /// integral, never `-0`. [`Decimal::parse`] inverts it exactly.
225    pub fn to_canonical_string(&self) -> String {
226        let mag = self.units.unsigned_abs();
227        let int = mag / ONE_UNITS as u128;
228        let frac = mag % ONE_UNITS as u128;
229        let sign = if self.units < 0 { "-" } else { "" };
230        if frac == 0 {
231            format!("{sign}{int}")
232        } else {
233            let digits = format!("{frac:09}");
234            format!("{sign}{int}.{}", digits.trim_end_matches('0'))
235        }
236    }
237
238    /// Exact sum, or [`ArithError::Overflow`].
239    pub fn checked_add(self, rhs: Decimal) -> Result<Decimal, ArithError> {
240        self.units
241            .checked_add(rhs.units)
242            .map(Decimal::from_units)
243            .ok_or(ArithError::Overflow)
244    }
245
246    /// Exact difference, or [`ArithError::Overflow`].
247    pub fn checked_sub(self, rhs: Decimal) -> Result<Decimal, ArithError> {
248        self.units
249            .checked_sub(rhs.units)
250            .map(Decimal::from_units)
251            .ok_or(ArithError::Overflow)
252    }
253
254    /// Product rounded to nine fractional digits.
255    ///
256    /// Declared bound: the exact intermediate `units_a * units_b` must fit in
257    /// `i128`; otherwise [`ArithError::Overflow`], even if the rounded result
258    /// would fit.
259    pub fn checked_mul(self, rhs: Decimal, rounding: Rounding) -> Result<Decimal, ArithError> {
260        let p = self
261            .units
262            .checked_mul(rhs.units)
263            .ok_or(ArithError::Overflow)?;
264        div_round(p, ONE_UNITS, rounding).map(Decimal::from_units)
265    }
266
267    /// Quotient rounded to nine fractional digits.
268    ///
269    /// Declared bound: the intermediate `units_a * 10^9` must fit in `i128`;
270    /// otherwise [`ArithError::Overflow`]. A zero divisor is
271    /// [`ArithError::DivisionByZero`].
272    pub fn checked_div(self, rhs: Decimal, rounding: Rounding) -> Result<Decimal, ArithError> {
273        if rhs.units == 0 {
274            return Err(ArithError::DivisionByZero);
275        }
276        let n = self
277            .units
278            .checked_mul(ONE_UNITS)
279            .ok_or(ArithError::Overflow)?;
280        div_round(n, rhs.units, rounding).map(Decimal::from_units)
281    }
282
283    /// Round to `scale` fractional digits (`0..=9`); a larger scale is
284    /// [`ArithError::InvalidScale`]. Rounding away from zero at the edge of
285    /// the range is [`ArithError::Overflow`].
286    pub fn round_to_scale(self, scale: u32, rounding: Rounding) -> Result<Decimal, ArithError> {
287        if scale > SCALE {
288            return Err(ArithError::InvalidScale);
289        }
290        let factor = 10i128.pow(SCALE - scale);
291        let q = div_round(self.units, factor, rounding)?;
292        q.checked_mul(factor)
293            .map(Decimal::from_units)
294            .ok_or(ArithError::Overflow)
295    }
296
297    /// The `f64` nearest to the exact value (correctly rounded by parsing the
298    /// canonical string). Lossy by nature; never used for exact computation.
299    pub fn to_f64_nearest(&self) -> f64 {
300        self.to_canonical_string()
301            .parse::<f64>()
302            .expect("canonical decimal is valid f64 syntax")
303    }
304}
305
306impl fmt::Display for Decimal {
307    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308        f.write_str(&self.to_canonical_string())
309    }
310}
311
312impl Serialize for Decimal {
313    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
314        serializer.serialize_str(&self.to_canonical_string())
315    }
316}
317
318impl schemars::JsonSchema for Decimal {
319    fn schema_name() -> std::borrow::Cow<'static, str> {
320        "Decimal".into()
321    }
322
323    /// A decimal string; the parser alone judges canonical form and bounds.
324    fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
325        schemars::json_schema!({
326            "type": "string",
327            "pattern": "^-?(0|[1-9][0-9]*)(\\.[0-9]+)?$"
328        })
329    }
330}
331
332struct DecimalVisitor;
333
334impl Visitor<'_> for DecimalVisitor {
335    type Value = Decimal;
336
337    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338        f.write_str("a decimal string")
339    }
340
341    fn visit_str<E: de::Error>(self, v: &str) -> Result<Decimal, E> {
342        Decimal::parse(v).map_err(E::custom)
343    }
344}
345
346impl<'de> Deserialize<'de> for Decimal {
347    /// Accepts a JSON string only; a JSON number is refused.
348    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Decimal, D::Error> {
349        deserializer.deserialize_str(DecimalVisitor)
350    }
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    fn d(s: &str) -> Decimal {
358        Decimal::parse(s).unwrap()
359    }
360
361    #[test]
362    fn grammar_accepts() {
363        let cases = [
364            ("0", 0),
365            ("-0", 0),
366            ("-0.0", 0),
367            ("1", ONE_UNITS),
368            ("1.5", 1_500_000_000),
369            ("1.50", 1_500_000_000),
370            ("-1.5", -1_500_000_000),
371            ("0.000000001", 1),
372            ("-0.000000001", -1),
373            ("10.000000000", 10 * ONE_UNITS),
374            ("123456789", 123_456_789 * ONE_UNITS),
375        ];
376        for (s, units) in cases {
377            assert_eq!(Decimal::parse(s), Ok(Decimal::from_units(units)), "{s}");
378        }
379    }
380
381    #[test]
382    fn grammar_refuses() {
383        use DecimalError::*;
384        let cases: [(&str, DecimalError); 16] = [
385            ("", Empty),
386            ("+1", Syntax { offset: 0 }),
387            ("-", Syntax { offset: 1 }),
388            ("01", Syntax { offset: 1 }),
389            ("-01", Syntax { offset: 2 }),
390            ("00", Syntax { offset: 1 }),
391            (".", Syntax { offset: 0 }),
392            (".5", Syntax { offset: 0 }),
393            ("1.", Syntax { offset: 2 }),
394            ("1e3", Syntax { offset: 1 }),
395            ("1.0e3", Syntax { offset: 3 }),
396            (" 1", Syntax { offset: 0 }),
397            ("1 ", Syntax { offset: 1 }),
398            ("1.2.3", Syntax { offset: 3 }),
399            ("0.0000000001", TooPrecise),
400            ("1.0000000000", TooPrecise),
401        ];
402        for (s, e) in cases {
403            assert_eq!(Decimal::parse(s), Err(e), "{s:?}");
404        }
405        assert_eq!(Decimal::parse("--1"), Err(Syntax { offset: 1 }));
406        assert_eq!(Decimal::parse("NaN"), Err(Syntax { offset: 0 }));
407        assert_eq!(Decimal::parse("١"), Err(Syntax { offset: 0 }));
408    }
409
410    #[test]
411    fn range_boundaries() {
412        let max = Decimal::from_units(i128::MAX);
413        let min = Decimal::from_units(i128::MIN);
414        assert_eq!(
415            max.to_canonical_string(),
416            "170141183460469231731687303715.884105727"
417        );
418        assert_eq!(
419            min.to_canonical_string(),
420            "-170141183460469231731687303715.884105728"
421        );
422        assert_eq!(d("170141183460469231731687303715.884105727"), max);
423        assert_eq!(d("-170141183460469231731687303715.884105728"), min);
424        assert_eq!(
425            Decimal::parse("170141183460469231731687303715.884105728"),
426            Err(DecimalError::OutOfRange)
427        );
428        assert_eq!(
429            Decimal::parse("-170141183460469231731687303715.884105729"),
430            Err(DecimalError::OutOfRange)
431        );
432        assert_eq!(
433            Decimal::parse("999999999999999999999999999999999999999999"),
434            Err(DecimalError::OutOfRange)
435        );
436    }
437
438    #[test]
439    fn canonical_round_trip() {
440        let units = [
441            0,
442            1,
443            -1,
444            ONE_UNITS,
445            -ONE_UNITS,
446            1_500_000_000,
447            -1_050_000_000,
448            123_456_789_012,
449            i128::MAX,
450            i128::MIN,
451            i128::MAX - 1,
452            i128::MIN + 1,
453        ];
454        for u in units {
455            let x = Decimal::from_units(u);
456            let s = x.to_canonical_string();
457            assert_eq!(d(&s), x, "{s}");
458            assert_eq!(x.to_string(), s);
459            assert!(!s.ends_with('.'));
460            assert!(!s.contains('.') || !s.ends_with('0'), "{s}");
461        }
462        assert_eq!(d("-0").to_canonical_string(), "0");
463        assert_eq!(d("1.50").to_canonical_string(), "1.5");
464        assert_eq!(d("2.000").to_canonical_string(), "2");
465        assert_eq!(d("-0.05").to_canonical_string(), "-0.05");
466    }
467
468    #[test]
469    fn from_i64_and_sign() {
470        assert_eq!(
471            Decimal::from_i64(i64::MIN).units(),
472            i64::MIN as i128 * ONE_UNITS
473        );
474        assert_eq!(Decimal::from_i64(-3), d("-3"));
475        assert!(d("-0.1").is_negative());
476        assert!(!Decimal::ZERO.is_negative());
477        assert!(!d("-0").is_negative());
478    }
479
480    #[test]
481    fn add_sub_exact_and_overflow() {
482        assert_eq!(d("0.1").checked_add(d("0.2")), Ok(d("0.3")));
483        assert_eq!(d("0.1").checked_sub(d("0.3")), Ok(d("-0.2")));
484        let max = Decimal::from_units(i128::MAX);
485        let min = Decimal::from_units(i128::MIN);
486        assert_eq!(
487            max.checked_add(Decimal::from_units(1)),
488            Err(ArithError::Overflow)
489        );
490        assert_eq!(
491            min.checked_sub(Decimal::from_units(1)),
492            Err(ArithError::Overflow)
493        );
494        assert_eq!(max.checked_add(Decimal::ZERO), Ok(max));
495    }
496
497    #[test]
498    fn mul_rounding() {
499        use Rounding::*;
500        assert_eq!(d("1.5").checked_mul(d("2"), HalfEven), Ok(d("3")));
501        assert_eq!(d("-1.5").checked_mul(d("2"), HalfEven), Ok(d("-3")));
502        // 0.000000025 * 0.1 = 0.0000000025: tie, 2 is even.
503        assert_eq!(
504            d("0.000000025").checked_mul(d("0.1"), HalfEven),
505            Ok(d("0.000000002"))
506        );
507        // 0.000000035 * 0.1 = 0.0000000035: tie, rounds to 4.
508        assert_eq!(
509            d("0.000000035").checked_mul(d("0.1"), HalfEven),
510            Ok(d("0.000000004"))
511        );
512        assert_eq!(
513            d("-0.000000025").checked_mul(d("0.1"), HalfEven),
514            Ok(d("-0.000000002"))
515        );
516        assert_eq!(
517            d("-0.000000035").checked_mul(d("0.1"), HalfEven),
518            Ok(d("-0.000000004"))
519        );
520        // Not a tie: 0.0000000026 rounds up.
521        assert_eq!(
522            d("0.000000026").checked_mul(d("0.1"), HalfEven),
523            Ok(d("0.000000003"))
524        );
525        assert_eq!(
526            d("0.000000029").checked_mul(d("0.1"), TowardZero),
527            Ok(d("0.000000002"))
528        );
529        assert_eq!(
530            d("-0.000000029").checked_mul(d("0.1"), TowardZero),
531            Ok(d("-0.000000002"))
532        );
533    }
534
535    #[test]
536    fn mul_overflow_is_declared_intermediate() {
537        let big = d("10000000000000");
538        // 10^13 * 10^13 = 10^26 fits, but the unit product 10^22 * 10^22 does not.
539        assert_eq!(
540            big.checked_mul(big, Rounding::HalfEven),
541            Err(ArithError::Overflow)
542        );
543        assert_eq!(
544            d("1000000").checked_mul(d("1000000"), Rounding::HalfEven),
545            Ok(d("1000000000000"))
546        );
547    }
548
549    #[test]
550    fn div_rounding_and_errors() {
551        use Rounding::*;
552        assert_eq!(d("1").checked_div(d("4"), HalfEven), Ok(d("0.25")));
553        assert_eq!(d("1").checked_div(d("3"), HalfEven), Ok(d("0.333333333")));
554        assert_eq!(d("2").checked_div(d("3"), HalfEven), Ok(d("0.666666667")));
555        assert_eq!(d("2").checked_div(d("3"), TowardZero), Ok(d("0.666666666")));
556        assert_eq!(d("-2").checked_div(d("3"), HalfEven), Ok(d("-0.666666667")));
557        assert_eq!(d("2").checked_div(d("-3"), HalfEven), Ok(d("-0.666666667")));
558        assert_eq!(d("-2").checked_div(d("-3"), HalfEven), Ok(d("0.666666667")));
559        // Exact tie at the ninth digit: 0.000000005 / 2 = 0.0000000025.
560        assert_eq!(
561            d("0.000000005").checked_div(d("2"), HalfEven),
562            Ok(d("0.000000002"))
563        );
564        assert_eq!(
565            d("-0.000000005").checked_div(d("2"), HalfEven),
566            Ok(d("-0.000000002"))
567        );
568        assert_eq!(
569            d("0.000000007").checked_div(d("2"), HalfEven),
570            Ok(d("0.000000004"))
571        );
572        assert_eq!(
573            d("1").checked_div(Decimal::ZERO, HalfEven),
574            Err(ArithError::DivisionByZero)
575        );
576        assert_eq!(
577            Decimal::from_units(i128::MAX).checked_div(d("1"), HalfEven),
578            Err(ArithError::Overflow)
579        );
580    }
581
582    #[test]
583    fn round_to_scale_cases() {
584        use Rounding::*;
585        assert_eq!(d("2.5").round_to_scale(0, HalfEven), Ok(d("2")));
586        assert_eq!(d("3.5").round_to_scale(0, HalfEven), Ok(d("4")));
587        assert_eq!(d("-2.5").round_to_scale(0, HalfEven), Ok(d("-2")));
588        assert_eq!(d("-3.5").round_to_scale(0, HalfEven), Ok(d("-4")));
589        assert_eq!(d("2.51").round_to_scale(0, HalfEven), Ok(d("3")));
590        assert_eq!(d("2.9").round_to_scale(0, TowardZero), Ok(d("2")));
591        assert_eq!(d("-2.9").round_to_scale(0, TowardZero), Ok(d("-2")));
592        assert_eq!(d("1.005").round_to_scale(2, HalfEven), Ok(d("1")));
593        assert_eq!(d("1.015").round_to_scale(2, HalfEven), Ok(d("1.02")));
594        assert_eq!(
595            d("1.123456789").round_to_scale(9, HalfEven),
596            Ok(d("1.123456789"))
597        );
598        assert_eq!(
599            d("1").round_to_scale(10, HalfEven),
600            Err(ArithError::InvalidScale)
601        );
602        assert_eq!(
603            Decimal::from_units(i128::MAX).round_to_scale(0, HalfEven),
604            Err(ArithError::Overflow)
605        );
606    }
607
608    #[test]
609    fn nearest_f64() {
610        assert_eq!(d("0.1").to_f64_nearest(), 0.1);
611        assert_eq!(d("-2.5").to_f64_nearest(), -2.5);
612        assert_eq!(Decimal::ZERO.to_f64_nearest(), 0.0);
613        assert!(Decimal::from_units(i128::MIN).to_f64_nearest().is_finite());
614    }
615
616    #[test]
617    fn serde_string_only() {
618        assert_eq!(serde_json::to_string(&d("1.50")).unwrap(), "\"1.5\"");
619        assert_eq!(
620            serde_json::from_str::<Decimal>("\"-0.25\"").unwrap(),
621            d("-0.25")
622        );
623        assert!(serde_json::from_str::<Decimal>("1.5").is_err());
624        assert!(serde_json::from_str::<Decimal>("1").is_err());
625        assert!(serde_json::from_str::<Decimal>("\"1e3\"").is_err());
626        assert!(serde_json::from_str::<Decimal>("\"0.0000000001\"").is_err());
627        assert!(serde_json::from_str::<Decimal>("null").is_err());
628    }
629}