1#![allow(dead_code)]
74
75use num_format::{Locale, ToFormattedString};
76use itertools::Itertools;
77
78extern crate float_cmp;
79pub extern crate num_format;
80
81pub mod convert_rate;
82#[doc(inline)]
83pub use convert_rate::*;
84
85pub mod round;
86#[doc(inline)]
87pub use round::*;
88
89pub mod cashflow;
90#[doc(inline)]
91pub use cashflow::*;
92
93pub mod tvm;
94#[doc(inline)]
95pub use tvm::*;
96
97pub mod tvm_convert_rate;
98#[doc(inline)]
99pub use tvm_convert_rate::*;
100use std::cmp::max;
101use std::fmt::{Debug, Formatter, Error};
102
103#[macro_export]
129macro_rules! is_approx_equal {
130 ( $x1:expr, $x2:expr ) => {
131 float_cmp::approx_eq!(f64, $x1, $x2, epsilon = 0.000001, ulps = 20)
132 };
133}
134
135#[macro_export]
136macro_rules! assert_approx_equal {
137 ( $x1:expr, $x2:expr ) => {
138 assert!(float_cmp::approx_eq!(f64, $x1, $x2, epsilon = 0.000001, ulps = 20));
139 };
140}
141
142#[macro_export]
143macro_rules! assert_same_sign_or_zero {
144 ( $x1:expr, $x2:expr ) => {
145 assert!(
146 is_approx_equal!($x1, 0.0)
147 || is_approx_equal!($x2, 0.0)
148 || ($x1 > 0.0 && $x2 > 0.0)
149 || ($x1 < -0.0 && $x2 < -0.0)
150 );
151 };
152}
153
154#[macro_export]
155macro_rules! is_approx_equal_symmetry_test {
156 ( $x1:expr, $x2:expr ) => {
157 if (($x1 > 0.000001 && $x1 < 1_000_000.0) || ($x1 < -0.000001 && $x1 > -1_000_000.0)) && (($x2 > 0.000001 && $x2 < 1_000_000.0) || ($x2 < -0.000001 && $x2 > -1_000_000.0)) {
158 float_cmp::approx_eq!(f64, $x1, $x2, epsilon = 0.00000001, ulps = 2)
159 } else {
160 true
161 }
162 };
163}
164
165#[macro_export]
166macro_rules! assert_approx_equal_symmetry_test {
167 ( $x1:expr, $x2:expr ) => {
168 if (($x1 > 0.000001 && $x1 < 1_000_000.0) || ($x1 < -0.000001 && $x1 > -1_000_000.0)) && (($x2 > 0.000001 && $x2 < 1_000_000.0) || ($x2 < -0.000001 && $x2 > -1_000_000.0)) {
169 assert!(float_cmp::approx_eq!(f64, $x1, $x2, epsilon = 0.00000001, ulps = 2));
170 }
171 };
172}
173
174#[macro_export]
175macro_rules! assert_rounded_2 {
176 ( $x1:expr, $x2:expr ) => {
177 assert_eq!(($x1 * 100.0f64).round() / 100.0, ($x2 * 100.0f64).round() / 100.0);
178 };
179}
180
181#[macro_export]
182macro_rules! assert_rounded_4 {
183 ( $x1:expr, $x2:expr ) => {
184 assert_eq!(($x1 * 10_000.0f64).round() / 10_000.0, ($x2 * 10_000.0f64).round() / 10_000.0);
185 };
186}
187
188#[macro_export]
189macro_rules! assert_rounded_6 {
190 ( $x1:expr, $x2:expr ) => {
191 assert_eq!(($x1 * 1_000_000.0f64).round() / 1_000_000.0, ($x2 * 1_000_000f64).round() / 1_000_000.0);
192 };
193}
194
195#[macro_export]
196macro_rules! assert_rounded_8 {
197 ( $x1:expr, $x2:expr ) => {
198 assert_eq!(($x1 * 100_000_000.0f64).round() / 100_000_000.0, ($x2 * 100_000_000.0f64).round() / 100_000_000.0);
199 };
200}
201
202
203#[macro_export]
204macro_rules! repeating_vec {
205 ( $x1:expr, $x2:expr ) => {{
206 let mut repeats = vec![];
207 for _i in 0..$x2 {
208 repeats.push($x1);
209 }
210 repeats
211 }};
212}
213
214fn decimal_separator_locale_opt(locale: Option<&Locale>) -> String {
215 match locale {
216 Some(locale) => locale.decimal().to_string(),
217 None => ".".to_string(),
218 }
219}
220
221fn minus_sign_locale_opt(val: f64, locale: Option<&Locale>) -> String {
222 if val.is_sign_negative() {
223 match locale {
224 Some(locale) => locale.minus_sign().to_string(),
225 None => "-".to_string(),
226 }
227 } else {
228 "".to_string()
229 }
230}
231
232pub(crate) fn parse_and_format_int(val: &str) -> String {
233 parse_and_format_int_locale_opt(val, None)
234}
235
236pub(crate) fn parse_and_format_int_locale_opt(val: &str, locale: Option<&Locale>) -> String {
237 let float_val: f64 = val.parse().unwrap();
238 if float_val.is_finite() {
239 let int_val: i128 = val.parse().unwrap();
240 format_int_locale_opt(int_val, locale)
241 } else {
242 val.to_string()
246 }
247}
248
249pub(crate) fn format_int<T>(val: T) -> String
250 where T: ToFormattedString
251{
252 format_int_locale_opt(val, None)
253}
254
255pub(crate) fn format_int_locale_opt<T>(val: T, locale: Option<&Locale>) -> String
256 where T: ToFormattedString
257{
258 match locale {
259 Some(locale) => val.to_formatted_string(locale),
260 None => val.to_formatted_string(&Locale::en).replace(",", "_"),
261 }
262}
263
264pub(crate) fn format_float<T>(val: T) -> String
265 where T: Into<f64>
266{
267 format_float_locale_opt(val, None, None)
268}
269
270pub(crate) fn format_rate<T>(val: T) -> String
271 where T: Into<f64>
272{
273 format_float_locale_opt(val, None, Some(6))
274}
275
276pub(crate) fn format_float_locale_opt<T>(val: T, locale: Option<&Locale>, precision: Option<usize>) -> String
277 where T: Into<f64>
278{
279 let precision = precision.unwrap_or(4);
280 let val = val.into();
281 if val.is_finite() {
282 if precision == 0 {
284 format_int_locale_opt(val.round() as i128, locale)
285 } else {
286 let left = format_int_locale_opt(val.trunc().abs() as i128, locale);
287 let right = &format!("{:.*}", precision, val.fract().abs())[2..];
288 let minus_sign = minus_sign_locale_opt(val as f64, locale);
289 format!("{}{}{}{}", minus_sign, left, decimal_separator_locale_opt(locale), right)
290 }
291 } else {
292 format!("{:?}", val)
293 }
294}
295
296pub(crate) fn print_table_locale_opt(columns: &[(String, String, bool)], mut data: Vec<Vec<String>>, locale: Option<&num_format::Locale>, precision: Option<usize>) {
297 if columns.is_empty() || data.is_empty() {
298 return;
299 }
300
301 let column_separator = " ";
302
303 let column_count = data[0].len();
304
305 for row_index in 0..data.len() {
306 for col_index in 0..column_count {
307 let visible = columns[col_index].2;
308 if visible {
309 if !data[row_index][col_index].is_empty() {
312 let col_type = columns[col_index].1.to_lowercase();
313 if col_type != "s" {
315 data[row_index][col_index] = if col_type == "f" || col_type == "r" {
316 let precision = if col_type == "f" {
317 precision
318 } else {
319 precision_opt_set_min(precision, 6)
320 };
321 format_float_locale_opt(data[row_index][col_index].parse::<f64>().unwrap(), locale, precision)
322 } else if col_type == "i" {
323 parse_and_format_int_locale_opt(&data[row_index][col_index], locale)
325 } else {
326 panic!("Unexpected column type = \"{}\"", col_type)
327 }
328 }
329 }
330 }
331 }
332 }
333
334 let mut column_widths = vec![];
335 for col_index in 0..column_count {
336 let visible = columns[col_index].2;
337 let width = if visible {
338 let mut width = columns[col_index].0.len();
339 for row in &data {
340 width = max(width, row[col_index].len());
341 }
342 width
343 } else {
344 0
345 };
346 column_widths.push(width);
347 }
348
349 let header_line = columns.iter()
350 .enumerate()
351 .map(|(col_index, (header, _type, visible))|
352 if *visible {
353 format!("{:>width$}{}", header, column_separator, width = column_widths[col_index])
354 } else {
355 "".to_string()
356 }
357 )
358 .join("");
359 println!("\n{}", header_line.trim_end());
360
361 let dash_line = columns.iter()
362 .enumerate()
363 .map(|(col_index, (_header, _type, visible))|
364 if *visible {
365 format!("{}{}", "-".repeat(column_widths[col_index]), column_separator)
366 } else {
367 "".to_string()
368 }
369 )
370 .join("");
371 println!("{}", dash_line.trim_end());
372
373 for row in data.iter() {
374 let value_line = row.iter()
375 .enumerate()
376 .map(|(col_index, value)| {
377 let visible = columns[col_index].2;
378 if visible {
379 format!("{:>width$}{}", value, column_separator, width = column_widths[col_index])
380 } else {
381 "".to_string()
382 }
383 }).join("");
384 println!("{}", value_line.trim_end());
385 }
386}
387
388pub(crate) fn print_ab_comparison_values_string(field_name: &str, value_a: &str, value_b: &str) {
389 print_ab_comparison_values_internal(field_name, value_a, value_b, false);
390}
391
392pub(crate) fn print_ab_comparison_values_int(field_name: &str, value_a: i128, value_b: i128, locale: Option<&num_format::Locale>) {
393 print_ab_comparison_values_internal(
394 field_name,
395 &format_int_locale_opt(value_a, locale),
396 &format_int_locale_opt(value_b, locale),
397 true
398 );
399}
400
401pub(crate) fn print_ab_comparison_values_float(field_name: &str, value_a: f64, value_b: f64, locale: Option<&num_format::Locale>, precision: Option<usize>) {
402 print_ab_comparison_values_internal(
403 field_name,
404 &format_float_locale_opt(value_a, locale, precision),
405 &format_float_locale_opt(value_b, locale, precision),
406 true
407 );
408}
409
410pub(crate) fn print_ab_comparison_values_rate(field_name: &str, value_a: f64, value_b: f64, locale: Option<&num_format::Locale>, precision: Option<usize>) {
411 let precision = precision_opt_set_min(precision, 6);
412 print_ab_comparison_values_float(field_name, value_a, value_b, locale, precision);
413}
414
415pub(crate) fn print_ab_comparison_values_bool(field_name: &str, value_a: bool, value_b: bool) {
416 print_ab_comparison_values_internal(
417 field_name,
418 &format!("{:?}", value_a),
419 &format!("{:?}", value_b),
420 false
421 );
422}
423
424fn print_ab_comparison_values_internal(field_name: &str, value_a: &str, value_b: &str, right_align: bool) {
425 if value_a == value_b {
426 println!("{}: {}", field_name, value_a);
427 } else if right_align {
428 let width = max(value_a.len(), value_b.len());
429 println!("{} a: {:>width$}", field_name, value_a, width = width);
430 println!("{} b: {:>width$}", field_name, value_b, width = width);
431 } else {
432 println!("{} a: {}", field_name, value_a);
433 println!("{} b: {}", field_name, value_b);
434 }
435}
436
437fn precision_opt_set_min(precision: Option<usize>, min: usize) -> Option<usize> {
438 Some(match precision {
439 Some(precision) => precision.max(min),
440 None => 6,
441 })
442}
443
444#[derive(Debug)]
445pub enum ValueType {
446 Payment,
447 Rate,
448}
449
450impl ValueType {
451 pub fn is_payment(&self) -> bool {
452 match self {
453 ValueType::Payment => true,
454 _ => false,
455 }
456 }
457
458 pub fn is_rate(&self) -> bool {
459 match self {
460 ValueType::Rate => true,
461 _ => false,
462 }
463 }
464}
465
466#[derive(Debug)]
467pub enum Schedule {
468 Repeating {
469 value_type: ValueType,
470 value: f64,
471 periods: u32,
472 },
473 Custom {
474 value_type: ValueType,
475 values: Vec<f64>
476 },
477}
478
479impl Schedule {
480
481 pub fn new_repeating(value_type: ValueType, value: f64, periods: u32) -> Self {
482 assert!(value.is_finite());
483 Schedule::Repeating {
484 value_type,
485 value,
486 periods,
487 }
488 }
489
490 pub fn new_custom(value_type: ValueType, values: &[f64]) -> Self {
491 for value in values {
492 assert!(value.is_finite());
493 }
494 Schedule::Custom {
495 value_type,
496 values: values.to_vec(),
497 }
498 }
499
500 pub fn is_payment(&self) -> bool {
501 self.value_type().is_payment()
502 }
503
504 pub fn is_rate(&self) -> bool {
505 self.value_type().is_rate()
506 }
507
508 pub fn value_type(&self) -> &ValueType {
509 match self {
510 Schedule::Repeating { value_type, .. } => value_type,
511 Schedule::Custom { value_type, .. } => value_type,
512 }
513 }
514
515 pub fn value(&self) -> Option<f64> {
516 match self {
517 Schedule::Repeating{ value_type: _, value, .. } => Some(*value),
518 Schedule::Custom { .. } => None,
519 }
520 }
521
522 pub fn get(&self, index: usize) -> f64 {
523 match self {
524 Schedule::Repeating { value, periods, .. } => {
525 assert!(index < *periods as usize);
526 *value
527 },
528 Schedule::Custom { values, .. } => {
529 *values.get(index).unwrap()
530 },
531 }
532 }
533
534 pub fn max(&self) -> Option<f64> {
535 match self {
536 Schedule::Repeating{ value, .. } => Some(*value),
537 Schedule::Custom { values, ..} => {
538 match values.len() {
539 0 => None,
540 1 => Some(values[0]),
541 _ => Some(values.iter().cloned().fold(std::f64::NAN, f64::max))
543 }
544 }
545 }
546 }
547}
548
549#[derive(Debug)]
550pub struct ScenarioList {
551 pub setup: String,
552 pub input_variable: TvmVariable,
553 pub output_variable: TvmVariable,
554 pub entries: Vec<ScenarioEntry>,
555}
556
557pub struct ScenarioEntry {
558 pub input: f64,
559 pub output: f64,
560 input_precision: usize,
561 output_precision: usize,
562}
563
564impl ScenarioList {
565
566 pub(crate) fn new(setup: String, input_variable: TvmVariable, output_variable: TvmVariable, entries: Vec<(f64, f64)>) -> Self {
567 let input_precision = match input_variable {
568 TvmVariable::Periods => 0,
569 TvmVariable::Rate => 6,
570 _ => 4,
571 };
572 let output_precision = match output_variable {
573 TvmVariable::Periods => 0,
574 TvmVariable::Rate => 6,
575 _ => 4,
576 };
577 let entries= entries.iter().map(|entry| ScenarioEntry::new(entry.0, entry.1, input_precision, output_precision)).collect();
578 Self {
579 setup,
580 input_variable,
581 output_variable,
582 entries,
583 }
584 }
585
586 pub fn print_table(&self) {
587 self.print_table_locale_opt(None, None);
588 }
589
590 pub fn print_table_locale(&self, locale: &num_format::Locale, precision: usize) {
591 self.print_table_locale_opt(Some(locale), Some(precision));
592 }
593
594 fn print_table_locale_opt(&self, locale: Option<&num_format::Locale>, precision: Option<usize>) {
595 let columns = vec![self.input_variable.table_column_spec(true), self.output_variable.table_column_spec(true)];
596 let data = self.entries.iter()
598 .map(|entry| vec![entry.input.to_string(), entry.output.to_string()])
599 .collect::<Vec<_>>();
600 print_table_locale_opt(&columns, data, locale, precision);
601 }
602
603}
604
605impl ScenarioEntry {
606 pub(crate) fn new(input: f64, output: f64, input_precision: usize, output_precision: usize) -> Self {
607 Self { input, output, input_precision, output_precision }
608 }
609}
610
611impl Debug for ScenarioEntry {
612 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
613 let input = format_float_locale_opt(self.input, None, Some(self.input_precision));
614 let output = format_float_locale_opt(self.output, None, Some(self.output_precision));
615 write!(f, "{{ input: {}, output: {} }}", input, output)
616 }
617}
618
619pub(crate) fn columns_with_strings(columns: &[(&str, &str, bool)]) -> Vec<(String, String, bool)> {
620 columns.iter().map(|(label, data_type, visible)| (label.to_string(), data_type.to_string(), *visible)).collect()
621}
622
623pub (crate) fn initialized_vector<L, V>(length: L, value: V) -> Vec<V>
624 where
625 L: Into<usize>,
626 V: Copy,
627{
628 let mut v = vec![];
629 for _ in 0..length.into() {
630 v.push(value);
631 }
632 v
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638
639 #[test]
640 fn test_assert_same_sign_or_zero_nominal() {
641 assert_same_sign_or_zero!(0.0, 0.0);
642 assert_same_sign_or_zero!(0.0, -0.0);
643 assert_same_sign_or_zero!(-0.0, 0.0);
644 assert_same_sign_or_zero!(-0.0, -0.0);
645 assert_same_sign_or_zero!(0.023, 0.023);
646 assert_same_sign_or_zero!(10.0, 0.023);
647 assert_same_sign_or_zero!(-0.000045, -100.0);
648 assert_same_sign_or_zero!(0.023, 0.0);
649 assert_same_sign_or_zero!(0.0, 0.023);
650 assert_same_sign_or_zero!(0.023, -0.0);
651 assert_same_sign_or_zero!(-0.0, 0.023);
652 assert_same_sign_or_zero!(-0.000045, -100.0);
653 assert_same_sign_or_zero!(-0.000045, 0.0);
654 assert_same_sign_or_zero!(0.0, -100.0);
655 assert_same_sign_or_zero!(-0.000045, -0.0);
656 assert_same_sign_or_zero!(-0.0, -100.0);
657 assert_same_sign_or_zero!(100.0, -0.00000000001864464138634503);
658 }
659
660 #[should_panic]
661 #[test]
662 fn test_assert_same_sign_or_zero_fail_diff_sign() {
663 assert_same_sign_or_zero!(-0.000045, 100.0);
664 }
665}