1use std::cmp::Ordering;
30use std::fmt;
31use std::str::FromStr;
32
33use num_bigint::{BigInt, Sign};
34use num_integer::Integer;
35use num_rational::BigRational;
36use num_traits::{One, Signed, ToPrimitive, Zero};
37use serde::{Deserialize, Serialize};
38
39use crate::error::{EngineError, ErrorCode};
40use crate::limits::Limits;
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
44#[serde(rename_all = "snake_case")]
45pub enum NumericMode {
46 Exact,
49 Auto,
52 Scientific,
54}
55
56impl NumericMode {
57 pub fn as_str(self) -> &'static str {
58 match self {
59 NumericMode::Exact => "exact",
60 NumericMode::Auto => "auto",
61 NumericMode::Scientific => "scientific",
62 }
63 }
64}
65
66impl fmt::Display for NumericMode {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 f.write_str(self.as_str())
69 }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
74#[serde(rename_all = "snake_case")]
75pub enum RoundingMode {
76 HalfEven,
78 HalfAwayFromZero,
80 TowardZero,
82 Floor,
84 Ceiling,
86}
87
88impl RoundingMode {
89 pub fn as_str(self) -> &'static str {
90 match self {
91 RoundingMode::HalfEven => "half_even",
92 RoundingMode::HalfAwayFromZero => "half_away_from_zero",
93 RoundingMode::TowardZero => "toward_zero",
94 RoundingMode::Floor => "floor",
95 RoundingMode::Ceiling => "ceiling",
96 }
97 }
98}
99
100impl fmt::Display for RoundingMode {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.write_str(self.as_str())
103 }
104}
105
106pub fn div_round(numerator: &BigInt, denominator: &BigInt, mode: RoundingMode) -> BigInt {
111 debug_assert!(!denominator.is_zero());
112 let (q, r) = numerator.div_rem(denominator);
113 if r.is_zero() {
114 return q;
115 }
116 let negative = (numerator.sign() == Sign::Minus) ^ (denominator.sign() == Sign::Minus);
117 match mode {
118 RoundingMode::TowardZero => q,
119 RoundingMode::Floor => {
120 if negative {
121 q - 1
122 } else {
123 q
124 }
125 }
126 RoundingMode::Ceiling => {
127 if negative {
128 q
129 } else {
130 q + 1
131 }
132 }
133 RoundingMode::HalfAwayFromZero | RoundingMode::HalfEven => {
134 let twice_remainder = r.abs() * 2u32;
135 let divisor = denominator.abs();
136 match twice_remainder.cmp(&divisor) {
137 Ordering::Less => q,
138 Ordering::Greater => {
139 if negative {
140 q - 1
141 } else {
142 q + 1
143 }
144 }
145 Ordering::Equal => match mode {
146 RoundingMode::HalfAwayFromZero => {
147 if negative {
148 q - 1
149 } else {
150 q + 1
151 }
152 }
153 RoundingMode::HalfEven => {
154 if (&q % 2u32).is_zero() {
155 q
156 } else if negative {
157 q - 1
158 } else {
159 q + 1
160 }
161 }
162 _ => unreachable!("outer match restricts mode"),
163 },
164 }
165 }
166 }
167}
168
169fn pow10(n: u64) -> BigInt {
171 debug_assert!(n <= 10_000_000, "pow10 exponent out of supported range");
172 BigInt::from(10u32).pow(n as u32)
173}
174
175#[derive(Clone, Debug)]
180pub struct Decimal {
181 mantissa: BigInt,
182 scale: u32,
183}
184
185impl Decimal {
186 pub fn from_parts(mantissa: BigInt, scale: u32) -> Decimal {
187 Decimal { mantissa, scale }
188 }
189
190 pub fn zero() -> Decimal {
191 Decimal {
192 mantissa: BigInt::zero(),
193 scale: 0,
194 }
195 }
196
197 pub fn from_bigint(value: BigInt) -> Decimal {
198 Decimal {
199 mantissa: value,
200 scale: 0,
201 }
202 }
203
204 pub fn mantissa(&self) -> &BigInt {
205 &self.mantissa
206 }
207
208 pub fn scale(&self) -> u32 {
209 self.scale
210 }
211
212 pub fn parse(source: &str, limits: &Limits) -> Result<Decimal, EngineError> {
217 let (mantissa, scale) = parse_decimal_parts(source, limits)?;
218 Ok(Decimal { mantissa, scale })
219 }
220
221 pub fn parse_default(source: &str) -> Result<Decimal, EngineError> {
224 Self::parse(source, &Limits::conservative())
225 }
226
227 pub fn is_zero(&self) -> bool {
228 self.mantissa.is_zero()
229 }
230
231 pub fn is_negative(&self) -> bool {
232 self.mantissa.sign() == Sign::Minus
233 }
234
235 pub fn is_positive(&self) -> bool {
236 self.mantissa.sign() == Sign::Plus
237 }
238
239 pub fn sign(&self) -> Sign {
240 self.mantissa.sign()
241 }
242
243 pub fn neg(&self) -> Decimal {
244 Decimal {
245 mantissa: -&self.mantissa,
246 scale: self.scale,
247 }
248 }
249
250 pub fn abs(&self) -> Decimal {
251 Decimal {
252 mantissa: self.mantissa.abs(),
253 scale: self.scale,
254 }
255 }
256
257 pub fn normalized(&self) -> Decimal {
259 if self.mantissa.is_zero() {
260 return Decimal {
261 mantissa: BigInt::zero(),
262 scale: 0,
263 };
264 }
265 let mut mantissa = self.mantissa.clone();
266 let mut scale = self.scale;
267 while scale > 0 {
268 let (q, r) = mantissa.div_rem(&pow10(1));
269 if r.is_zero() {
270 mantissa = q;
271 scale -= 1;
272 } else {
273 break;
274 }
275 }
276 Decimal { mantissa, scale }
277 }
278
279 pub fn canonical_string(&self) -> String {
282 self.normalized().to_plain_string()
283 }
284
285 pub fn to_plain_string(&self) -> String {
287 let negative = self.mantissa.sign() == Sign::Minus;
288 let digits = self.mantissa.abs().to_string();
289 let scale = self.scale as usize;
290 let mut out = String::new();
291 if negative {
292 out.push('-');
293 }
294 if scale == 0 {
295 out.push_str(&digits);
296 } else if digits.len() > scale {
297 let split = digits.len() - scale;
298 out.push_str(&digits[..split]);
299 out.push('.');
300 out.push_str(&digits[split..]);
301 } else {
302 out.push_str("0.");
303 for _ in 0..(scale - digits.len()) {
304 out.push('0');
305 }
306 out.push_str(&digits);
307 }
308 out
309 }
310
311 pub fn to_bigint_if_integral(&self) -> Option<BigInt> {
312 let divisor = pow10(self.scale as u64);
313 let (q, r) = self.mantissa.div_rem(&divisor);
314 if r.is_zero() { Some(q) } else { None }
315 }
316
317 pub fn to_rational(&self) -> BigRational {
318 BigRational::new(self.mantissa.clone(), pow10(self.scale as u64))
319 }
320
321 pub fn to_f64(&self) -> Option<f64> {
323 let v: f64 = self.to_plain_string().parse().ok()?;
324 if v.is_finite() { Some(v) } else { None }
325 }
326
327 pub fn from_f64_display(value: f64) -> Option<Decimal> {
330 if !value.is_finite() {
331 return None;
332 }
333 let text = format!("{value}");
334 Decimal::parse(&text, &Limits::conservative()).ok()
335 }
336
337 pub fn from_f64_exact(value: f64) -> Option<BigRational> {
339 float_to_rational(value)
340 }
341
342 pub fn numeric_cmp(&self, other: &Decimal) -> Ordering {
344 let scale = self.scale.max(other.scale);
345 let lhs = &self.mantissa * pow10((scale - self.scale) as u64);
346 let rhs = &other.mantissa * pow10((scale - other.scale) as u64);
347 lhs.cmp(&rhs)
348 }
349
350 fn check_bits(&self, limits: &Limits, ctx: &NumericContext) -> Result<(), EngineError> {
351 let bits = self.mantissa.bits();
352 let cap = limits.max_integer_bits.min(ctx.max_integer_bits) as u64;
353 if bits > cap {
354 return Err(EngineError::new(
355 ErrorCode::ResourceLimit,
356 format!(
357 "decimal result needs {bits} bits of mantissa, exceeding the limit of {cap}"
358 ),
359 ));
360 }
361 if self.scale > limits.max_decimal_scale.min(ctx.max_decimal_scale) {
362 return Err(EngineError::new(
363 ErrorCode::PrecisionLimit,
364 format!(
365 "decimal scale {} exceeds the limit of {}",
366 self.scale,
367 limits.max_decimal_scale.min(ctx.max_decimal_scale)
368 ),
369 ));
370 }
371 Ok(())
372 }
373
374 pub fn add(
375 &self,
376 other: &Decimal,
377 ctx: &NumericContext,
378 limits: &Limits,
379 ) -> Result<Decimal, EngineError> {
380 let scale = self.scale.max(other.scale);
381 let lhs = &self.mantissa * pow10((scale - self.scale) as u64);
382 let rhs = &other.mantissa * pow10((scale - other.scale) as u64);
383 let out = Decimal {
384 mantissa: lhs + rhs,
385 scale,
386 };
387 out.check_bits(limits, ctx)?;
388 Ok(out)
389 }
390
391 pub fn sub(
392 &self,
393 other: &Decimal,
394 ctx: &NumericContext,
395 limits: &Limits,
396 ) -> Result<Decimal, EngineError> {
397 self.add(&other.neg(), ctx, limits)
398 }
399
400 pub fn mul(
401 &self,
402 other: &Decimal,
403 ctx: &NumericContext,
404 limits: &Limits,
405 ) -> Result<Decimal, EngineError> {
406 let out = Decimal {
407 mantissa: &self.mantissa * &other.mantissa,
408 scale: self.scale.saturating_add(other.scale),
409 };
410 out.check_bits(limits, ctx)?;
411 Ok(out)
412 }
413
414 pub fn div(
420 &self,
421 other: &Decimal,
422 ctx: &NumericContext,
423 limits: &Limits,
424 ) -> Result<(Decimal, bool), EngineError> {
425 if other.is_zero() {
426 return Err(EngineError::division_by_zero("decimal division by zero"));
427 }
428 let precision = ctx.precision.max(1) as u64;
429 let q_scale = precision + other.scale.saturating_sub(self.scale) as u64;
430 let shift = q_scale + other.scale as u64 - self.scale as u64;
431 let numerator = &self.mantissa * pow10(shift);
432 let (q, r) = numerator.div_rem(&other.mantissa);
433 let raw = Decimal {
434 mantissa: q,
435 scale: q_scale as u32,
436 };
437 if r.is_zero() {
438 let value = raw.normalized();
439 value.check_bits(limits, ctx)?;
440 return Ok((value, false));
441 }
442 if ctx.mode == NumericMode::Exact {
443 return Err(EngineError::new(
444 ErrorCode::UnsupportedNumericMode,
445 "division does not terminate in the selected representation; \
446 use auto or scientific mode, or an explicit rounding context",
447 ));
448 }
449 let rounded = raw.round_significant(ctx.precision.max(1), ctx.rounding);
450 rounded.check_bits(limits, ctx)?;
451 Ok((rounded, true))
452 }
453
454 pub fn round_to(&self, scale: u32, mode: RoundingMode) -> Decimal {
456 self.round_to_scale(scale as i64, mode)
457 }
458
459 pub fn round_significant(&self, digits: u32, mode: RoundingMode) -> Decimal {
461 if self.mantissa.is_zero() {
462 return self.clone();
463 }
464 let nd = self.mantissa.abs().to_string().len() as i64;
465 let target = self.scale as i64 - (nd - digits as i64);
466 self.round_to_scale(target, mode)
467 }
468
469 pub fn round_to_scale(&self, target_scale: i64, mode: RoundingMode) -> Decimal {
471 if target_scale >= self.scale as i64 {
472 let shift = target_scale as u64 - self.scale as u64;
473 return Decimal {
474 mantissa: &self.mantissa * pow10(shift),
475 scale: target_scale as u32,
476 };
477 }
478 let k = self.scale as u64 - target_scale as u64;
479 let divisor = pow10(k);
480 let q = div_round(&self.mantissa, &divisor, mode);
481 if target_scale >= 0 {
482 Decimal {
483 mantissa: q,
484 scale: target_scale as u32,
485 }
486 } else {
487 Decimal {
488 mantissa: q * pow10((-target_scale) as u64),
489 scale: 0,
490 }
491 }
492 }
493
494 pub fn floor(&self) -> Decimal {
495 self.round_to_scale(0, RoundingMode::Floor)
496 }
497
498 pub fn ceil(&self) -> Decimal {
499 self.round_to_scale(0, RoundingMode::Ceiling)
500 }
501
502 pub fn trunc(&self) -> Decimal {
503 self.round_to_scale(0, RoundingMode::TowardZero)
504 }
505
506 pub fn sqrt_exact(&self) -> Option<Decimal> {
508 if self.mantissa.sign() == Sign::Minus {
509 return None;
510 }
511 if self.mantissa.is_zero() {
512 return Some(Decimal::zero());
513 }
514 let (mantissa, scale) = if self.scale.is_multiple_of(2) {
515 (self.mantissa.clone(), self.scale)
516 } else {
517 (&self.mantissa * 10u32, self.scale + 1)
518 };
519 let root = mantissa.sqrt();
520 if &root * &root == mantissa {
521 Some(Decimal {
522 mantissa: root,
523 scale: scale / 2,
524 })
525 } else {
526 None
527 }
528 }
529
530 pub fn pow_u32(
532 &self,
533 exponent: u32,
534 ctx: &NumericContext,
535 limits: &Limits,
536 ) -> Result<Decimal, EngineError> {
537 let predicted = self.mantissa.bits().saturating_mul(exponent as u64);
538 let cap = limits.max_integer_bits.min(ctx.max_integer_bits) as u64;
539 if predicted > cap {
540 return Err(EngineError::new(
541 ErrorCode::ResourceLimit,
542 format!("power would need about {predicted} bits, exceeding the limit of {cap}"),
543 ));
544 }
545 let out = Decimal {
546 mantissa: self.mantissa.pow(exponent),
547 scale: self.scale.saturating_mul(exponent),
548 };
549 out.check_bits(limits, ctx)?;
550 Ok(out)
551 }
552
553 pub fn from_rational(
555 value: &BigRational,
556 ctx: &NumericContext,
557 limits: &Limits,
558 ) -> Result<(Decimal, bool), EngineError> {
559 let numer = value.numer();
560 let denom = value.denom();
561 let mut d = denom.clone();
563 let two = BigInt::from(2u32);
564 let five = BigInt::from(5u32);
565 let mut twos = 0u32;
566 let mut fives = 0u32;
567 while d.is_even() {
568 d /= &two;
569 twos += 1;
570 }
571 while (&d % &five).is_zero() {
572 d /= &five;
573 fives += 1;
574 }
575 if d.is_one() {
576 let scale = twos.max(fives);
577 let mantissa = numer * pow10(scale as u64) / denom;
578 let out = Decimal { mantissa, scale };
579 out.check_bits(limits, ctx)?;
580 return Ok((out, false));
581 }
582 if ctx.mode == NumericMode::Exact {
583 return Err(EngineError::new(
584 ErrorCode::UnsupportedNumericMode,
585 "rational value has a non-terminating decimal expansion; \
586 use auto or scientific mode, or request a rational result",
587 ));
588 }
589 let precision = ctx.precision.max(1) as u64;
590 let shift = precision + 4;
592 let scaled = numer * pow10(shift);
593 let (q, _r) = scaled.div_rem(denom);
594 let raw = Decimal {
595 mantissa: q,
596 scale: shift as u32,
597 };
598 let rounded = raw.round_significant(ctx.precision.max(1), ctx.rounding);
599 rounded.check_bits(limits, ctx)?;
600 Ok((rounded, true))
601 }
602}
603
604impl PartialEq for Decimal {
605 fn eq(&self, other: &Self) -> bool {
606 self.numeric_cmp(other) == Ordering::Equal
607 }
608}
609
610impl Eq for Decimal {}
611
612impl PartialOrd for Decimal {
613 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
614 Some(self.cmp(other))
615 }
616}
617
618impl Ord for Decimal {
619 fn cmp(&self, other: &Self) -> Ordering {
620 self.numeric_cmp(other)
621 }
622}
623
624impl fmt::Display for Decimal {
625 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
626 f.write_str(&self.to_plain_string())
627 }
628}
629
630fn float_to_rational(value: f64) -> Option<BigRational> {
632 if !value.is_finite() {
633 return None;
634 }
635 if value == 0.0 {
636 return Some(BigRational::zero());
637 }
638 let bits = value.to_bits();
639 let negative = (bits >> 63) & 1 == 1;
640 let exponent_bits = ((bits >> 52) & 0x7ff) as i32;
641 let fraction = bits & 0x000f_ffff_ffff_ffff;
642 let (mantissa, exponent) = if exponent_bits == 0 {
643 (fraction, -1074i32)
644 } else {
645 (fraction | (1u64 << 52), exponent_bits - 1075)
646 };
647 let mut numerator = BigInt::from(mantissa);
648 if negative {
649 numerator = -numerator;
650 }
651 let (numer, denom) = if exponent >= 0 {
652 (numerator << exponent as u32, BigInt::one())
653 } else {
654 (numerator, BigInt::one() << (-exponent) as u32)
655 };
656 Some(BigRational::new(numer, denom))
657}
658
659#[derive(Clone, Copy, Debug)]
662pub struct Float64(f64);
663
664impl Float64 {
665 pub fn new(value: f64) -> Result<Float64, EngineError> {
666 if value.is_finite() {
667 Ok(Float64(value))
668 } else {
669 Err(EngineError::new(
670 ErrorCode::DomainViolation,
671 format!("non-finite float64 value {value} is not a valid public result"),
672 ))
673 }
674 }
675
676 pub fn get(self) -> f64 {
677 self.0
678 }
679
680 pub fn canonical(self) -> Float64 {
682 if self.0 == 0.0 { Float64(0.0) } else { self }
683 }
684
685 pub fn to_rational_exact(self) -> Option<BigRational> {
686 float_to_rational(self.0)
687 }
688
689 pub fn parse(source: &str) -> Result<Float64, EngineError> {
690 let trimmed = source.trim();
691 if trimmed.is_empty() {
692 return Err(EngineError::malformed("empty float64 literal"));
693 }
694 let value: f64 = trimmed
695 .parse()
696 .map_err(|_| EngineError::malformed(format!("invalid float64 literal {source:?}")))?;
697 Float64::new(value)
698 }
699}
700
701impl PartialEq for Float64 {
702 fn eq(&self, other: &Self) -> bool {
703 self.0 == other.0
704 }
705}
706
707impl Eq for Float64 {}
708
709impl Ord for Float64 {
710 fn cmp(&self, other: &Self) -> Ordering {
711 self.0.total_cmp(&other.0)
712 }
713}
714
715impl PartialOrd for Float64 {
716 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
717 Some(self.cmp(other))
718 }
719}
720
721impl fmt::Display for Float64 {
722 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
723 write!(f, "{}", self.0)
725 }
726}
727
728#[derive(Clone, Debug, PartialEq, Eq)]
730pub enum Number {
731 Integer(BigInt),
732 Rational(BigRational),
733 Decimal(Decimal),
734 Float64(Float64),
735}
736
737#[derive(Clone, Debug)]
739pub struct NumberResult {
740 pub value: Number,
741 pub rounded: bool,
742}
743
744impl NumberResult {
745 pub fn exact(value: Number) -> NumberResult {
746 NumberResult {
747 value,
748 rounded: false,
749 }
750 }
751
752 pub fn rounded(value: Number) -> NumberResult {
753 NumberResult {
754 value,
755 rounded: true,
756 }
757 }
758}
759
760impl Number {
761 pub fn integer<T: Into<BigInt>>(value: T) -> Number {
762 Number::Integer(value.into())
763 }
764
765 pub fn decimal(value: Decimal) -> Number {
766 Number::Decimal(value)
767 }
768
769 pub fn rational(numer: BigInt, denom: BigInt) -> Result<Number, EngineError> {
770 if denom.is_zero() {
771 return Err(EngineError::division_by_zero(
772 "rational with zero denominator",
773 ));
774 }
775 Ok(Number::Rational(BigRational::new(numer, denom)))
776 }
777
778 pub fn float(value: f64) -> Result<Number, EngineError> {
779 Ok(Number::Float64(Float64::new(value)?))
780 }
781
782 pub fn is_zero(&self) -> bool {
783 match self {
784 Number::Integer(v) => v.is_zero(),
785 Number::Rational(v) => v.is_zero(),
786 Number::Decimal(v) => v.is_zero(),
787 Number::Float64(v) => v.0 == 0.0,
788 }
789 }
790
791 pub fn is_negative(&self) -> bool {
792 match self {
793 Number::Integer(v) => v.sign() == Sign::Minus,
794 Number::Rational(v) => v.is_negative(),
795 Number::Decimal(v) => v.is_negative(),
796 Number::Float64(v) => v.0 < 0.0,
797 }
798 }
799
800 pub fn is_float(&self) -> bool {
801 matches!(self, Number::Float64(_))
802 }
803
804 pub fn kind_name(&self) -> &'static str {
805 match self {
806 Number::Integer(_) => "integer",
807 Number::Rational(_) => "rational",
808 Number::Decimal(_) => "decimal",
809 Number::Float64(_) => "float64",
810 }
811 }
812
813 pub fn as_exact_rational(&self) -> Option<BigRational> {
816 match self {
817 Number::Integer(v) => Some(BigRational::from_integer(v.clone())),
818 Number::Rational(v) => Some(v.clone()),
819 Number::Decimal(v) => Some(v.to_rational()),
820 Number::Float64(_) => None,
821 }
822 }
823
824 pub fn to_exact_rational(&self) -> Option<BigRational> {
826 match self {
827 Number::Float64(v) => v.to_rational_exact(),
828 other => other.as_exact_rational(),
829 }
830 }
831
832 pub fn to_f64(&self) -> Option<f64> {
833 match self {
834 Number::Integer(v) => v.to_f64(),
835 Number::Rational(v) => v.to_f64(),
836 Number::Decimal(v) => v.to_f64(),
837 Number::Float64(v) => Some(v.0),
838 }
839 }
840
841 pub fn neg(&self) -> Number {
842 match self {
843 Number::Integer(v) => Number::Integer(-v),
844 Number::Rational(v) => Number::Rational(-v),
845 Number::Decimal(v) => Number::Decimal(v.neg()),
846 Number::Float64(v) => Number::Float64(Float64(-v.0)),
847 }
848 }
849
850 pub fn abs(&self) -> Number {
851 match self {
852 Number::Integer(v) => Number::Integer(v.abs()),
853 Number::Rational(v) => Number::Rational(v.abs()),
854 Number::Decimal(v) => Number::Decimal(v.abs()),
855 Number::Float64(v) => Number::Float64(Float64(v.0.abs())),
856 }
857 }
858
859 pub fn compare(&self, other: &Number) -> Result<Ordering, EngineError> {
861 if let (Number::Float64(a), Number::Float64(b)) = (self, other) {
862 return Ok(a.get().total_cmp(&b.get()));
863 }
864 let lhs = self
865 .to_exact_rational()
866 .ok_or_else(|| EngineError::internal("unrepresentable comparison operand"))?;
867 let rhs = other
868 .to_exact_rational()
869 .ok_or_else(|| EngineError::internal("unrepresentable comparison operand"))?;
870 Ok(lhs.cmp(&rhs))
871 }
872
873 pub fn numeric_eq(&self, other: &Number) -> Result<bool, EngineError> {
875 Ok(self.compare(other)? == Ordering::Equal)
876 }
877
878 fn require_float_allowed(
879 &self,
880 other: &Number,
881 ctx: &NumericContext,
882 ) -> Result<(), EngineError> {
883 if (self.is_float() || other.is_float()) && ctx.mode != NumericMode::Scientific {
884 return Err(EngineError::new(
885 ErrorCode::UnsupportedNumericMode,
886 format!(
887 "float64 arithmetic requires scientific mode; current mode is {}",
888 ctx.mode
889 ),
890 ));
891 }
892 Ok(())
893 }
894
895 pub fn add(
896 &self,
897 other: &Number,
898 ctx: &NumericContext,
899 limits: &Limits,
900 ) -> Result<NumberResult, EngineError> {
901 self.require_float_allowed(other, ctx)?;
902 if self.is_float() || other.is_float() {
903 let a = self.to_f64().unwrap_or(f64::NAN);
904 let b = other.to_f64().unwrap_or(f64::NAN);
905 return Ok(NumberResult::rounded(Number::float(a + b)?));
906 }
907 match (self, other) {
908 (Number::Integer(a), Number::Integer(b)) => {
909 let out = a + b;
910 check_bigint(&out, ctx, limits)?;
911 Ok(NumberResult::exact(Number::Integer(out)))
912 }
913 (Number::Rational(a), Number::Rational(b)) => {
914 let out = a + b;
915 check_rational(&out, ctx, limits)?;
916 Ok(NumberResult::exact(Number::Rational(out)))
917 }
918 (Number::Rational(a), Number::Integer(b)) => {
919 let out = a + BigRational::from_integer(b.clone());
920 check_rational(&out, ctx, limits)?;
921 Ok(NumberResult::exact(Number::Rational(out)))
922 }
923 (Number::Integer(a), Number::Rational(b)) => {
924 let out = BigRational::from_integer(a.clone()) + b;
925 check_rational(&out, ctx, limits)?;
926 Ok(NumberResult::exact(Number::Rational(out)))
927 }
928 (Number::Decimal(a), Number::Decimal(b)) => {
929 Ok(NumberResult::exact(Number::Decimal(a.add(b, ctx, limits)?)))
930 }
931 (Number::Decimal(a), Number::Integer(b)) => {
932 let b = Decimal::from_bigint(b.clone());
933 Ok(NumberResult::exact(Number::Decimal(
934 a.add(&b, ctx, limits)?,
935 )))
936 }
937 (Number::Integer(a), Number::Decimal(b)) => {
938 let a = Decimal::from_bigint(a.clone());
939 Ok(NumberResult::exact(Number::Decimal(a.add(b, ctx, limits)?)))
940 }
941 (Number::Decimal(a), Number::Rational(b)) => {
942 let (b, inexact) = Decimal::from_rational(b, ctx, limits)?;
943 Ok(NumberResult {
944 value: Number::Decimal(a.add(&b, ctx, limits)?),
945 rounded: inexact,
946 })
947 }
948 (Number::Rational(a), Number::Decimal(b)) => {
949 let (a, inexact) = Decimal::from_rational(a, ctx, limits)?;
950 Ok(NumberResult {
951 value: Number::Decimal(a.add(b, ctx, limits)?),
952 rounded: inexact,
953 })
954 }
955 (Number::Float64(_), _) | (_, Number::Float64(_)) => unreachable!("handled above"),
956 }
957 }
958
959 pub fn sub(
960 &self,
961 other: &Number,
962 ctx: &NumericContext,
963 limits: &Limits,
964 ) -> Result<NumberResult, EngineError> {
965 self.add(&other.neg(), ctx, limits)
966 }
967
968 pub fn mul(
969 &self,
970 other: &Number,
971 ctx: &NumericContext,
972 limits: &Limits,
973 ) -> Result<NumberResult, EngineError> {
974 self.require_float_allowed(other, ctx)?;
975 if self.is_float() || other.is_float() {
976 let a = self.to_f64().unwrap_or(f64::NAN);
977 let b = other.to_f64().unwrap_or(f64::NAN);
978 return Ok(NumberResult::rounded(Number::float(a * b)?));
979 }
980 match (self, other) {
981 (Number::Integer(a), Number::Integer(b)) => {
982 let out = a * b;
983 check_bigint(&out, ctx, limits)?;
984 Ok(NumberResult::exact(Number::Integer(out)))
985 }
986 (Number::Rational(a), Number::Rational(b)) => {
987 let out = a * b;
988 check_rational(&out, ctx, limits)?;
989 Ok(NumberResult::exact(Number::Rational(out)))
990 }
991 (Number::Rational(a), Number::Integer(b)) => {
992 let out = a * BigRational::from_integer(b.clone());
993 check_rational(&out, ctx, limits)?;
994 Ok(NumberResult::exact(Number::Rational(out)))
995 }
996 (Number::Integer(a), Number::Rational(b)) => {
997 let out = BigRational::from_integer(a.clone()) * b;
998 check_rational(&out, ctx, limits)?;
999 Ok(NumberResult::exact(Number::Rational(out)))
1000 }
1001 (Number::Decimal(a), Number::Decimal(b)) => {
1002 Ok(NumberResult::exact(Number::Decimal(a.mul(b, ctx, limits)?)))
1003 }
1004 (Number::Decimal(a), Number::Integer(b)) => {
1005 let b = Decimal::from_bigint(b.clone());
1006 Ok(NumberResult::exact(Number::Decimal(
1007 a.mul(&b, ctx, limits)?,
1008 )))
1009 }
1010 (Number::Integer(a), Number::Decimal(b)) => {
1011 let a = Decimal::from_bigint(a.clone());
1012 Ok(NumberResult::exact(Number::Decimal(a.mul(b, ctx, limits)?)))
1013 }
1014 (Number::Decimal(a), Number::Rational(b)) => {
1015 let (b, inexact) = Decimal::from_rational(b, ctx, limits)?;
1016 Ok(NumberResult {
1017 value: Number::Decimal(a.mul(&b, ctx, limits)?),
1018 rounded: inexact,
1019 })
1020 }
1021 (Number::Rational(a), Number::Decimal(b)) => {
1022 let (a, inexact) = Decimal::from_rational(a, ctx, limits)?;
1023 Ok(NumberResult {
1024 value: Number::Decimal(a.mul(b, ctx, limits)?),
1025 rounded: inexact,
1026 })
1027 }
1028 (Number::Float64(_), _) | (_, Number::Float64(_)) => unreachable!("handled above"),
1029 }
1030 }
1031
1032 pub fn div(
1033 &self,
1034 other: &Number,
1035 ctx: &NumericContext,
1036 limits: &Limits,
1037 ) -> Result<NumberResult, EngineError> {
1038 self.require_float_allowed(other, ctx)?;
1039 if other.is_zero() {
1040 return Err(EngineError::division_by_zero(format!(
1041 "division by zero ({} / {})",
1042 self.kind_name(),
1043 other.kind_name()
1044 )));
1045 }
1046 if self.is_float() || other.is_float() {
1047 let a = self.to_f64().unwrap_or(f64::NAN);
1048 let b = other.to_f64().unwrap_or(f64::NAN);
1049 return Ok(NumberResult::rounded(Number::float(a / b)?));
1050 }
1051 match (self, other) {
1052 (Number::Integer(a), Number::Integer(b)) => {
1053 let (q, r) = a.div_rem(b);
1054 if r.is_zero() {
1055 check_bigint(&q, ctx, limits)?;
1056 Ok(NumberResult::exact(Number::Integer(q)))
1057 } else {
1058 let out = BigRational::new(a.clone(), b.clone());
1059 Ok(NumberResult::exact(Number::Rational(out)))
1060 }
1061 }
1062 (Number::Rational(a), Number::Rational(b)) => {
1063 let out = a / b;
1064 check_rational(&out, ctx, limits)?;
1065 Ok(NumberResult::exact(Number::Rational(out)))
1066 }
1067 (Number::Rational(a), Number::Integer(b)) => {
1068 let out = a / BigRational::from_integer(b.clone());
1069 check_rational(&out, ctx, limits)?;
1070 Ok(NumberResult::exact(Number::Rational(out)))
1071 }
1072 (Number::Integer(a), Number::Rational(b)) => {
1073 let out = BigRational::from_integer(a.clone()) / b;
1074 check_rational(&out, ctx, limits)?;
1075 Ok(NumberResult::exact(Number::Rational(out)))
1076 }
1077 (Number::Decimal(a), Number::Decimal(b)) => {
1078 let (value, inexact) = a.div(b, ctx, limits)?;
1079 Ok(NumberResult {
1080 value: Number::Decimal(value),
1081 rounded: inexact,
1082 })
1083 }
1084 (Number::Decimal(a), Number::Integer(b)) => {
1085 let b = Decimal::from_bigint(b.clone());
1086 let (value, inexact) = a.div(&b, ctx, limits)?;
1087 Ok(NumberResult {
1088 value: Number::Decimal(value),
1089 rounded: inexact,
1090 })
1091 }
1092 (Number::Integer(a), Number::Decimal(b)) => {
1093 let a = Decimal::from_bigint(a.clone());
1094 let (value, inexact) = a.div(b, ctx, limits)?;
1095 Ok(NumberResult {
1096 value: Number::Decimal(value),
1097 rounded: inexact,
1098 })
1099 }
1100 (Number::Decimal(a), Number::Rational(b)) => {
1101 let (b, b_inexact) = Decimal::from_rational(b, ctx, limits)?;
1102 let (value, d_inexact) = a.div(&b, ctx, limits)?;
1103 Ok(NumberResult {
1104 value: Number::Decimal(value),
1105 rounded: b_inexact || d_inexact,
1106 })
1107 }
1108 (Number::Rational(a), Number::Decimal(b)) => {
1109 let (a, a_inexact) = Decimal::from_rational(a, ctx, limits)?;
1110 let (value, d_inexact) = a.div(b, ctx, limits)?;
1111 Ok(NumberResult {
1112 value: Number::Decimal(value),
1113 rounded: a_inexact || d_inexact,
1114 })
1115 }
1116 (Number::Float64(_), _) | (_, Number::Float64(_)) => unreachable!("handled above"),
1117 }
1118 }
1119
1120 pub fn pow(
1122 &self,
1123 exponent: &Number,
1124 ctx: &NumericContext,
1125 limits: &Limits,
1126 ) -> Result<NumberResult, EngineError> {
1127 if self.is_float() || exponent.is_float() {
1128 if ctx.mode != NumericMode::Scientific {
1129 return Err(EngineError::new(
1130 ErrorCode::UnsupportedNumericMode,
1131 "exponentiation involving float64 requires scientific mode",
1132 ));
1133 }
1134 let a = self.to_f64().unwrap_or(f64::NAN);
1135 let b = exponent.to_f64().unwrap_or(f64::NAN);
1136 return Ok(NumberResult::rounded(Number::float(a.powf(b))?));
1137 }
1138 let exp_int = match exponent {
1139 Number::Integer(v) => v.clone(),
1140 Number::Rational(v) if v.is_integer() => v.to_integer(),
1141 Number::Decimal(v) => match v.to_bigint_if_integral() {
1142 Some(i) => i,
1143 None => {
1144 if ctx.mode != NumericMode::Scientific {
1145 return Err(EngineError::new(
1146 ErrorCode::UnsupportedNumericMode,
1147 "non-integer exponent requires scientific mode",
1148 ));
1149 }
1150 let a = self.to_f64().unwrap_or(f64::NAN);
1151 let b = exponent.to_f64().unwrap_or(f64::NAN);
1152 return Ok(NumberResult::rounded(Number::float(a.powf(b))?));
1153 }
1154 },
1155 Number::Rational(_) => {
1156 if ctx.mode != NumericMode::Scientific {
1157 return Err(EngineError::new(
1158 ErrorCode::UnsupportedNumericMode,
1159 "non-integer exponent requires scientific mode",
1160 ));
1161 }
1162 let a = self.to_f64().unwrap_or(f64::NAN);
1163 let b = exponent.to_f64().unwrap_or(f64::NAN);
1164 return Ok(NumberResult::rounded(Number::float(a.powf(b))?));
1165 }
1166 Number::Float64(_) => unreachable!("handled above"),
1167 };
1168 let exp_u32 = match exp_int.to_u32() {
1169 Some(v) if v <= limits.max_exponent => v,
1170 _ => {
1171 return Err(EngineError::new(
1172 ErrorCode::ResourceLimit,
1173 format!(
1174 "exponent {} exceeds the supported range 0..={}",
1175 exp_int, limits.max_exponent
1176 ),
1177 ));
1178 }
1179 };
1180 let negative_exponent = exp_int.sign() == Sign::Minus;
1181 match self {
1182 Number::Integer(base) => {
1183 if negative_exponent {
1184 let power = base.pow(exp_u32);
1185 if power.is_zero() {
1186 return Err(EngineError::division_by_zero("zero to a negative power"));
1187 }
1188 let out = BigRational::new(BigInt::one(), power);
1189 check_rational(&out, ctx, limits)?;
1190 Ok(NumberResult::exact(Number::Rational(out)))
1191 } else {
1192 let predicted = base.bits().saturating_mul(exp_u32 as u64);
1193 let cap = limits.max_integer_bits.min(ctx.max_integer_bits) as u64;
1194 if predicted > cap {
1195 return Err(EngineError::new(
1196 ErrorCode::ResourceLimit,
1197 format!(
1198 "integer power would need about {predicted} bits, exceeding the limit of {cap}"
1199 ),
1200 ));
1201 }
1202 let out = base.pow(exp_u32);
1203 check_bigint(&out, ctx, limits)?;
1204 Ok(NumberResult::exact(Number::Integer(out)))
1205 }
1206 }
1207 Number::Rational(base) => {
1208 if negative_exponent {
1209 let flipped = BigRational::new(base.denom().clone(), base.numer().clone());
1210 let out = flipped.pow(exp_u32 as i32);
1211 check_rational(&out, ctx, limits)?;
1212 Ok(NumberResult::exact(Number::Rational(out)))
1213 } else {
1214 let out = base.pow(exp_u32 as i32);
1215 check_rational(&out, ctx, limits)?;
1216 Ok(NumberResult::exact(Number::Rational(out)))
1217 }
1218 }
1219 Number::Decimal(base) => {
1220 if negative_exponent {
1221 let positive = base.pow_u32(exp_u32, ctx, limits)?;
1222 let one = Decimal::from_bigint(BigInt::one());
1223 let (value, inexact) = one.div(&positive, ctx, limits)?;
1224 Ok(NumberResult {
1225 value: Number::Decimal(value),
1226 rounded: inexact,
1227 })
1228 } else {
1229 Ok(NumberResult::exact(Number::Decimal(
1230 base.pow_u32(exp_u32, ctx, limits)?,
1231 )))
1232 }
1233 }
1234 Number::Float64(_) => unreachable!("handled above"),
1235 }
1236 }
1237
1238 pub fn rem(
1240 &self,
1241 other: &Number,
1242 ctx: &NumericContext,
1243 limits: &Limits,
1244 ) -> Result<NumberResult, EngineError> {
1245 if other.is_zero() {
1246 return Err(EngineError::division_by_zero("remainder by zero"));
1247 }
1248 if self.is_float() || other.is_float() {
1249 if ctx.mode != NumericMode::Scientific {
1250 return Err(EngineError::new(
1251 ErrorCode::UnsupportedNumericMode,
1252 "float64 remainder requires scientific mode",
1253 ));
1254 }
1255 let a = self.to_f64().unwrap_or(f64::NAN);
1256 let b = other.to_f64().unwrap_or(f64::NAN);
1257 return Ok(NumberResult::rounded(Number::float(a % b)?));
1258 }
1259 let a = self
1260 .to_exact_rational()
1261 .ok_or_else(|| EngineError::internal("remainder operand not representable"))?;
1262 let b = other
1263 .to_exact_rational()
1264 .ok_or_else(|| EngineError::internal("remainder operand not representable"))?;
1265 let q = &a / &b;
1266 let truncated = q.trunc();
1267 let r = a - truncated * b;
1268 rational_result(r, self, other, ctx, limits)
1269 }
1270
1271 pub fn modulo(
1273 &self,
1274 other: &Number,
1275 ctx: &NumericContext,
1276 limits: &Limits,
1277 ) -> Result<NumberResult, EngineError> {
1278 if other.is_zero() {
1279 return Err(EngineError::division_by_zero("modulo by zero"));
1280 }
1281 if self.is_float() || other.is_float() {
1282 if ctx.mode != NumericMode::Scientific {
1283 return Err(EngineError::new(
1284 ErrorCode::UnsupportedNumericMode,
1285 "float64 modulo requires scientific mode",
1286 ));
1287 }
1288 let a = self.to_f64().unwrap_or(f64::NAN);
1289 let b = other.to_f64().unwrap_or(f64::NAN);
1290 let mut r = a % b;
1291 if r != 0.0 && (r < 0.0) != (b < 0.0) {
1292 r += b;
1293 }
1294 return Ok(NumberResult::rounded(Number::float(r)?));
1295 }
1296 let a = self
1297 .to_exact_rational()
1298 .ok_or_else(|| EngineError::internal("modulo operand not representable"))?;
1299 let b = other
1300 .to_exact_rational()
1301 .ok_or_else(|| EngineError::internal("modulo operand not representable"))?;
1302 let q = &a / &b;
1303 let floored = q.floor();
1304 let r = a - floored * b;
1305 rational_result(r, self, other, ctx, limits)
1306 }
1307
1308 pub fn round_to_scale(&self, scale: i64, mode: RoundingMode) -> Result<Number, EngineError> {
1309 match self {
1310 Number::Integer(v) => {
1311 if scale >= 0 {
1312 Ok(Number::Decimal(
1313 Decimal::from_bigint(v.clone()).round_to(scale as u32, mode),
1314 ))
1315 } else {
1316 let k = pow10((-scale) as u64);
1317 Ok(Number::Integer(div_round(v, &k, mode) * k))
1318 }
1319 }
1320 Number::Decimal(v) => Ok(Number::Decimal(v.round_to_scale(scale, mode))),
1321 Number::Rational(v) => {
1322 let scale = scale.clamp(-1_000_000, 1_000_000);
1323 if scale >= 0 {
1324 let factor = pow10(scale as u64);
1325 let scaled = v * BigRational::from_integer(factor);
1326 let rounded = div_round(scaled.numer(), scaled.denom(), mode);
1327 Ok(Number::Decimal(Decimal::from_parts(rounded, scale as u32)))
1328 } else {
1329 let factor = pow10((-scale) as u64);
1330 let scaled = v / BigRational::from_integer(factor.clone());
1331 let rounded = div_round(scaled.numer(), scaled.denom(), mode);
1332 Ok(Number::Integer(rounded * factor))
1333 }
1334 }
1335 Number::Float64(v) => {
1336 let factor = 10f64.powi(scale.clamp(-308, 308) as i32);
1337 let rounded = match mode {
1338 RoundingMode::HalfEven => (v.0 * factor).round_ties_even() / factor,
1339 RoundingMode::HalfAwayFromZero => (v.0 * factor).round() / factor,
1340 RoundingMode::TowardZero => (v.0 * factor).trunc() / factor,
1341 RoundingMode::Floor => (v.0 * factor).floor() / factor,
1342 RoundingMode::Ceiling => (v.0 * factor).ceil() / factor,
1343 };
1344 Ok(Number::Float64(Float64::new(rounded)?))
1345 }
1346 }
1347 }
1348}
1349
1350fn rational_result(
1351 value: BigRational,
1352 lhs: &Number,
1353 rhs: &Number,
1354 ctx: &NumericContext,
1355 limits: &Limits,
1356) -> Result<NumberResult, EngineError> {
1357 match (lhs, rhs) {
1358 (Number::Integer(_), Number::Integer(_)) => {
1359 if value.is_integer() {
1360 Ok(NumberResult::exact(Number::Integer(value.to_integer())))
1361 } else {
1362 check_rational(&value, ctx, limits)?;
1363 Ok(NumberResult::exact(Number::Rational(value)))
1364 }
1365 }
1366 (Number::Decimal(_), _) | (_, Number::Decimal(_)) => {
1367 let (decimal, inexact) = Decimal::from_rational(&value, ctx, limits)?;
1368 Ok(NumberResult {
1369 value: Number::Decimal(decimal),
1370 rounded: inexact,
1371 })
1372 }
1373 _ => {
1374 check_rational(&value, ctx, limits)?;
1375 Ok(NumberResult::exact(Number::Rational(value)))
1376 }
1377 }
1378}
1379
1380fn check_bigint(value: &BigInt, ctx: &NumericContext, limits: &Limits) -> Result<(), EngineError> {
1381 let cap = limits.max_integer_bits.min(ctx.max_integer_bits) as u64;
1382 if value.bits() > cap {
1383 return Err(EngineError::new(
1384 ErrorCode::ResourceLimit,
1385 format!(
1386 "integer result needs {} bits, exceeding the limit of {cap}",
1387 value.bits()
1388 ),
1389 ));
1390 }
1391 Ok(())
1392}
1393
1394fn check_rational(
1395 value: &BigRational,
1396 ctx: &NumericContext,
1397 limits: &Limits,
1398) -> Result<(), EngineError> {
1399 let cap = limits.max_integer_bits.min(ctx.max_integer_bits) as u64;
1400 if value.numer().bits() > cap || value.denom().bits() > cap {
1401 return Err(EngineError::new(
1402 ErrorCode::ResourceLimit,
1403 format!(
1404 "rational result needs {}/{} bits, exceeding the limit of {cap}",
1405 value.numer().bits(),
1406 value.denom().bits()
1407 ),
1408 ));
1409 }
1410 Ok(())
1411}
1412
1413fn parse_decimal_parts(source: &str, limits: &Limits) -> Result<(BigInt, u32), EngineError> {
1415 let bytes = source.as_bytes();
1416 if bytes.is_empty() {
1417 return Err(EngineError::malformed("empty numeric literal"));
1418 }
1419 let mut i = 0usize;
1420 let mut negative = false;
1421 match bytes[i] {
1422 b'+' => i += 1,
1423 b'-' => {
1424 negative = true;
1425 i += 1;
1426 }
1427 _ => {}
1428 }
1429 let mut digits = String::new();
1430 let mut frac_digits: i64 = 0;
1431 let mut seen_digit = false;
1432 let mut seen_point = false;
1433 while i < bytes.len() {
1434 match bytes[i] {
1435 b'0'..=b'9' => {
1436 digits.push(bytes[i] as char);
1437 if seen_point {
1438 frac_digits += 1;
1439 }
1440 seen_digit = true;
1441 i += 1;
1442 }
1443 b'.' => {
1444 if seen_point {
1445 return Err(EngineError::malformed(format!(
1446 "invalid numeric literal {source:?}: multiple decimal points"
1447 )));
1448 }
1449 seen_point = true;
1450 i += 1;
1451 }
1452 b'e' | b'E' => break,
1453 _ => {
1454 return Err(EngineError::malformed(format!(
1455 "invalid numeric literal {source:?}: unexpected character {:?}",
1456 bytes[i] as char
1457 )));
1458 }
1459 }
1460 }
1461 if !seen_digit {
1462 return Err(EngineError::malformed(format!(
1463 "invalid numeric literal {source:?}: no digits"
1464 )));
1465 }
1466 let mut exponent: i64 = 0;
1467 if i < bytes.len() {
1468 i += 1;
1470 let mut exp_negative = false;
1471 if i < bytes.len() {
1472 match bytes[i] {
1473 b'+' => i += 1,
1474 b'-' => {
1475 exp_negative = true;
1476 i += 1;
1477 }
1478 _ => {}
1479 }
1480 }
1481 let start = i;
1482 while i < bytes.len() && bytes[i].is_ascii_digit() {
1483 i += 1;
1484 }
1485 if start == i {
1486 return Err(EngineError::malformed(format!(
1487 "invalid numeric literal {source:?}: missing exponent digits"
1488 )));
1489 }
1490 if i != bytes.len() {
1491 return Err(EngineError::malformed(format!(
1492 "invalid numeric literal {source:?}: trailing characters"
1493 )));
1494 }
1495 let exp_text = &source[start..i];
1496 if exp_text.len() > 7 {
1497 return Err(EngineError::new(
1498 ErrorCode::PrecisionLimit,
1499 format!("exponent {exp_text} exceeds the supported range"),
1500 ));
1501 }
1502 exponent = exp_text.parse::<i64>().map_err(|_| {
1503 EngineError::malformed(format!("invalid numeric literal {source:?}: bad exponent"))
1504 })?;
1505 if exp_negative {
1506 exponent = -exponent;
1507 }
1508 }
1509 if digits.len() > limits.max_digits {
1510 return Err(EngineError::new(
1511 ErrorCode::ResourceLimit,
1512 format!(
1513 "numeric literal has {} digits, exceeding the limit of {}",
1514 digits.len(),
1515 limits.max_digits
1516 ),
1517 ));
1518 }
1519 let mut mantissa = BigInt::from_str(&digits)
1520 .map_err(|_| EngineError::malformed(format!("invalid numeric literal {source:?}")))?;
1521 if negative {
1522 mantissa = -mantissa;
1523 }
1524 let mut scale = frac_digits - exponent;
1525 if scale < 0 {
1526 mantissa *= pow10((-scale) as u64);
1527 scale = 0;
1528 }
1529 if scale > limits.max_decimal_scale as i64 {
1530 return Err(EngineError::new(
1531 ErrorCode::PrecisionLimit,
1532 format!(
1533 "numeric literal scale {scale} exceeds the limit of {}",
1534 limits.max_decimal_scale
1535 ),
1536 ));
1537 }
1538 if mantissa.bits() > limits.max_integer_bits as u64 {
1539 return Err(EngineError::new(
1540 ErrorCode::ResourceLimit,
1541 format!(
1542 "numeric literal needs {} bits, exceeding the limit of {}",
1543 mantissa.bits(),
1544 limits.max_integer_bits
1545 ),
1546 ));
1547 }
1548 Ok((mantissa, scale as u32))
1549}
1550
1551impl Number {
1552 pub fn parse_literal(source: &str, limits: &Limits) -> Result<Number, EngineError> {
1558 let trimmed = source.trim();
1559 if trimmed.is_empty() {
1560 return Err(EngineError::malformed("empty numeric literal"));
1561 }
1562 if trimmed.contains('.') || trimmed.contains('e') || trimmed.contains('E') {
1563 Ok(Number::Decimal(Decimal::parse(trimmed, limits)?))
1564 } else {
1565 let digits = trimmed.strip_prefix(['+', '-']).unwrap_or(trimmed);
1566 if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_digit()) {
1567 return Err(EngineError::malformed(format!(
1568 "invalid numeric literal {source:?}"
1569 )));
1570 }
1571 if digits.len() > limits.max_digits {
1572 return Err(EngineError::new(
1573 ErrorCode::ResourceLimit,
1574 format!(
1575 "numeric literal has {} digits, exceeding the limit of {}",
1576 digits.len(),
1577 limits.max_digits
1578 ),
1579 ));
1580 }
1581 let value = BigInt::from_str(trimmed).map_err(|_| {
1582 EngineError::malformed(format!("invalid integer literal {source:?}"))
1583 })?;
1584 if value.bits() > limits.max_integer_bits as u64 {
1585 return Err(EngineError::new(
1586 ErrorCode::ResourceLimit,
1587 format!(
1588 "integer literal needs {} bits, exceeding the limit of {}",
1589 value.bits(),
1590 limits.max_integer_bits
1591 ),
1592 ));
1593 }
1594 Ok(Number::Integer(value))
1595 }
1596 }
1597}
1598
1599impl fmt::Display for Number {
1600 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1601 match self {
1602 Number::Integer(v) => write!(f, "{v}"),
1603 Number::Rational(v) => {
1604 if v.is_integer() {
1605 write!(f, "{}", v.to_integer())
1606 } else {
1607 write!(f, "{}/{}", v.numer(), v.denom())
1608 }
1609 }
1610 Number::Decimal(v) => write!(f, "{v}"),
1611 Number::Float64(v) => write!(f, "{v}"),
1612 }
1613 }
1614}
1615
1616#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1618pub struct NumericContext {
1619 pub mode: NumericMode,
1620 pub precision: u32,
1622 pub max_integer_bits: u32,
1624 pub max_decimal_scale: u32,
1626 pub rounding: RoundingMode,
1628}
1629
1630impl Default for NumericContext {
1631 fn default() -> Self {
1632 NumericContext {
1633 mode: NumericMode::Auto,
1634 precision: 34,
1635 max_integer_bits: 8192,
1636 max_decimal_scale: 4096,
1637 rounding: RoundingMode::HalfEven,
1638 }
1639 }
1640}
1641
1642impl NumericContext {
1643 pub fn exact() -> Self {
1644 NumericContext {
1645 mode: NumericMode::Exact,
1646 ..Default::default()
1647 }
1648 }
1649
1650 pub fn scientific() -> Self {
1651 NumericContext {
1652 mode: NumericMode::Scientific,
1653 ..Default::default()
1654 }
1655 }
1656}
1657
1658#[cfg(test)]
1659mod tests {
1660 use super::*;
1661
1662 fn ctx() -> NumericContext {
1663 NumericContext::default()
1664 }
1665
1666 fn limits() -> Limits {
1667 Limits::conservative()
1668 }
1669
1670 #[test]
1671 fn decimal_parse_and_display_preserves_scale() {
1672 let d = Decimal::parse_default("0.10").unwrap();
1673 assert_eq!(d.mantissa(), &BigInt::from(10));
1674 assert_eq!(d.scale(), 2);
1675 assert_eq!(d.to_plain_string(), "0.10");
1676 assert_eq!(d.canonical_string(), "0.1");
1677 }
1678
1679 #[test]
1680 fn decimal_add_is_exact() {
1681 let a = Decimal::parse_default("0.1").unwrap();
1682 let b = Decimal::parse_default("0.2").unwrap();
1683 let sum = a.add(&b, &ctx(), &limits()).unwrap();
1684 assert_eq!(sum.to_plain_string(), "0.3");
1685 assert!(sum.to_plain_string() != "0.30000000000000004");
1686 }
1687
1688 #[test]
1689 fn rounding_modes_positive_and_negative_ties() {
1690 let cases = [
1691 ("1.005", RoundingMode::HalfEven, "1.00"),
1692 ("1.015", RoundingMode::HalfEven, "1.02"),
1693 ("1.005", RoundingMode::HalfAwayFromZero, "1.01"),
1694 ("-1.005", RoundingMode::HalfEven, "-1.00"),
1695 ("-1.015", RoundingMode::HalfEven, "-1.02"),
1696 ("-1.005", RoundingMode::HalfAwayFromZero, "-1.01"),
1697 ("1.009", RoundingMode::TowardZero, "1.00"),
1698 ("-1.009", RoundingMode::TowardZero, "-1.00"),
1699 ("-1.001", RoundingMode::Floor, "-1.01"),
1700 ("1.001", RoundingMode::Ceiling, "1.01"),
1701 ];
1702 for (input, mode, expected) in cases {
1703 let d = Decimal::parse_default(input).unwrap();
1704 let rounded = d.round_to(2, mode);
1705 assert_eq!(
1706 rounded.to_plain_string(),
1707 expected,
1708 "round({input}, {mode:?})"
1709 );
1710 }
1711 }
1712
1713 #[test]
1714 fn exact_division_terminates() {
1715 let a = Decimal::parse_default("1").unwrap();
1716 let b = Decimal::parse_default("8").unwrap();
1717 let (q, inexact) = a.div(&b, &ctx(), &limits()).unwrap();
1718 assert!(!inexact);
1719 assert_eq!(q.to_plain_string(), "0.125");
1720 }
1721
1722 #[test]
1723 fn inexact_division_reports_flag() {
1724 let a = Decimal::parse_default("1").unwrap();
1725 let b = Decimal::parse_default("3").unwrap();
1726 let (q, inexact) = a.div(&b, &ctx(), &limits()).unwrap();
1727 assert!(inexact);
1728 assert!(q.to_plain_string().starts_with("0.3333"));
1729 }
1730
1731 #[test]
1732 fn exact_mode_rejects_inexact_division() {
1733 let a = Decimal::parse_default("1").unwrap();
1734 let b = Decimal::parse_default("3").unwrap();
1735 let err = a.div(&b, &NumericContext::exact(), &limits()).unwrap_err();
1736 assert_eq!(err.code, ErrorCode::UnsupportedNumericMode);
1737 }
1738
1739 #[test]
1740 fn exact_sqrt_detection() {
1741 assert_eq!(
1742 Decimal::parse_default("0.25")
1743 .unwrap()
1744 .sqrt_exact()
1745 .unwrap()
1746 .to_plain_string(),
1747 "0.5"
1748 );
1749 assert!(Decimal::parse_default("2").unwrap().sqrt_exact().is_none());
1750 }
1751
1752 #[test]
1753 fn number_promotion_int_div() {
1754 let one = Number::integer(1);
1755 let three = Number::integer(3);
1756 let result = one.div(&three, &ctx(), &limits()).unwrap();
1757 assert!(!result.rounded);
1758 assert_eq!(result.value.to_string(), "1/3");
1759 }
1760
1761 #[test]
1762 fn float_arithmetic_requires_scientific_mode() {
1763 let a = Number::float(0.1).unwrap();
1764 let b = Number::integer(1);
1765 let err = a.add(&b, &ctx(), &limits()).unwrap_err();
1766 assert_eq!(err.code, ErrorCode::UnsupportedNumericMode);
1767 let ok = a.add(&b, &NumericContext::scientific(), &limits()).unwrap();
1768 assert!(ok.rounded);
1769 }
1770
1771 #[test]
1772 fn comparisons_are_exact_across_representations() {
1773 let dec = Number::Decimal(Decimal::parse_default("0.5").unwrap());
1774 let float = Number::float(0.5).unwrap();
1775 assert!(dec.numeric_eq(&float).unwrap());
1776 let dec_tenth = Number::Decimal(Decimal::parse_default("0.1").unwrap());
1777 let float_tenth = Number::float(0.1).unwrap();
1778 assert!(!dec_tenth.numeric_eq(&float_tenth).unwrap());
1779 }
1780
1781 #[test]
1782 fn pow_zero_zero_is_one() {
1783 let zero = Number::integer(0);
1784 let result = zero.pow(&zero, &ctx(), &limits()).unwrap();
1785 assert_eq!(result.value.to_string(), "1");
1786 }
1787
1788 #[test]
1789 fn division_by_zero_is_structured() {
1790 let one = Number::integer(1);
1791 let zero = Number::integer(0);
1792 let err = one.div(&zero, &ctx(), &limits()).unwrap_err();
1793 assert_eq!(err.code, ErrorCode::DivisionByZero);
1794 }
1795
1796 #[test]
1797 fn remainder_vs_modulo_negative_operands() {
1798 let a = Number::integer(-7);
1799 let b = Number::integer(3);
1800 let rem = a.rem(&b, &ctx(), &limits()).unwrap();
1801 let modulo = a.modulo(&b, &ctx(), &limits()).unwrap();
1802 assert_eq!(rem.value.to_string(), "-1");
1803 assert_eq!(modulo.value.to_string(), "2");
1804 }
1805}