Skip to main content

finance_solution/cashflow/
mod.rs

1#![allow(unused_imports)]
2
3//! The internal module which supports the solution struct for the Cashflow family of functions (e.g., `payment`).
4
5// use std::fmt::Debug;
6use std::fmt;
7// use colored::*;
8
9// Import needed for the function references in the Rustdoc comments.
10use crate::*;
11use std::cmp::max;
12use std::ops::Deref;
13
14pub mod future_value_annuity;
15#[doc(inline)]
16pub use future_value_annuity::*;
17
18pub mod payment;
19#[doc(inline)]
20pub use payment::*;
21
22pub mod present_value_annuity;
23#[doc(inline)]
24pub use present_value_annuity::*;
25
26pub mod net_present_value;
27#[doc(inline)]
28pub use net_present_value::*;
29
30pub mod nper;
31#[doc(inline)]
32pub use nper::*;
33
34/// When a level payment (annuity installment) falls within each period.
35///
36/// Prefer this enum over a bare `bool`. For ergonomics, `false` converts to
37/// [`PaymentTiming::EndOfPeriod`] and `true` to [`PaymentTiming::BeginningOfPeriod`]
38/// (Excel `type=0` / `type=1`).
39///
40/// # Examples
41/// ```
42/// use finance_solution::{payment, PaymentTiming};
43///
44/// let end = payment(0.01, 12, 1_000.0, 0.0, PaymentTiming::EndOfPeriod).unwrap();
45/// let end_bool = payment(0.01, 12, 1_000.0, 0.0, false).unwrap();
46/// assert!((end - end_bool).abs() < 1e-12);
47///
48/// let due = payment(0.01, 12, 1_000.0, 0.0, true).unwrap();
49/// assert!(due.abs() < end.abs()); // due payments are slightly smaller in magnitude
50/// ```
51#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
52pub enum PaymentTiming {
53    /// Payment at the end of each period (ordinary annuity). Default / Excel `type=0`.
54    #[default]
55    EndOfPeriod,
56    /// Payment at the beginning of each period (annuity due). Excel `type=1`.
57    BeginningOfPeriod,
58}
59
60impl PaymentTiming {
61    /// True when payments fall at the start of each period.
62    #[inline]
63    pub fn is_beginning(&self) -> bool {
64        matches!(self, PaymentTiming::BeginningOfPeriod)
65    }
66
67    /// True when payments fall at the end of each period.
68    #[inline]
69    pub fn is_end(&self) -> bool {
70        matches!(self, PaymentTiming::EndOfPeriod)
71    }
72}
73
74impl fmt::Display for PaymentTiming {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        match self {
77            PaymentTiming::EndOfPeriod => write!(f, "EndOfPeriod"),
78            PaymentTiming::BeginningOfPeriod => write!(f, "BeginningOfPeriod"),
79        }
80    }
81}
82
83impl From<bool> for PaymentTiming {
84    fn from(due_at_beginning: bool) -> Self {
85        if due_at_beginning {
86            PaymentTiming::BeginningOfPeriod
87        } else {
88            PaymentTiming::EndOfPeriod
89        }
90    }
91}
92
93impl From<PaymentTiming> for bool {
94    fn from(timing: PaymentTiming) -> bool {
95        timing.is_beginning()
96    }
97}
98
99#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
100pub enum CashflowVariable {
101    PresentValueAnnuity,
102    PresentValueAnnuityDue,
103    FutureValueAnnuity,
104    Payment,
105    FutureValueAnnuityDue,
106    NetPresentValue,
107}
108
109impl CashflowVariable {
110    /// Returns true if the variant is CashflowVariable::PresentValueAnnuity indicating that the
111    /// solution was created by calculating the present value of an annuity with the payment due at
112    /// the end of the month.
113    pub fn is_present_value_annuity(&self) -> bool {
114        match self {
115            CashflowVariable::PresentValueAnnuity => true,
116            _ => false,
117        }
118    }
119
120    /// Returns true if the variant is CashflowVariable::FutureValueAnnuity indicating that the
121    /// solution was created by calculating the future value of an annuity with the payment due at
122    /// the end of the month.
123    pub fn is_future_value_annuity(&self) -> bool {
124        match self {
125            CashflowVariable::FutureValueAnnuity => true,
126            _ => false,
127        }
128    }
129
130    /// Returns true if the variant is CashflowVariable::Payment indicating that the solution
131    /// was created in a call to [`payment_solution`].
132    pub fn is_payment(&self) -> bool {
133        match self {
134            CashflowVariable::Payment => true,
135            _ => false,
136        }
137    }
138
139    /// Returns true if the variant is CashflowVariable::PresentValueAnnuityDue indicating that
140    /// the solution was created by calculating the present value of an annuity with the payment due
141    /// at the beginning of the month.
142    pub fn is_present_value_annuity_due(&self) -> bool {
143        match self {
144            CashflowVariable::PresentValueAnnuityDue => true,
145            _ => false,
146        }
147    }
148
149    /// Returns true if the variant is CashflowVariable::FutureValueAnnuityDue indicating that
150    /// the solution was created by calculating the future value of an annuity with the payment due
151    /// at the beginning of the month.
152    pub fn is_future_value_annuity_due(&self) -> bool {
153        match self {
154            CashflowVariable::FutureValueAnnuityDue => true,
155            _ => false,
156        }
157    }
158
159    /// Returns true if the variant is CashflowVariable::NetPresentValue indicating that the
160    /// solution was created by calculating a net present value.
161    pub fn is_net_present_value(&self) -> bool {
162        match self {
163            CashflowVariable::NetPresentValue => true,
164            _ => false,
165        }
166    }
167}
168
169impl fmt::Display for CashflowVariable {
170    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
171        match *self {
172            CashflowVariable::PresentValueAnnuity => write!(f, "Present Value Annuity"),
173            CashflowVariable::FutureValueAnnuity => write!(f, "Future Value Annuity"),
174            CashflowVariable::Payment => write!(f, "Payment"),
175            CashflowVariable::PresentValueAnnuityDue => write!(f, "Present Value Annuity Due"),
176            CashflowVariable::FutureValueAnnuityDue => write!(f, "Future Value Annuity Due"),
177            CashflowVariable::NetPresentValue => write!(f, "Net Present Value"),
178        }
179    }
180}
181
182/// A record of a cash flow calculation such as payment, net present value, or the present value or
183/// future value of an annuity.
184#[derive(Clone, Debug)]
185pub struct CashflowSolution {
186    calculated_field: CashflowVariable,
187    rate: f64,
188    periods: u32,
189    present_value: f64,
190    future_value: f64,
191    due_at_beginning: bool,
192    payment: f64,
193    sum_of_payments: f64,
194    sum_of_interest: f64,
195    formula: String,
196    symbolic_formula: String,
197    // pub input_in_percent: String,
198}
199
200impl CashflowSolution {
201    pub(crate) fn new(
202        calculated_field: CashflowVariable,
203        rate: f64,
204        periods: u32,
205        present_value: f64,
206        future_value: f64,
207        due_at_beginning: bool,
208        payment: f64,
209        formula: &str,
210        symbolic_formula: &str,
211    ) -> Self {
212        // Caller validated formulas and money fields before construction.
213        debug_assert!(!formula.is_empty());
214        let sum_of_payments = payment * periods as f64;
215        let sum_of_interest = sum_of_payments + present_value + future_value;
216        Self {
217            calculated_field,
218            rate,
219            periods,
220            present_value,
221            future_value,
222            due_at_beginning,
223            payment,
224            sum_of_payments,
225            sum_of_interest,
226            formula: formula.to_string(),
227            symbolic_formula: symbolic_formula.to_string(),
228        }
229    }
230
231    pub fn calculated_field(&self) -> &CashflowVariable {
232        &self.calculated_field
233    }
234
235    pub fn rate(&self) -> f64 {
236        self.rate
237    }
238
239    pub fn periods(&self) -> u32 {
240        self.periods
241    }
242
243    pub fn present_value(&self) -> f64 {
244        self.present_value
245    }
246
247    pub fn future_value(&self) -> f64 {
248        self.future_value
249    }
250
251    pub fn due_at_beginning(&self) -> bool {
252        self.due_at_beginning
253    }
254
255    pub fn payment(&self) -> f64 {
256        self.payment
257    }
258
259    pub fn sum_of_payments(&self) -> f64 {
260        self.sum_of_payments
261    }
262
263    pub fn sum_of_interest(&self) -> f64 {
264        self.sum_of_interest
265    }
266
267    pub fn formula(&self) -> &str {
268        &self.formula
269    }
270
271    pub fn symbolic_formula(&self) -> &str {
272        &self.symbolic_formula
273    }
274}
275
276/*
277impl Debug for CashflowSolution {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        write!(f, "{{{}{}{}{}{}{}{}{}{}{}{}\n}}",
280               &format!("\n\tcalculated_field: {}", self.calculated_field.to_string().magenta()),
281               &format!("\n\trate (r): {}", format!("{:?}", self.rate).yellow()),
282               &format!("\n\tperiods (n): {}", self.periods.to_string().yellow()),
283               &format!("\n\tpresent_value (pv): {}", self.present_value),
284               &format!("\n\tfuture_value (fv): {}", self.future_value),
285               &format!("\n\tdue_at_beginning: {}", self.due_at_beginning),
286               //    if self.calculated_field.is_net_present_value() { format!("\n\tcashflow: {}", self.cashflow.to_string().red()) } else { "".to_string() },
287               //    if self.calculated_field.is_net_present_value() { format!("\n\tcashflow_0: {}", self.cashflow_0.to_string().red()) } else { "".to_string() },
288               &format!("\n\tpayment (pmt): {}", if self.calculated_field.is_payment() || self.calculated_field.is_payment_due() { self.payment.to_string().green() } else { self.payment.to_string().normal() }),
289               &format!("\n\tsum_of_payments: {}", self.sum_of_payments),
290               &format!("\n\tsum_of_interest: {}", self.sum_of_interest),
291               &format!("\n\tformula: {:?}", self.formula),
292               &format!("\n\tsymbolic_formula: {:?}", self.symbolic_formula),
293               // &format!("input_in_percent: {:.6}%", self.input_in_percent),
294               // &format!("output: {}", self.output.to_string().green()),
295        )
296    }
297}
298*/
299
300#[derive(Clone, Debug)]
301pub struct CashflowSeries(Vec<CashflowPeriod>);
302
303impl CashflowSeries {
304    pub(crate) fn new(series: Vec<CashflowPeriod>) -> Self {
305        Self { 0: series }
306    }
307
308    pub fn filter<P>(&self, predicate: P) -> Self
309    where
310        P: Fn(&&CashflowPeriod) -> bool,
311    {
312        Self {
313            0: self.iter().filter(|x| predicate(x)).cloned().collect(),
314        }
315    }
316
317    pub fn print_table(&self, include_running_totals: bool, include_remaining_amounts: bool) {
318        self.print_table_locale_opt(
319            include_running_totals,
320            include_remaining_amounts,
321            None,
322            None,
323        );
324    }
325
326    pub fn print_table_locale(
327        &self,
328        include_running_totals: bool,
329        include_remaining_amounts: bool,
330        locale: &num_format::Locale,
331        precision: usize,
332    ) {
333        self.print_table_locale_opt(
334            include_running_totals,
335            include_remaining_amounts,
336            Some(locale),
337            Some(precision),
338        );
339    }
340
341    fn print_table_locale_opt(
342        &self,
343        include_running_totals: bool,
344        include_remaining_amounts: bool,
345        locale: Option<&num_format::Locale>,
346        precision: Option<usize>,
347    ) {
348        let columns = columns_with_strings(&[
349            ("period", "i", true),
350            ("payments_to_date", "f", include_running_totals),
351            ("payments_remaining", "f", include_remaining_amounts),
352            ("principal", "f", true),
353            ("principal_to_date", "f", include_running_totals),
354            ("principal_remaining", "f", include_remaining_amounts),
355            ("interest", "f", true),
356            ("interest_to_date", "f", include_running_totals),
357            ("interest_remaining", "f", include_remaining_amounts),
358        ]);
359        let data = self
360            .iter()
361            .map(|entry| {
362                vec![
363                    entry.period.to_string(),
364                    entry.payments_to_date.to_string(),
365                    entry.payments_remaining.to_string(),
366                    entry.principal.to_string(),
367                    entry.principal_to_date.to_string(),
368                    entry.principal_remaining.to_string(),
369                    entry.interest.to_string(),
370                    entry.interest_to_date.to_string(),
371                    entry.interest_remaining.to_string(),
372                ]
373            })
374            .collect::<Vec<_>>();
375        print_table_locale_opt(&columns, data, locale, precision);
376    }
377
378    pub fn print_ab_comparison(
379        &self,
380        other: &CashflowSeries,
381        include_running_totals: bool,
382        include_remaining_amounts: bool,
383    ) {
384        self.print_ab_comparison_locale_opt(
385            other,
386            include_running_totals,
387            include_remaining_amounts,
388            None,
389            None,
390        );
391    }
392
393    pub fn print_ab_comparison_locale(
394        &self,
395        other: &CashflowSeries,
396        include_running_totals: bool,
397        include_remaining_amounts: bool,
398        locale: &num_format::Locale,
399        precision: usize,
400    ) {
401        self.print_ab_comparison_locale_opt(
402            other,
403            include_running_totals,
404            include_remaining_amounts,
405            Some(locale),
406            Some(precision),
407        );
408    }
409
410    pub(crate) fn print_ab_comparison_locale_opt(
411        &self,
412        other: &CashflowSeries,
413        include_running_totals: bool,
414        include_remaining_amounts: bool,
415        locale: Option<&num_format::Locale>,
416        precision: Option<usize>,
417    ) {
418        let columns = columns_with_strings(&[
419            ("period", "i", true),
420            ("payment_a", "f", true),
421            ("payment_b", "f", true),
422            ("pmt_to_date_a", "f", include_running_totals),
423            ("pmt_to_date_b", "f", include_running_totals),
424            // ("pmt_remaining_a", "f", include_remaining_amounts), ("pmt_remaining_b", "f", include_remaining_amounts),
425            ("pmt_remaining_a", "f", false),
426            ("pmt_remaining_b", "f", false),
427            ("principal_a", "f", true),
428            ("principal_b", "f", true),
429            ("princ_to_date_a", "f", include_running_totals),
430            ("princ_to_date_b", "f", include_running_totals),
431            ("princ_remaining_a", "f", include_remaining_amounts),
432            ("princ_remaining_b", "f", include_remaining_amounts),
433            ("interest_a", "f", false),
434            ("interest_b", "f", false),
435            ("int_to_date_a", "f", include_running_totals),
436            ("int_to_date_b", "f", include_running_totals),
437            ("int_remaining_a", "f", false),
438            ("int_remaining_b", "f", false),
439        ]);
440        let mut data = vec![];
441        let rows = max(self.len(), other.len());
442        for row_index in 0..rows {
443            data.push(vec![
444                (row_index + 1).to_string(),
445                self.get(row_index)
446                    .map_or("".to_string(), |x| x.payment.to_string()),
447                other
448                    .get(row_index)
449                    .map_or("".to_string(), |x| x.payment.to_string()),
450                self.get(row_index)
451                    .map_or("".to_string(), |x| x.payments_to_date.to_string()),
452                other
453                    .get(row_index)
454                    .map_or("".to_string(), |x| x.payments_to_date.to_string()),
455                self.get(row_index)
456                    .map_or("".to_string(), |x| x.payments_remaining.to_string()),
457                other
458                    .get(row_index)
459                    .map_or("".to_string(), |x| x.payments_remaining.to_string()),
460                self.get(row_index)
461                    .map_or("".to_string(), |x| x.principal.to_string()),
462                other
463                    .get(row_index)
464                    .map_or("".to_string(), |x| x.principal.to_string()),
465                self.get(row_index)
466                    .map_or("".to_string(), |x| x.principal_to_date.to_string()),
467                other
468                    .get(row_index)
469                    .map_or("".to_string(), |x| x.principal_to_date.to_string()),
470                self.get(row_index)
471                    .map_or("".to_string(), |x| x.principal_remaining.to_string()),
472                other
473                    .get(row_index)
474                    .map_or("".to_string(), |x| x.principal_remaining.to_string()),
475                self.get(row_index)
476                    .map_or("".to_string(), |x| x.interest.to_string()),
477                other
478                    .get(row_index)
479                    .map_or("".to_string(), |x| x.interest.to_string()),
480                self.get(row_index)
481                    .map_or("".to_string(), |x| x.interest_to_date.to_string()),
482                other
483                    .get(row_index)
484                    .map_or("".to_string(), |x| x.interest_to_date.to_string()),
485                self.get(row_index)
486                    .map_or("".to_string(), |x| x.interest_remaining.to_string()),
487                other
488                    .get(row_index)
489                    .map_or("".to_string(), |x| x.interest_remaining.to_string()),
490            ]);
491        }
492        print_table_locale_opt(&columns, data, locale, precision);
493    }
494}
495
496impl Deref for CashflowSeries {
497    type Target = Vec<CashflowPeriod>;
498
499    fn deref(&self) -> &Self::Target {
500        &self.0
501    }
502}
503
504#[derive(Clone, Debug)]
505pub struct CashflowPeriod {
506    period: u32,
507    rate: f64,
508    due_at_beginning: bool,
509    // pub cashflow: f64,
510    // pub cashflow_0: f64,
511    payment: f64,
512    payments_to_date: f64,
513    payments_remaining: f64,
514    principal: f64,
515    principal_to_date: f64,
516    principal_remaining: f64,
517    interest: f64,
518    interest_to_date: f64,
519    interest_remaining: f64,
520    formula: String,
521    symbolic_formula: String,
522    // pub input_in_percent: String,
523}
524
525impl CashflowPeriod {
526    pub(crate) fn new(
527        period: u32,
528        rate: f64,
529        due_at_beginning: bool,
530        payment: f64,
531        payments_to_date: f64,
532        payments_remaining: f64,
533        principal: f64,
534        principal_to_date: f64,
535        principal_remaining: f64,
536        interest: f64,
537        interest_to_date: f64,
538        interest_remaining: f64,
539        formula: String,
540        symbolic_formula: String,
541    ) -> Self {
542        Self {
543            period,
544            rate,
545            due_at_beginning,
546            payment,
547            payments_to_date,
548            payments_remaining,
549            principal,
550            principal_to_date,
551            principal_remaining,
552            interest,
553            interest_to_date,
554            interest_remaining,
555            formula,
556            symbolic_formula,
557        }
558    }
559
560    pub fn rate(&self) -> f64 {
561        self.rate
562    }
563
564    pub fn period(&self) -> u32 {
565        self.period
566    }
567
568    pub fn payment(&self) -> f64 {
569        self.payment
570    }
571
572    pub fn payments_to_date(&self) -> f64 {
573        self.payments_to_date
574    }
575
576    pub fn payments_remaining(&self) -> f64 {
577        self.payments_remaining
578    }
579
580    pub fn principal(&self) -> f64 {
581        self.principal
582    }
583
584    pub fn principal_to_date(&self) -> f64 {
585        self.principal_to_date
586    }
587
588    pub fn principal_remaining(&self) -> f64 {
589        self.principal_remaining
590    }
591
592    pub fn interest(&self) -> f64 {
593        self.interest
594    }
595
596    pub fn interest_to_date(&self) -> f64 {
597        self.interest_to_date
598    }
599
600    pub fn interest_remaining(&self) -> f64 {
601        self.interest_remaining
602    }
603
604    pub fn due_at_beginning(&self) -> bool {
605        self.due_at_beginning
606    }
607
608    pub fn formula(&self) -> &str {
609        &self.formula
610    }
611
612    pub fn symbolic_formula(&self) -> &str {
613        &self.symbolic_formula
614    }
615
616    pub fn print_flat(&self, precision: usize) {
617        println!(
618            "CashflowPeriod = {{ {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} }}",
619            &format!("period: {}", self.period),
620            &format!("due_at_beginning: {}", self.due_at_beginning),
621            &format!("payment: {:.prec$}", self.payment, prec = precision),
622            &format!(
623                "payments_to_date: {:.prec$}",
624                self.payments_to_date,
625                prec = precision
626            ),
627            &format!(
628                "payments_remaining: {:.prec$}",
629                self.payments_remaining,
630                prec = precision
631            ),
632            &format!("principal: {:.prec$}", self.principal, prec = precision),
633            &format!(
634                "principal_to_date: {:.prec$}",
635                self.principal_to_date,
636                prec = precision
637            ),
638            &format!(
639                "principal_remaining: {:.prec$}",
640                self.principal_remaining,
641                prec = precision
642            ),
643            &format!("interest: {:.prec$}", self.interest, prec = precision),
644            &format!(
645                "interest_to_date: {:.prec$}",
646                self.interest_to_date,
647                prec = precision
648            ),
649            &format!(
650                "interest_remaining: {:.prec$}",
651                self.interest_remaining,
652                prec = precision
653            ),
654            &format!("formula: {:?}", self.formula),
655            &format!("symbolic_formula: {:?}", self.symbolic_formula)
656        );
657    }
658}
659
660// pub fn print_series_filtered(series: &[TvmPeriod], filter: )
661
662/*
663pub fn print_series_table(series: &[CashflowPeriod], precision: usize) {
664    if series.len() == 0 {
665        return;
666    }
667    let period_width = max("period".len(), series.iter().map(|x| x.period().to_string().len()).max().unwrap());
668    let payments_to_date_width = max("payments_to_date".len(), series.iter().map(|x| format!("{:.prec$}", x.payments_to_date(), prec = precision).len()).max().unwrap());
669    let payments_remaining_width = max("payments_remaining".len(), series.iter().map(|x| format!("{:.prec$}", x.payments_remaining(), prec = precision).len()).max().unwrap());
670    let principal_width = max("principal_width".len(), series.iter().map(|x| format!("{:.prec$}", x.principal(), prec = precision).len()).max().unwrap());
671    let principal_to_date_width = max("principal_to_date".len(), series.iter().map(|x| format!("{:.prec$}", x.principal_to_date(), prec = precision).len()).max().unwrap());
672    let principal_remaining_width = max("principal_remaining".len(), series.iter().map(|x| format!("{:.prec$}", x.principal_remaining(), prec = precision).len()).max().unwrap());
673    let interest_width = max("interest".len(), series.iter().map(|x| format!("{:.prec$}", x.interest(), prec = precision).len()).max().unwrap());
674    let interest_to_date_width = max("interest_to_date".len(), series.iter().map(|x| format!("{:.prec$}", x.interest_to_date(), prec = precision).len()).max().unwrap());
675    let interest_remaining_width = max("interest_remaining".len(), series.iter().map(|x| format!("{:.prec$}", x.interest_remaining(), prec = precision).len()).max().unwrap());
676    println!("\ndue_at_beginning: {}", series[0].due_at_beginning);
677    println!("payment: {:.prec$}", series[0].payment, prec = precision);
678    println!("{:>pe$}  {:>pmtd$}  {:>pmr$}  {:>pr$}  {:>prtd$}  {:>prr$}  {:>i$}  {:>itd$}  {:>ir$}",
679             "period", "payments_to_date", "payments_remaining", "principal", "principal_to_date", "principal_remaining", "interest", "interest_to_date", "interest_remaining",
680             pe = period_width, pmtd = payments_to_date_width, pmr = payments_remaining_width,
681             pr = principal_width, prtd = principal_to_date_width, prr = principal_remaining_width,
682             i = interest_width, itd = interest_to_date_width, ir = interest_remaining_width);
683    println!("{}  {}  {}  {}  {}  {}  {}  {}  {}",
684             "-".repeat(period_width), "-".repeat(payments_to_date_width), "-".repeat(payments_remaining_width),
685             "-".repeat(principal_width), "-".repeat(principal_to_date_width), "-".repeat(principal_remaining_width),
686             "-".repeat(interest_width), "-".repeat(interest_to_date_width), "-".repeat(interest_remaining_width));
687    for entry in series.iter() {
688        println!("{:>pe$}  {:>pmtd$.prec$}  {:>pmr$.prec$}  {:>pr$.prec$}  {:>prtd$.prec$}  {:>prr$.prec$}  {:>i$.prec$}  {:>itd$.prec$}  {:>ir$.prec$}",
689                 entry.period(), entry.payments_to_date(), entry.payments_remaining(),
690                 entry.principal(), entry.principal_to_date(), entry.principal_remaining(),
691                 entry.interest(), entry.interest_to_date(), entry.interest_remaining(),
692                 pe = period_width, pmtd = payments_to_date_width, pmr = payments_remaining_width,
693                 pr = principal_width, prtd = principal_to_date_width, prr = principal_remaining_width,
694                 i = interest_width, itd = interest_to_date_width, ir = interest_remaining_width, prec = precision);
695    }
696}
697*/
698
699/*
700pub fn print_series_table_locale(series: &[CashflowPeriod], locale: &num_format::Locale, precision: usize) {
701    if series.len() == 0 {
702        return;
703    }
704    let period_width = max("period".len(), series.iter().map(|x| format_int_locale(x.period(), locale).len()).max().unwrap());
705    let payments_to_date_width = max("payments_to_date".len(), series.iter().map(|x| format_float_locale(x.payments_to_date(), locale, precision).len()).max().unwrap());
706    let payments_remaining_width = max("payments_remaining".len(), series.iter().map(|x| format_float_locale(x.payments_remaining(), locale, precision).len()).max().unwrap());
707    let principal_width = max("principal".len(), series.iter().map(|x| format_float_locale(x.principal(), locale, precision).len()).max().unwrap());
708    let principal_to_date_width = max("principal_to_date".len(), series.iter().map(|x| format_float_locale(x.principal_to_date(), locale, precision).len()).max().unwrap());
709    let principal_remaining_width = max("principal_remaining".len(), series.iter().map(|x| format_float_locale(x.principal_remaining(), locale, precision).len()).max().unwrap());
710    let interest_width = max("interest".len(), series.iter().map(|x| format_float_locale(x.interest(), locale, precision).len()).max().unwrap());
711    let interest_to_date_width = max("interest_to_date".len(), series.iter().map(|x| format_float_locale(x.interest_to_date(), locale, precision).len()).max().unwrap());
712    let interest_remaining_width = max("interest_remaining".len(), series.iter().map(|x| format_float_locale(x.interest_remaining(), locale, precision).len()).max().unwrap());
713    println!("\ndue_at_beginning: {}", series[0].due_at_beginning);
714    println!("payment: {:.prec$}", series[0].payment, prec = precision);
715    println!("{:>pe$}  {:>pmtd$}  {:>pmr$}  {:>pr$}  {:>prtd$}  {:>prr$}  {:>i$}  {:>itd$}  {:>ir$}",
716             "period", "payments_to_date", "payments_remaining", "principal", "principal_to_date", "principal_remaining", "interest", "interest_to_date", "interest_remaining",
717             pe = period_width, pmtd = payments_to_date_width, pmr = payments_remaining_width,
718             pr = principal_width, prtd = principal_to_date_width, prr = principal_remaining_width,
719             i = interest_width, itd = interest_to_date_width, ir = interest_remaining_width);
720    println!("{}  {}  {}  {}  {}  {}  {}  {}  {}",
721             "-".repeat(period_width), "-".repeat(payments_to_date_width), "-".repeat(payments_remaining_width),
722             "-".repeat(principal_width), "-".repeat(principal_to_date_width), "-".repeat(principal_remaining_width),
723             "-".repeat(interest_width), "-".repeat(interest_to_date_width), "-".repeat(interest_remaining_width));
724    for entry in series.iter() {
725        println!("{:>pe$}  {:>pmtd$}  {:>pmr$}  {:>pr$}  {:>prtd$}  {:>prr$}  {:>i$}  {:>itd$}  {:>ir$}",
726                 format_int_locale(entry.period(), locale), format_float_locale(entry.payments_to_date(), locale, precision), format_float_locale(entry.payments_remaining(), locale, precision),
727                 format_float_locale(entry.principal(), locale, precision), format_float_locale(entry.principal_to_date(), locale, precision), format_float_locale(entry.principal_remaining(), locale, precision),
728                 format_float_locale(entry.interest(), locale, precision), format_float_locale(entry.interest_to_date(), locale, precision), format_float_locale(entry.interest_remaining(), locale, precision),
729                 pe = period_width, pmtd = payments_to_date_width, pmr = payments_remaining_width,
730                 pr = principal_width, prtd = principal_to_date_width, prr = principal_remaining_width,
731                 i = interest_width, itd = interest_to_date_width, ir = interest_remaining_width);
732    }
733}
734*/
735
736/*
737pub fn print_series_table_filtered(series: &[CashflowPeriod], predicate: P, precision: usize)
738    where P: FnMut(&CashflowPeriod) -> bool
739{
740}
741*/