Skip to main content

fiscal_core/
format_utils.rs

1//! Formatting helpers for monetary amounts, rates, and decimal numbers.
2//!
3//! All functions accept raw integer representations — cents for monetary values
4//! and scaled integers for rates — and return formatted `String`s suitable for
5//! insertion into NF-e XML elements.
6
7/// Format a cents integer to a decimal string with the given number of decimal places.
8///
9/// # Examples
10///
11/// ```
12/// use fiscal_core::format_utils::format_cents;
13/// assert_eq!(format_cents(1050, 2), "10.50");
14/// assert_eq!(format_cents(100000, 10), "1000.0000000000");
15/// ```
16pub fn format_cents(cents: i64, decimal_places: usize) -> String {
17    let divisor = 100.0_f64;
18    let value = cents as f64 / divisor;
19    format!("{value:.decimal_places$}")
20}
21
22/// Format a cents integer to a decimal string with 2 decimal places.
23///
24/// # Examples
25///
26/// ```
27/// use fiscal_core::format_utils::format_cents_2;
28/// assert_eq!(format_cents_2(1050), "10.50");
29/// assert_eq!(format_cents_2(0), "0.00");
30/// ```
31pub fn format_cents_2(cents: i64) -> String {
32    format_cents(cents, 2)
33}
34
35/// Format a cents integer to a decimal string with 10 decimal places (for unit prices).
36///
37/// # Examples
38///
39/// ```
40/// use fiscal_core::format_utils::format_cents_10;
41/// assert_eq!(format_cents_10(100000), "1000.0000000000");
42/// ```
43pub fn format_cents_10(cents: i64) -> String {
44    format_cents(cents, 10)
45}
46
47/// Format a floating-point number with `decimal_places` decimal places.
48///
49/// # Examples
50///
51/// ```
52/// use fiscal_core::format_utils::format_decimal;
53/// assert_eq!(format_decimal(3.14159, 2), "3.14");
54/// ```
55pub fn format_decimal(value: f64, decimal_places: usize) -> String {
56    format!("{value:.decimal_places$}")
57}
58
59/// Format a rate stored as hundredths of a percent to a decimal string.
60///
61/// For example, `1800` (= 18%) with 4 decimal places → `"18.0000"`.
62///
63/// # Examples
64///
65/// ```
66/// use fiscal_core::format_utils::format_rate;
67/// assert_eq!(format_rate(1800, 4), "18.0000");
68/// assert_eq!(format_rate(750, 2), "7.50");
69/// ```
70pub fn format_rate(hundredths: i64, decimal_places: usize) -> String {
71    let value = hundredths as f64 / 100.0;
72    format!("{value:.decimal_places$}")
73}
74
75/// Format a rate (stored as hundredths) with 4 decimal places.
76///
77/// # Examples
78///
79/// ```
80/// use fiscal_core::format_utils::format_rate_4;
81/// assert_eq!(format_rate_4(1800), "18.0000");
82/// ```
83pub fn format_rate_4(hundredths: i64) -> String {
84    format_rate(hundredths, 4)
85}
86
87/// Format a PIS/COFINS rate stored as `value × 10 000` to a 4-decimal string.
88///
89/// For example, `16500` (= 1.65%) → `"1.6500"`.
90///
91/// # Examples
92///
93/// ```
94/// use fiscal_core::format_utils::format_rate4;
95/// assert_eq!(format_rate4(16500), "1.6500");
96/// ```
97pub fn format_rate4(value: i64) -> String {
98    let v = value as f64 / 10000.0;
99    format!("{v:.4}")
100}
101
102/// Format an optional cents value to a decimal string, returning `None` when the
103/// input is `None`.
104pub fn format_cents_or_none(cents: Option<i64>, decimal_places: usize) -> Option<String> {
105    cents.map(|c| format_cents(c, decimal_places))
106}
107
108/// Format an optional cents value to a decimal string, defaulting to `"0.00"` (or
109/// `"0.` + `n` zeros`"`) when the input is `None`.
110pub fn format_cents_or_zero(cents: Option<i64>, decimal_places: usize) -> String {
111    match cents {
112        Some(c) => format_cents(c, decimal_places),
113        None => format_cents(0, decimal_places),
114    }
115}
116
117/// Format an optional `rate4` value (scaled by 10 000) to a 4-decimal string,
118/// defaulting to `"0.0000"` when the input is `None`.
119pub fn format_rate4_or_zero(value: Option<i64>) -> String {
120    match value {
121        Some(v) => format_rate4(v),
122        None => "0.0000".to_string(),
123    }
124}