1use std::{
2 collections::{BTreeMap, btree_map},
3 fmt::Display,
4 iter::FusedIterator,
5 ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
6};
7
8use rust_decimal::Decimal;
9
10use crate::report::{
11 commodity::{CommodityStore, CommodityTag},
12 context::ReportContext,
13};
14
15use super::{PostingAmount, SingleAmount, error::EvalError};
16
17#[derive(Debug, Default, PartialEq, Eq, Clone)]
19pub struct Amount<'ctx> {
20 values: BTreeMap<CommodityTag<'ctx>, Decimal>,
24}
25
26impl<'ctx> TryFrom<Amount<'ctx>> for SingleAmount<'ctx> {
27 type Error = EvalError<'ctx>;
28
29 fn try_from(value: Amount<'ctx>) -> Result<Self, Self::Error> {
30 SingleAmount::try_from(&value)
31 }
32}
33
34impl<'ctx> TryFrom<Amount<'ctx>> for PostingAmount<'ctx> {
35 type Error = EvalError<'ctx>;
36
37 fn try_from(value: Amount<'ctx>) -> Result<Self, Self::Error> {
38 PostingAmount::try_from(&value)
39 }
40}
41
42impl<'ctx> TryFrom<&Amount<'ctx>> for SingleAmount<'ctx> {
43 type Error = EvalError<'ctx>;
44
45 fn try_from(value: &Amount<'ctx>) -> Result<Self, Self::Error> {
46 let (commodity, value) = value
47 .values
48 .iter()
49 .next()
50 .ok_or(EvalError::SingleAmountRequired)?;
51 Ok(SingleAmount {
52 value: *value,
53 commodity: *commodity,
54 })
55 }
56}
57
58impl<'ctx> TryFrom<&Amount<'ctx>> for PostingAmount<'ctx> {
59 type Error = EvalError<'ctx>;
60
61 fn try_from(value: &Amount<'ctx>) -> Result<Self, Self::Error> {
62 if value.values.len() > 1 {
63 Err(EvalError::PostingAmountRequired)
64 } else {
65 Ok(value
66 .values
67 .iter()
68 .next()
69 .map(|(commodity, value)| {
70 PostingAmount::Single(SingleAmount {
71 value: *value,
72 commodity: *commodity,
73 })
74 })
75 .unwrap_or_default())
76 }
77 }
78}
79
80impl<'ctx> From<PostingAmount<'ctx>> for Amount<'ctx> {
81 fn from(value: PostingAmount<'ctx>) -> Self {
82 match value {
83 PostingAmount::Zero => Amount::zero(),
84 PostingAmount::Single(single_amount) => single_amount.into(),
85 }
86 }
87}
88
89impl<'ctx> From<SingleAmount<'ctx>> for Amount<'ctx> {
90 fn from(value: SingleAmount<'ctx>) -> Self {
91 Amount::from_value(value.commodity, value.value)
92 }
93}
94
95impl<'ctx> FromIterator<(CommodityTag<'ctx>, Decimal)> for Amount<'ctx> {
96 fn from_iter<T>(iter: T) -> Self
97 where
98 T: IntoIterator<Item = (CommodityTag<'ctx>, Decimal)>,
99 {
100 let mut ret = Self::zero();
101 for (commodity, value) in iter.into_iter() {
102 ret += SingleAmount::from_value(commodity, value);
103 }
104 ret
105 }
106}
107
108impl<'ctx> Amount<'ctx> {
109 #[inline(always)]
111 pub fn zero() -> Self {
112 Self::default()
113 }
114
115 pub fn from_value(commodity: CommodityTag<'ctx>, amount: Decimal) -> Self {
117 Self::zero() + SingleAmount::from_value(commodity, amount)
118 }
119
120 pub fn from_values(values: BTreeMap<CommodityTag<'ctx>, Decimal>) -> Self {
122 Self { values }
123 }
124
125 pub fn into_values(self) -> BTreeMap<CommodityTag<'ctx>, Decimal> {
127 self.values
128 }
129
130 pub fn iter(&self) -> impl Iterator<Item = SingleAmount<'ctx>> + '_ {
132 AmountIter(self.values.iter())
133 }
134
135 pub fn as_inline_display<'a>(&'a self, ctx: &'a ReportContext<'ctx>) -> impl Display + 'a + 'ctx
139 where
140 'a: 'ctx,
141 {
142 InlinePrintAmount {
143 commodity_store: &ctx.commodities,
144 amount: self,
145 }
146 }
147
148 pub fn is_absolute_zero(&self) -> bool {
151 self.values.is_empty()
152 }
153
154 pub fn is_zero(&self) -> bool {
156 self.values.iter().all(|(_, v)| v.is_zero())
157 }
158
159 pub fn remove_zero_entries(&mut self) {
163 self.values.retain(|_, v| !v.is_zero());
164 }
165
166 pub(crate) fn set_partial(&mut self, amount: SingleAmount<'ctx>) -> SingleAmount<'ctx> {
171 let value = if amount.value.is_zero() {
172 self.values.remove(&amount.commodity)
173 } else {
174 self.values.insert(amount.commodity, amount.value)
175 }
176 .unwrap_or_default();
177 SingleAmount {
178 value,
179 commodity: amount.commodity,
180 }
181 }
182
183 fn get_part(&self, commodity: CommodityTag<'ctx>) -> Decimal {
185 self.values.get(&commodity).copied().unwrap_or_default()
186 }
187
188 pub fn maybe_pair(&self) -> Option<(SingleAmount<'ctx>, SingleAmount<'ctx>)> {
191 if self.values.len() != 2 {
192 return None;
193 }
194 let ((c1, v1), (c2, v2)) = self.values.iter().zip(self.values.iter().skip(1)).next()?;
195 Some((
196 SingleAmount::from_value(*c1, *v1),
197 SingleAmount::from_value(*c2, *v2),
198 ))
199 }
200
201 pub fn round(mut self, ctx: &ReportContext) -> Self {
203 self.round_mut(ctx);
204 self
205 }
206
207 pub fn round_mut(&mut self, ctx: &ReportContext) {
209 for (k, v) in self.values.iter_mut() {
210 match ctx.commodities.scale(*k) {
211 None => (),
212 Some(dp) => {
213 let updated = v.round_dp_with_strategy(
214 dp,
215 rust_decimal::RoundingStrategy::MidpointNearestEven,
216 );
217 *v = updated;
218 }
219 }
220 }
221 }
222
223 pub fn negate(mut self) -> Self {
225 for v in self.values.values_mut() {
226 v.set_sign_positive(!v.is_sign_positive())
227 }
228 self
229 }
230
231 pub fn check_div(mut self, rhs: Decimal) -> Result<Self, EvalError<'ctx>> {
233 if rhs.is_zero() {
234 return Err(EvalError::DivideByZero);
235 }
236 for v in self.values.values_mut() {
237 *v = v.checked_div(rhs).ok_or(EvalError::NumberOverflow)?;
238 }
239 Ok(self)
240 }
241
242 pub(crate) fn assert_balance(&self, expected: &PostingAmount<'ctx>) -> Self {
251 match expected {
252 PostingAmount::Zero => {
253 if self.is_zero() {
254 Self::zero()
255 } else {
256 -self.clone()
257 }
258 }
259 PostingAmount::Single(single) => {
260 let diff = single.value - self.get_part(single.commodity);
261 if diff.is_zero() {
262 Self::zero()
263 } else {
264 Self::from_value(single.commodity, diff)
265 }
266 }
267 }
268 }
269}
270
271#[derive(Debug)]
272struct AmountIter<'a, 'ctx>(btree_map::Iter<'a, CommodityTag<'ctx>, Decimal>);
273
274impl<'ctx> Iterator for AmountIter<'_, 'ctx> {
275 type Item = SingleAmount<'ctx>;
276
277 fn next(&mut self) -> Option<Self::Item> {
278 self.0.next().map(|(c, v)| SingleAmount::from_value(*c, *v))
279 }
280}
281
282impl FusedIterator for AmountIter<'_, '_> {}
283
284#[derive(Debug)]
285struct InlinePrintAmount<'a, 'ctx> {
286 commodity_store: &'a CommodityStore<'ctx>,
287 amount: &'a Amount<'ctx>,
288}
289
290impl Display for InlinePrintAmount<'_, '_> {
291 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292 let vs = &self.amount.values;
293 if vs.len() <= 1 {
294 return match vs.iter().next() {
295 Some((c, v)) => {
296 write!(f, "{} {}", v, c.to_str_lossy(self.commodity_store))
297 }
298 None => write!(f, "0"),
299 };
300 }
301 write!(f, "(")?;
303 for (i, (c, v)) in vs.iter().enumerate() {
304 let mut v = *v;
305 if i != 0 {
306 if v.is_sign_negative() {
307 v.set_sign_negative(false);
308 write!(f, " - ")?;
309 } else {
310 write!(f, " + ")?;
311 }
312 }
313 write!(f, "{} {}", v, c.to_str_lossy(self.commodity_store))?;
314 }
315 write!(f, ")")
316 }
317}
318
319impl Neg for Amount<'_> {
320 type Output = Self;
321
322 fn neg(self) -> Self::Output {
323 self.negate()
324 }
325}
326
327impl<'ctx> Add<&'_ Amount<'ctx>> for Amount<'ctx> {
328 type Output = Self;
329
330 fn add(mut self, rhs: &Self) -> Self::Output {
331 self += rhs;
332 self
333 }
334}
335
336impl Add for Amount<'_> {
337 type Output = Self;
338
339 fn add(self, rhs: Self) -> Self::Output {
340 self + &rhs
341 }
342}
343
344impl<'ctx> AddAssign<&'_ Amount<'ctx>> for Amount<'ctx> {
345 #[inline]
346 fn add_assign(&mut self, rhs: &Amount<'ctx>) {
347 for (&c, &v2) in &rhs.values {
348 *self.values.entry(c).or_insert(Decimal::ZERO) += v2;
349 }
352 }
353}
354
355impl AddAssign for Amount<'_> {
356 fn add_assign(&mut self, rhs: Self) {
357 *self += &rhs;
358 }
359}
360
361impl<'ctx> Add<SingleAmount<'ctx>> for Amount<'ctx> {
362 type Output = Amount<'ctx>;
363
364 fn add(mut self, rhs: SingleAmount<'ctx>) -> Self::Output {
365 self += rhs;
366 self
367 }
368}
369
370impl<'ctx> AddAssign<SingleAmount<'ctx>> for Amount<'ctx> {
371 fn add_assign(&mut self, rhs: SingleAmount<'ctx>) {
372 let curr = self.values.entry(rhs.commodity).or_default();
373 *curr += rhs.value;
374 }
375}
376
377impl<'ctx> AddAssign<PostingAmount<'ctx>> for Amount<'ctx> {
378 fn add_assign(&mut self, rhs: PostingAmount<'ctx>) {
379 match rhs {
380 PostingAmount::Zero => (),
381 PostingAmount::Single(single) => *self += single,
382 }
383 }
384}
385
386impl<'ctx> Sub<&'_ Amount<'ctx>> for Amount<'ctx> {
387 type Output = Self;
388
389 fn sub(mut self, rhs: &Self) -> Self::Output {
390 self -= rhs;
391 self
392 }
393}
394
395impl Sub for Amount<'_> {
396 type Output = Self;
397
398 fn sub(self, rhs: Self) -> Self::Output {
399 self - &rhs
400 }
401}
402
403impl<'ctx> SubAssign<&'_ Amount<'ctx>> for Amount<'ctx> {
404 fn sub_assign(&mut self, rhs: &Amount<'ctx>) {
405 for (&c, &v2) in &rhs.values {
406 *self.values.entry(c).or_insert(Decimal::ZERO) -= v2;
407 }
408 }
409}
410
411impl SubAssign for Amount<'_> {
412 fn sub_assign(&mut self, rhs: Self) {
413 *self -= &rhs;
414 }
415}
416
417impl Mul<Decimal> for Amount<'_> {
418 type Output = Self;
419
420 fn mul(mut self, rhs: Decimal) -> Self::Output {
421 self *= rhs;
422 self
423 }
424}
425
426impl MulAssign<Decimal> for Amount<'_> {
427 fn mul_assign(&mut self, rhs: Decimal) {
428 for mut v in self.values.values_mut() {
429 v *= rhs;
430 }
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 use bumpalo::Bump;
439 use maplit::btreemap;
440 use pretty_assertions::assert_eq;
441 use pretty_decimal::PrettyDecimal;
442 use rust_decimal_macros::dec;
443
444 use crate::report::ReportContext;
445
446 #[test]
447 fn test_default() {
448 let arena = Bump::new();
449 let ctx = ReportContext::new(&arena);
450 let amount = Amount::default();
451 assert_eq!(format!("{}", amount.as_inline_display(&ctx)), "0")
452 }
453
454 #[test]
455 fn test_from_value() {
456 let arena = Bump::new();
457 let mut ctx = ReportContext::new(&arena);
458 let jpy = ctx.commodities.ensure("JPY");
459 let amount = Amount::from_value(jpy, dec!(123.45));
460 assert_eq!(format!("{}", amount.as_inline_display(&ctx)), "123.45 JPY")
461 }
462
463 #[test]
464 fn test_from_values() {
465 let arena = Bump::new();
466 let mut ctx = ReportContext::new(&arena);
467 let jpy = ctx.commodities.ensure("JPY");
468 let chf = ctx.commodities.ensure("CHF");
469
470 let amount = Amount::from_iter([(jpy, dec!(10)), (chf, dec!(1))]);
471 assert_eq!(
472 amount.into_values(),
473 btreemap! {jpy => dec!(10), chf => dec!(1)},
474 );
475
476 let amount = Amount::from_iter([(jpy, dec!(10)), (jpy, dec!(1))]);
477 assert_eq!(amount.into_values(), btreemap! {jpy => dec!(11)});
478
479 let amount = Amount::from_iter([(jpy, dec!(10)), (jpy, dec!(-10))]);
480 assert_eq!(amount.into_values(), btreemap! {jpy => dec!(0)});
481 }
482
483 #[test]
484 fn test_is_absolute_zero() {
485 let arena = Bump::new();
486 let mut ctx = ReportContext::new(&arena);
487 let jpy = ctx.commodities.ensure("JPY");
488 let usd = ctx.commodities.ensure("USD");
489
490 assert!(Amount::default().is_absolute_zero());
491 assert!(!Amount::from_value(jpy, dec!(0)).is_absolute_zero());
492
493 let mut amount = Amount::from_iter([(jpy, dec!(0)), (usd, dec!(0))]);
494 assert!(
495 !amount.is_absolute_zero(),
496 "{}",
497 amount.as_inline_display(&ctx)
498 );
499
500 amount.remove_zero_entries();
501 assert!(
502 amount.is_absolute_zero(),
503 "{}",
504 amount.as_inline_display(&ctx)
505 );
506 }
507
508 #[test]
509 fn test_is_zero() {
510 let arena = Bump::new();
511 let mut ctx = ReportContext::new(&arena);
512 let jpy = ctx.commodities.ensure("JPY");
513 let usd = ctx.commodities.ensure("USD");
514
515 assert!(Amount::default().is_zero());
516 assert!(Amount::from_value(jpy, dec!(0)).is_zero());
517 assert!(Amount::from_iter([(jpy, dec!(0)), (usd, dec!(0))]).is_zero());
518
519 assert!(!Amount::from_value(jpy, dec!(1)).is_zero());
520 assert!(!Amount::from_iter([(jpy, dec!(0)), (usd, dec!(1))]).is_zero());
521 }
522
523 #[test]
524 fn test_neg() {
525 let arena = Bump::new();
526 let mut ctx = ReportContext::new(&arena);
527 let jpy = ctx.commodities.ensure("JPY");
528 let usd = ctx.commodities.ensure("USD");
529
530 assert_eq!(-Amount::zero(), Amount::zero());
531 assert_eq!(
532 -Amount::from_value(jpy, dec!(100)),
533 Amount::from_value(jpy, dec!(-100))
534 );
535 assert_eq!(
536 -Amount::from_iter([(jpy, dec!(100)), (usd, dec!(-20.35))]),
537 Amount::from_iter([(jpy, dec!(-100)), (usd, dec!(20.35))]),
538 );
539 }
540
541 #[test]
542 fn test_add_amount() {
543 let arena = Bump::new();
544 let mut ctx = ReportContext::new(&arena);
545 let jpy = ctx.commodities.ensure("JPY");
546 let usd = ctx.commodities.ensure("USD");
547 let eur = ctx.commodities.ensure("EUR");
548 let chf = ctx.commodities.ensure("CHF");
549
550 let zero_plus_zero = Amount::zero() + Amount::zero();
551 assert_eq!(zero_plus_zero, Amount::zero());
552
553 assert_eq!(
554 Amount::from_value(jpy, dec!(1)) + Amount::zero(),
555 Amount::from_value(jpy, dec!(1)),
556 );
557 assert_eq!(
558 Amount::zero() + Amount::from_value(jpy, dec!(1)),
559 Amount::from_value(jpy, dec!(1)),
560 );
561 assert_eq!(
562 Amount::from_iter([
563 (jpy, dec!(123.00)),
564 (usd, dec!(456.0)),
565 (eur, dec!(7.89)),
566 (chf, dec!(0)), ]),
568 Amount::from_value(jpy, dec!(123.45))
569 + Amount::from_value(jpy, dec!(-0.45))
570 + Amount::from_value(usd, dec!(456))
571 + Amount::from_value(usd, dec!(0.0))
572 + -Amount::from_value(chf, dec!(100))
573 + Amount::from_value(eur, dec!(7.89))
574 + Amount::from_value(chf, dec!(100)),
575 );
576
577 assert_eq!(
578 Amount::from_iter([(jpy, dec!(0)), (usd, dec!(0)), (chf, dec!(0))]),
579 Amount::from_iter([(jpy, dec!(1)), (usd, dec!(2)), (chf, dec!(3))])
580 + Amount::from_iter([(jpy, dec!(-1)), (usd, dec!(-2)), (chf, dec!(-3))])
581 );
582 }
583
584 #[test]
585 fn test_add_single_amount() {
586 let arena = Bump::new();
587 let mut ctx = ReportContext::new(&arena);
588 let jpy = ctx.commodities.ensure("JPY");
589 let usd = ctx.commodities.ensure("USD");
590
591 let amount = Amount::zero() + SingleAmount::from_value(usd, dec!(0));
592 assert_eq!(amount, Amount::from_value(usd, dec!(0)));
593
594 assert_eq!(
595 Amount::zero() + SingleAmount::from_value(jpy, dec!(1)),
596 Amount::from_value(jpy, dec!(1)),
597 );
598 }
599
600 #[test]
601 fn test_sub() {
602 let arena = Bump::new();
603 let mut ctx = ReportContext::new(&arena);
604 let jpy = ctx.commodities.ensure("JPY");
605 let usd = ctx.commodities.ensure("USD");
606 let eur = ctx.commodities.ensure("EUR");
607 let chf = ctx.commodities.ensure("CHF");
608
609 let zero_minus_zero = Amount::zero() - Amount::zero();
610 assert_eq!(zero_minus_zero, Amount::zero());
611
612 assert_eq!(
613 Amount::from_value(jpy, dec!(1)) - Amount::zero(),
614 Amount::from_value(jpy, dec!(1)),
615 );
616 assert_eq!(
617 Amount::zero() - Amount::from_value(jpy, dec!(1)),
618 Amount::from_value(jpy, dec!(-1)),
619 );
620 assert_eq!(
621 Amount::from_iter([
622 (jpy, dec!(12345)),
623 (eur, dec!(-200)),
624 (chf, dec!(13.3)),
625 (usd, dec!(0))
626 ]),
627 Amount::from_iter([(jpy, dec!(12345)), (usd, dec!(56.78))])
628 - Amount::from_iter([(usd, dec!(56.780)), (eur, dec!(200)), (chf, dec!(-13.3)),]),
629 );
630
631 let mut val = Amount::zero();
632
633 val -= Amount::from_value(jpy, dec!(1));
634 assert_eq!(val, Amount::from_value(jpy, dec!(-1)));
635
636 val -= &Amount::from_value(jpy, dec!(1));
637 assert_eq!(val, Amount::from_value(jpy, dec!(-2)));
638 }
639
640 fn eps() -> Decimal {
641 Decimal::try_from_i128_with_scale(1, 28).unwrap()
642 }
643
644 #[test]
645 fn test_mul() {
646 let arena = Bump::new();
647 let mut ctx = ReportContext::new(&arena);
648 let jpy = ctx.commodities.ensure("JPY");
649 let eur = ctx.commodities.ensure("EUR");
650 let chf = ctx.commodities.ensure("CHF");
651
652 assert_eq!(Amount::zero() * dec!(5), Amount::zero());
653 assert_eq!(
654 Amount::from_value(jpy, dec!(1)) * Decimal::ZERO,
655 Amount::from_value(jpy, dec!(0)),
656 );
657 assert_eq!(
658 Amount::from_value(jpy, dec!(123)) * dec!(3),
659 Amount::from_value(jpy, dec!(369)),
660 );
661 assert_eq!(
662 Amount::from_iter([(jpy, dec!(10081)), (eur, dec!(200)), (chf, dec!(-13.3))])
663 * dec!(-0.5),
664 Amount::from_iter([(jpy, dec!(-5040.5)), (eur, dec!(-100.0)), (chf, dec!(6.65))]),
665 );
666 assert_eq!(
667 Amount::from_value(jpy, eps()) * eps(),
668 Amount::from_value(jpy, dec!(0))
669 );
670 }
671
672 #[test]
673 fn test_check_div() {
674 let arena = Bump::new();
675 let mut ctx = ReportContext::new(&arena);
676 let jpy = ctx.commodities.ensure("JPY");
677 let eur = ctx.commodities.ensure("EUR");
678 let chf = ctx.commodities.ensure("CHF");
679
680 assert_eq!(Amount::zero().check_div(dec!(5)).unwrap(), Amount::zero());
681 assert_eq!(
682 Amount::zero().check_div(dec!(0)).unwrap_err(),
683 EvalError::DivideByZero
684 );
685
686 assert_eq!(
687 Amount::from_value(jpy, dec!(50))
688 .check_div(dec!(4))
689 .unwrap(),
690 Amount::from_value(jpy, dec!(12.5))
691 );
692
693 assert_eq!(
694 Amount::from_value(jpy, Decimal::MAX)
695 .check_div(eps())
696 .unwrap_err(),
697 EvalError::NumberOverflow
698 );
699
700 assert_eq!(
701 Amount::from_value(jpy, eps())
702 .check_div(Decimal::MAX)
703 .unwrap(),
704 Amount::from_value(jpy, dec!(0))
705 );
706
707 assert_eq!(
708 Amount::from_iter([(jpy, dec!(810)), (eur, dec!(-100.0)), (chf, dec!(6.66))])
709 .check_div(dec!(3))
710 .unwrap(),
711 Amount::from_iter([
712 (jpy, dec!(270)),
713 (eur, dec!(-33.333333333333333333333333333)),
714 (chf, dec!(2.22))
715 ]),
716 );
717 }
718
719 #[test]
720 fn test_round() {
721 let arena = Bump::new();
722 let mut ctx = ReportContext::new(&arena);
723 let jpy = ctx.commodities.ensure("JPY");
724 let eur = ctx.commodities.ensure("EUR");
725 let chf = ctx.commodities.ensure("CHF");
726
727 ctx.commodities
728 .set_format(jpy, PrettyDecimal::comma3dot(dec!(12345)));
729 ctx.commodities
730 .set_format(eur, PrettyDecimal::plain(dec!(123.45)));
731 ctx.commodities
732 .set_format(chf, PrettyDecimal::comma3dot(dec!(123.450)));
733
734 assert_eq!(Amount::zero(), Amount::zero().round(&ctx));
735
736 assert_eq!(
737 Amount::from_iter([(jpy, dec!(812)), (eur, dec!(-100.00)), (chf, dec!(6.660))]),
738 Amount::from_iter([(jpy, dec!(812)), (eur, dec!(-100.0)), (chf, dec!(6.66))])
739 .round(&ctx),
740 );
741
742 assert_eq!(
743 Amount::from_iter([(jpy, dec!(812)), (eur, dec!(-100.02)), (chf, dec!(6.666))]),
744 Amount::from_iter([
745 (jpy, dec!(812.5)),
746 (eur, dec!(-100.015)),
747 (chf, dec!(6.6665))
748 ])
749 .round(&ctx),
750 );
751 }
752
753 #[test]
754 fn test_to_string() {
755 let arena = Bump::new();
756 let mut ctx = ReportContext::new(&arena);
757 let jpy = ctx.commodities.ensure("JPY");
758 let chf = ctx.commodities.ensure("CHF");
759
760 assert_eq!("0", Amount::default().as_inline_display(&ctx).to_string());
761
762 assert_eq!(
763 "10 JPY",
764 Amount::from_value(jpy, dec!(10))
765 .as_inline_display(&ctx)
766 .to_string()
767 );
768
769 assert_eq!(
770 "(10 JPY + 1 CHF)",
771 Amount::from_iter([(jpy, dec!(10)), (chf, dec!(1))])
772 .as_inline_display(&ctx)
773 .to_string()
774 );
775
776 assert_eq!(
777 "(-10 JPY - 1 CHF)",
778 Amount::from_iter([(jpy, dec!(-10)), (chf, dec!(-1))])
779 .as_inline_display(&ctx)
780 .to_string()
781 );
782 }
783}