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