use serde::{Deserialize, Serialize};
pub fn bytes_format_function(bytes: f64, precision: usize) -> String {
const UNITS: [&str; 7] = ["B", "KB", "MB", "GB", "TB", "PB", "EB"];
let mut size = bytes;
let mut idx = 0;
while size >= 1024.0 && idx < UNITS.len() - 1 {
size /= 1024.0;
idx += 1;
}
format!("{:.*} {}", precision, size, UNITS[idx])
}
#[macro_export]
macro_rules! bytes_format {
($bytes:tt) => {
$crate::helper::bytes_format_function($bytes, 2)
};
($bytes:tt,$precision:tt) => {
$crate::helper::bytes_format_function($bytes, $precision)
};
}
pub fn format_number_function(
value: f64,
precision: usize,
thousand_sep: char,
decimal_sep: char,
) -> String {
let s = format!("{:.*}", precision, value);
let parts = s.split('.').collect::<Vec<_>>();
let int_part = parts[0];
let frac_part = if precision > 0 && parts.len() > 1 {
parts[1]
} else {
""
};
let mut rev = int_part.chars().rev().peekable();
let mut formatted_int = String::new();
for (i, c) in rev.by_ref().enumerate() {
if i > 0 && i % 3 == 0 {
formatted_int.push(thousand_sep);
}
formatted_int.push(c);
}
let formatted_int = formatted_int.chars().rev().collect::<String>();
if precision > 0 {
format!("{}{}{}", formatted_int, decimal_sep, frac_part)
} else {
formatted_int
}
}
#[macro_export]
macro_rules! format_number {
($value:tt) => {
$crate::helper::format_number_function($value, 0, '.', ',')
};
($value:tt,$precision:tt) => {
$crate::helper::format_number_function($value, $precision, '.', ',')
};
($value:tt,$precision:tt,$thousand_sep:expr,$decimal_sep:expr) => {
$crate::helper::format_number_function($value, $precision, $thousand_sep, $decimal_sep)
};
}
#[derive(Serialize, Deserialize)]
pub struct FormatShortResponse {
number: f64,
format: String,
}
pub fn format_short_number(value: f64, precision: usize) -> FormatShortResponse {
let sign = if value.is_sign_negative() { "-" } else { "" };
let abs = value.abs();
let (scaled, suffix) = if abs >= 1_000_000_000.0 {
(abs / 1_000_000_000.0, "B")
} else if abs >= 1_000_000.0 {
(abs / 1_000_000.0, "M")
} else if abs >= 1_000.0 {
(abs / 1_000.0, "K")
} else {
(abs, "")
};
let mut resp = FormatShortResponse {
number: value,
format: String::new(),
};
if suffix.is_empty() {
resp.format = format!("{}{:.*}", sign, precision, scaled);
} else {
resp.format = format!("{}{:.*} {}", sign, precision, scaled, suffix);
}
resp
}
#[cfg(test)]
mod tests {
use crate::helper::format_short_number;
#[test]
fn test_bytes_format_various() {
let cases = vec![
(0.0, 2, "0.00 B"),
(500.0, 0, "500 B"),
(1024.0, 1, "1.0 KB"),
(1536.0, 2, "1.50 KB"), (1_048_576.0, 2, "1.00 MB"), (1_073_741_824.0, 2, "1.00 GB"), (5_368_709_120.0, 1, "5.0 GB"), (1_099_511_627_776.0, 3, "1.000 TB"), (50_000_000_000.0, 2, "46.57 GB"), (5_629_499_534_213_120.0, 1, "5.0 PB"), (50486525485f64, 2, "47.02 GB"),
(18037807f64, 2, "17.20 MB"),
(0f64, 0, "0 B"),
];
for (bytes, precision, expected) in cases {
let got = bytes_format!(bytes, precision);
assert_eq!(
got, expected,
"bytes_format({}, {}) should be {:?}, got {:?}",
bytes, precision, expected, got
);
}
}
#[test]
fn test_format_number() {
let cases = vec![
(5000.0, 0, '.', ',', "5.000"),
(1234.0, 0, ',', '.', "1,234"),
(1234567.89, 2, '.', ',', "1.234.567,89"),
(1234567.89, 2, ',', '.', "1,234,567.89"),
(100.5, 1, '.', ',', "100,5"),
(100.0, 3, ',', '.', "100.000"),
(0.0, 2, '.', ',', "0,00"),
];
for (value, prec, thou, dec, expected) in cases {
let got = format_number!(value, prec, thou, dec);
assert_eq!(
got, expected,
"format_number({}, {}, '{}', '{}') -> {:?}, expected {:?}",
value, prec, thou, dec, got, expected
);
}
let n = format_number!(1000.0);
assert_eq!(n, "1.000");
let n = format_number!(1000.0, 2);
assert_eq!(n, "1.000,00");
let n = format_number!(1000.0, 2, ',', '.');
assert_eq!(n, "1,000.00")
}
#[test]
fn test_format_short_number() {
let cases = vec![
(5025.0, 2, "5.03 K"),
(64768456.0, 2, "64.77 M"),
(1_065_201_025.0, 2, "1.07 B"),
(999.0, 2, "999.00"), (1000.0, 1, "1.0 K"),
(1_000_000.0, 0, "1 M"),
(1_500_000_000.0, 3, "1.500 B"),
(-2500.0, 2, "-2.50 K"), (0.0, 2, "0.00"),
];
for (value, precision, expected) in cases {
let got = format_short_number(value, precision);
assert_eq!(
got.format, expected,
"format_short_number({}, {}) should be {:?}, got {:?}",
value, precision, expected, got.format
);
}
}
}