1#![allow(dead_code)]
87
88use itertools::Itertools;
89use num_format::{Locale, ToFormattedString};
90
91pub use float_cmp;
92pub use num_format;
93
94pub mod util;
99#[doc(inline)]
100pub use util::{FinanceError, FinanceResult, Money, PeriodLength, Periods, PositivePrice, Rate};
101
102pub mod round;
103#[doc(inline)]
104pub use round::*;
105
106pub mod tvm;
111#[doc(inline)]
112pub use tvm::*;
113
114pub mod cashflow;
115#[doc(inline)]
116pub use cashflow::*;
117
118pub mod convert_rate;
122#[doc(inline)]
123pub use convert_rate::*;
124
125pub mod tvm_convert_rate;
126#[doc(inline)]
127pub use tvm_convert_rate::*;
128
129pub mod amortization;
134#[doc(inline)]
135pub use amortization::*;
136
137pub mod returns;
138#[doc(inline)]
139pub use returns::*;
140
141pub mod stocks;
142#[doc(inline)]
143pub use stocks::*;
144
145use std::cmp::max;
146use std::fmt::{Debug, Error, Formatter};
147
148#[macro_export]
174macro_rules! is_approx_equal {
175 ( $x1:expr, $x2:expr ) => {
176 float_cmp::approx_eq!(f64, $x1, $x2, epsilon = 0.000001, ulps = 20)
177 };
178}
179
180#[macro_export]
181macro_rules! assert_approx_equal {
182 ( $x1:expr, $x2:expr ) => {
183 assert!(float_cmp::approx_eq!(
184 f64,
185 $x1,
186 $x2,
187 epsilon = 0.000001,
188 ulps = 20
189 ));
190 };
191}
192
193#[macro_export]
194macro_rules! assert_same_sign_or_zero {
195 ( $x1:expr, $x2:expr ) => {
196 assert!(
197 is_approx_equal!($x1, 0.0)
198 || is_approx_equal!($x2, 0.0)
199 || ($x1 > 0.0 && $x2 > 0.0)
200 || ($x1 < -0.0 && $x2 < -0.0)
201 );
202 };
203}
204
205#[macro_export]
206macro_rules! is_approx_equal_symmetry_test {
207 ( $x1:expr, $x2:expr ) => {
208 if (($x1 > 0.000001 && $x1 < 1_000_000.0) || ($x1 < -0.000001 && $x1 > -1_000_000.0))
209 && (($x2 > 0.000001 && $x2 < 1_000_000.0) || ($x2 < -0.000001 && $x2 > -1_000_000.0))
210 {
211 float_cmp::approx_eq!(f64, $x1, $x2, epsilon = 0.00000001, ulps = 2)
212 } else {
213 true
214 }
215 };
216}
217
218#[macro_export]
219macro_rules! assert_approx_equal_symmetry_test {
220 ( $x1:expr, $x2:expr ) => {
221 if (($x1 > 0.000001 && $x1 < 1_000_000.0) || ($x1 < -0.000001 && $x1 > -1_000_000.0))
222 && (($x2 > 0.000001 && $x2 < 1_000_000.0) || ($x2 < -0.000001 && $x2 > -1_000_000.0))
223 {
224 assert!(float_cmp::approx_eq!(
225 f64,
226 $x1,
227 $x2,
228 epsilon = 0.00000001,
229 ulps = 2
230 ));
231 }
232 };
233}
234
235#[macro_export]
236macro_rules! assert_rounded_2 {
237 ( $x1:expr, $x2:expr ) => {
238 assert_eq!(
239 ($x1 * 100.0f64).round() / 100.0,
240 ($x2 * 100.0f64).round() / 100.0
241 );
242 };
243}
244
245#[macro_export]
246macro_rules! assert_rounded_4 {
247 ( $x1:expr, $x2:expr ) => {
248 assert_eq!(
249 ($x1 * 10_000.0f64).round() / 10_000.0,
250 ($x2 * 10_000.0f64).round() / 10_000.0
251 );
252 };
253}
254
255#[macro_export]
256macro_rules! assert_rounded_6 {
257 ( $x1:expr, $x2:expr ) => {
258 assert_eq!(
259 ($x1 * 1_000_000.0f64).round() / 1_000_000.0,
260 ($x2 * 1_000_000f64).round() / 1_000_000.0
261 );
262 };
263}
264
265#[macro_export]
266macro_rules! assert_rounded_8 {
267 ( $x1:expr, $x2:expr ) => {
268 assert_eq!(
269 ($x1 * 100_000_000.0f64).round() / 100_000_000.0,
270 ($x2 * 100_000_000.0f64).round() / 100_000_000.0
271 );
272 };
273}
274
275#[macro_export]
276macro_rules! repeating_vec {
277 ( $x1:expr, $x2:expr ) => {{
278 let mut repeats = vec![];
279 for _i in 0..$x2 {
280 repeats.push($x1);
281 }
282 repeats
283 }};
284}
285
286fn decimal_separator_locale_opt(locale: Option<&Locale>) -> String {
287 match locale {
288 Some(locale) => locale.decimal().to_string(),
289 None => ".".to_string(),
290 }
291}
292
293fn minus_sign_locale_opt(val: f64, locale: Option<&Locale>) -> String {
294 if val.is_sign_negative() {
295 match locale {
296 Some(locale) => locale.minus_sign().to_string(),
297 None => "-".to_string(),
298 }
299 } else {
300 "".to_string()
301 }
302}
303
304pub(crate) fn parse_and_format_int(val: &str) -> String {
305 parse_and_format_int_locale_opt(val, None)
306}
307
308pub(crate) fn parse_and_format_int_locale_opt(val: &str, locale: Option<&Locale>) -> String {
309 let float_val: f64 = val.parse().unwrap();
310 if float_val.is_finite() {
311 let int_val: i128 = val.parse().unwrap();
312 format_int_locale_opt(int_val, locale)
313 } else {
314 val.to_string()
318 }
319}
320
321pub(crate) fn format_int<T>(val: T) -> String
322where
323 T: ToFormattedString,
324{
325 format_int_locale_opt(val, None)
326}
327
328pub(crate) fn format_int_locale_opt<T>(val: T, locale: Option<&Locale>) -> String
329where
330 T: ToFormattedString,
331{
332 match locale {
333 Some(locale) => val.to_formatted_string(locale),
334 None => val.to_formatted_string(&Locale::en).replace(",", "_"),
335 }
336}
337
338pub(crate) fn format_float<T>(val: T) -> String
339where
340 T: Into<f64>,
341{
342 format_float_locale_opt(val, None, None)
343}
344
345pub(crate) fn format_rate<T>(val: T) -> String
346where
347 T: Into<f64>,
348{
349 format_float_locale_opt(val, None, Some(6))
350}
351
352pub(crate) fn format_float_locale_opt<T>(
353 val: T,
354 locale: Option<&Locale>,
355 precision: Option<usize>,
356) -> String
357where
358 T: Into<f64>,
359{
360 let precision = precision.unwrap_or(4);
361 let val = val.into();
362 if val.is_finite() {
363 if precision == 0 {
367 format_int_locale_opt(val.round() as i128, locale)
368 } else {
369 let scale = 10_f64.powi(precision as i32);
372 let rounded_abs = (val.abs() * scale).round() / scale;
373 let left = format_int_locale_opt(rounded_abs.trunc() as i128, locale);
374 let frac_digits = (rounded_abs.fract() * scale).round() as u64;
375 let right = format!("{:0>width$}", frac_digits, width = precision);
376 let minus_sign = minus_sign_locale_opt(val, locale);
377 format!(
378 "{}{}{}{}",
379 minus_sign,
380 left,
381 decimal_separator_locale_opt(locale),
382 right
383 )
384 }
385 } else {
386 format!("{:?}", val)
387 }
388}
389
390pub(crate) fn print_table_locale_opt(
391 columns: &[(String, String, bool)],
392 mut data: Vec<Vec<String>>,
393 locale: Option<&num_format::Locale>,
394 precision: Option<usize>,
395) {
396 if columns.is_empty() || data.is_empty() {
397 return;
398 }
399
400 let column_separator = " ";
401
402 let column_count = data[0].len();
403
404 for row_index in 0..data.len() {
405 for col_index in 0..column_count {
406 let visible = columns[col_index].2;
407 if visible {
408 if !data[row_index][col_index].is_empty() {
411 let col_type = columns[col_index].1.to_lowercase();
412 if col_type != "s" {
414 if col_type == "f" || col_type == "r" {
415 let precision = if col_type == "f" {
416 precision
417 } else {
418 precision_opt_set_min(precision, 6)
419 };
420 if let Ok(n) = data[row_index][col_index].parse::<f64>() {
422 data[row_index][col_index] =
423 format_float_locale_opt(n, locale, precision);
424 }
425 } else if col_type == "i" {
426 data[row_index][col_index] = parse_and_format_int_locale_opt(
427 &data[row_index][col_index],
428 locale,
429 );
430 }
431 }
433 }
434 }
435 }
436 }
437
438 let mut column_widths = vec![];
439 for col_index in 0..column_count {
440 let visible = columns[col_index].2;
441 let width = if visible {
442 let mut width = columns[col_index].0.len();
443 for row in &data {
444 width = max(width, row[col_index].len());
445 }
446 width
447 } else {
448 0
449 };
450 column_widths.push(width);
451 }
452
453 let header_line = columns
454 .iter()
455 .enumerate()
456 .map(|(col_index, (header, _type, visible))| {
457 if *visible {
458 format!(
459 "{:>width$}{}",
460 header,
461 column_separator,
462 width = column_widths[col_index]
463 )
464 } else {
465 "".to_string()
466 }
467 })
468 .join("");
469 println!("\n{}", header_line.trim_end());
470
471 let dash_line = columns
472 .iter()
473 .enumerate()
474 .map(|(col_index, (_header, _type, visible))| {
475 if *visible {
476 format!(
477 "{}{}",
478 "-".repeat(column_widths[col_index]),
479 column_separator
480 )
481 } else {
482 "".to_string()
483 }
484 })
485 .join("");
486 println!("{}", dash_line.trim_end());
487
488 for row in data.iter() {
489 let value_line = row
490 .iter()
491 .enumerate()
492 .map(|(col_index, value)| {
493 let visible = columns[col_index].2;
494 if visible {
495 format!(
496 "{:>width$}{}",
497 value,
498 column_separator,
499 width = column_widths[col_index]
500 )
501 } else {
502 "".to_string()
503 }
504 })
505 .join("");
506 println!("{}", value_line.trim_end());
507 }
508}
509
510pub(crate) fn print_ab_comparison_values_string(field_name: &str, value_a: &str, value_b: &str) {
511 print_ab_comparison_values_internal(field_name, value_a, value_b, false);
512}
513
514pub(crate) fn print_ab_comparison_values_int(
515 field_name: &str,
516 value_a: i128,
517 value_b: i128,
518 locale: Option<&num_format::Locale>,
519) {
520 print_ab_comparison_values_internal(
521 field_name,
522 &format_int_locale_opt(value_a, locale),
523 &format_int_locale_opt(value_b, locale),
524 true,
525 );
526}
527
528pub(crate) fn print_ab_comparison_values_float(
529 field_name: &str,
530 value_a: f64,
531 value_b: f64,
532 locale: Option<&num_format::Locale>,
533 precision: Option<usize>,
534) {
535 print_ab_comparison_values_internal(
536 field_name,
537 &format_float_locale_opt(value_a, locale, precision),
538 &format_float_locale_opt(value_b, locale, precision),
539 true,
540 );
541}
542
543pub(crate) fn print_ab_comparison_values_rate(
544 field_name: &str,
545 value_a: f64,
546 value_b: f64,
547 locale: Option<&num_format::Locale>,
548 precision: Option<usize>,
549) {
550 let precision = precision_opt_set_min(precision, 6);
551 print_ab_comparison_values_float(field_name, value_a, value_b, locale, precision);
552}
553
554pub(crate) fn print_ab_comparison_values_bool(field_name: &str, value_a: bool, value_b: bool) {
555 print_ab_comparison_values_internal(
556 field_name,
557 &format!("{:?}", value_a),
558 &format!("{:?}", value_b),
559 false,
560 );
561}
562
563fn print_ab_comparison_values_internal(
564 field_name: &str,
565 value_a: &str,
566 value_b: &str,
567 right_align: bool,
568) {
569 if value_a == value_b {
570 println!("{}: {}", field_name, value_a);
571 } else if right_align {
572 let width = max(value_a.len(), value_b.len());
573 println!("{} a: {:>width$}", field_name, value_a, width = width);
574 println!("{} b: {:>width$}", field_name, value_b, width = width);
575 } else {
576 println!("{} a: {}", field_name, value_a);
577 println!("{} b: {}", field_name, value_b);
578 }
579}
580
581fn precision_opt_set_min(precision: Option<usize>, min: usize) -> Option<usize> {
582 Some(match precision {
583 Some(precision) => precision.max(min),
584 None => 6,
585 })
586}
587
588#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
590pub enum ValueType {
591 Payment,
592 Rate,
593}
594
595impl ValueType {
596 pub fn is_payment(&self) -> bool {
597 matches!(self, ValueType::Payment)
598 }
599
600 pub fn is_rate(&self) -> bool {
601 matches!(self, ValueType::Rate)
602 }
603}
604
605impl std::fmt::Display for ValueType {
606 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
607 match self {
608 ValueType::Payment => write!(f, "Payment"),
609 ValueType::Rate => write!(f, "Rate"),
610 }
611 }
612}
613
614#[derive(Clone, Debug)]
639pub enum Schedule {
640 Repeating {
641 value_type: ValueType,
642 value: f64,
643 periods: u32,
644 },
645 Custom {
646 value_type: ValueType,
647 values: Vec<f64>,
648 },
649}
650
651impl Schedule {
652 pub fn new_repeating(value_type: ValueType, value: f64, periods: u32) -> FinanceResult<Self> {
657 crate::util::error::require_finite("value", value)?;
658 Ok(Schedule::Repeating {
659 value_type,
660 value,
661 periods,
662 })
663 }
664
665 pub fn new_custom(value_type: ValueType, values: &[f64]) -> FinanceResult<Self> {
671 if values.is_empty() {
672 return Err(FinanceError::EmptyInput { what: "values" });
673 }
674 for (i, &value) in values.iter().enumerate() {
675 if !value.is_finite() {
676 return Err(FinanceError::NonFinite {
677 field: "values",
678 value,
679 });
680 }
681 let _ = i;
682 }
683 Ok(Schedule::Custom {
684 value_type,
685 values: values.to_vec(),
686 })
687 }
688
689 pub fn is_payment(&self) -> bool {
690 self.value_type().is_payment()
691 }
692
693 pub fn is_rate(&self) -> bool {
694 self.value_type().is_rate()
695 }
696
697 pub fn value_type(&self) -> &ValueType {
698 match self {
699 Schedule::Repeating { value_type, .. } => value_type,
700 Schedule::Custom { value_type, .. } => value_type,
701 }
702 }
703
704 pub fn value(&self) -> Option<f64> {
706 match self {
707 Schedule::Repeating { value, .. } => Some(*value),
708 Schedule::Custom { .. } => None,
709 }
710 }
711
712 pub fn get(&self, index: usize) -> Option<f64> {
714 match self {
715 Schedule::Repeating { value, periods, .. } => {
716 if index < *periods as usize {
717 Some(*value)
718 } else {
719 None
720 }
721 }
722 Schedule::Custom { values, .. } => values.get(index).copied(),
723 }
724 }
725
726 pub fn max(&self) -> Option<f64> {
728 match self {
729 Schedule::Repeating { value, .. } => Some(*value),
730 Schedule::Custom { values, .. } => {
731 if values.is_empty() {
732 None
733 } else {
734 Some(values.iter().cloned().fold(f64::NAN, f64::max))
735 }
736 }
737 }
738 }
739
740 pub fn len(&self) -> usize {
742 match self {
743 Schedule::Repeating { periods, .. } => *periods as usize,
744 Schedule::Custom { values, .. } => values.len(),
745 }
746 }
747
748 pub fn is_empty(&self) -> bool {
749 self.len() == 0
750 }
751}
752
753#[derive(Debug)]
754pub struct ScenarioList {
755 pub setup: String,
756 pub input_variable: TvmVariable,
757 pub output_variable: TvmVariable,
758 pub entries: Vec<ScenarioEntry>,
759}
760
761pub struct ScenarioEntry {
762 pub input: f64,
763 pub output: f64,
764 input_precision: usize,
765 output_precision: usize,
766}
767
768impl ScenarioList {
769 pub(crate) fn new(
770 setup: String,
771 input_variable: TvmVariable,
772 output_variable: TvmVariable,
773 entries: Vec<(f64, f64)>,
774 ) -> Self {
775 let input_precision = match input_variable {
776 TvmVariable::Periods => 0,
777 TvmVariable::Rate => 6,
778 _ => 4,
779 };
780 let output_precision = match output_variable {
781 TvmVariable::Periods => 0,
782 TvmVariable::Rate => 6,
783 _ => 4,
784 };
785 let entries = entries
786 .iter()
787 .map(|entry| ScenarioEntry::new(entry.0, entry.1, input_precision, output_precision))
788 .collect();
789 Self {
790 setup,
791 input_variable,
792 output_variable,
793 entries,
794 }
795 }
796
797 pub fn print_table(&self) {
798 self.print_table_locale_opt(None, None);
799 }
800
801 pub fn print_table_locale(&self, locale: &num_format::Locale, precision: usize) {
802 self.print_table_locale_opt(Some(locale), Some(precision));
803 }
804
805 fn print_table_locale_opt(
806 &self,
807 locale: Option<&num_format::Locale>,
808 precision: Option<usize>,
809 ) {
810 let columns = vec![
811 self.input_variable.table_column_spec(true),
812 self.output_variable.table_column_spec(true),
813 ];
814 let data = self
816 .entries
817 .iter()
818 .map(|entry| vec![entry.input.to_string(), entry.output.to_string()])
819 .collect::<Vec<_>>();
820 print_table_locale_opt(&columns, data, locale, precision);
821 }
822}
823
824impl ScenarioEntry {
825 pub(crate) fn new(
826 input: f64,
827 output: f64,
828 input_precision: usize,
829 output_precision: usize,
830 ) -> Self {
831 Self {
832 input,
833 output,
834 input_precision,
835 output_precision,
836 }
837 }
838}
839
840impl Debug for ScenarioEntry {
841 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
842 let input = format_float_locale_opt(self.input, None, Some(self.input_precision));
843 let output = format_float_locale_opt(self.output, None, Some(self.output_precision));
844 write!(f, "{{ input: {}, output: {} }}", input, output)
845 }
846}
847
848pub(crate) fn columns_with_strings(columns: &[(&str, &str, bool)]) -> Vec<(String, String, bool)> {
849 columns
850 .iter()
851 .map(|(label, data_type, visible)| (label.to_string(), data_type.to_string(), *visible))
852 .collect()
853}
854
855pub(crate) fn initialized_vector<L, V>(length: L, value: V) -> Vec<V>
856where
857 L: Into<usize>,
858 V: Copy,
859{
860 let mut v = vec![];
861 for _ in 0..length.into() {
862 v.push(value);
863 }
864 v
865}
866
867#[cfg(test)]
868mod tests {
869 use super::*;
870
871 #[test]
872 fn test_schedule_new_and_get() {
873 let s = Schedule::new_repeating(ValueType::Rate, 0.05, 3).unwrap();
874 assert_eq!(s.len(), 3);
875 assert_eq!(s.get(0), Some(0.05));
876 assert_eq!(s.get(3), None);
877 assert!(Schedule::new_repeating(ValueType::Rate, f64::NAN, 1).is_err());
878
879 let c = Schedule::new_custom(ValueType::Payment, &[1.0, 2.0]).unwrap();
880 assert_eq!(c.get(1), Some(2.0));
881 assert_eq!(c.get(2), None);
882 assert!(Schedule::new_custom(ValueType::Payment, &[]).is_err());
883 assert!(Schedule::new_custom(ValueType::Payment, &[1.0, f64::INFINITY]).is_err());
884 }
885
886 #[test]
887 fn test_assert_same_sign_or_zero_nominal() {
888 assert_same_sign_or_zero!(0.0, 0.0);
889 assert_same_sign_or_zero!(0.0, -0.0);
890 assert_same_sign_or_zero!(-0.0, 0.0);
891 assert_same_sign_or_zero!(-0.0, -0.0);
892 assert_same_sign_or_zero!(0.023, 0.023);
893 assert_same_sign_or_zero!(10.0, 0.023);
894 assert_same_sign_or_zero!(-0.000045, -100.0);
895 assert_same_sign_or_zero!(0.023, 0.0);
896 assert_same_sign_or_zero!(0.0, 0.023);
897 assert_same_sign_or_zero!(0.023, -0.0);
898 assert_same_sign_or_zero!(-0.0, 0.023);
899 assert_same_sign_or_zero!(-0.000045, -100.0);
900 assert_same_sign_or_zero!(-0.000045, 0.0);
901 assert_same_sign_or_zero!(0.0, -100.0);
902 assert_same_sign_or_zero!(-0.000045, -0.0);
903 assert_same_sign_or_zero!(-0.0, -100.0);
904 assert_same_sign_or_zero!(100.0, -0.00000000001864464138634503);
905 }
906
907 #[should_panic]
908 #[test]
909 fn test_assert_same_sign_or_zero_fail_diff_sign() {
910 assert_same_sign_or_zero!(-0.000045, 100.0);
911 }
912}