#![deny(missing_docs)]
use std::iter::FromIterator;
use std::string::ToString;
pub fn custom_group(num: &str,
decimal_mark: char,
grouping_delimiter: char,
first_group_size: usize,
group_size: usize,
group_fractional_part: bool)
-> String {
let parts = num.split('.').collect::<Vec<_>>();
let integer_part = match parts.get(0) {
Some(num) => {
groupify_integer(num.chars(),
grouping_delimiter,
first_group_size,
group_size,
GroupDirection::RightToLeft)
}
None => String::from(""),
};
let mut grouped_string = integer_part;
if let Some(fractional_part) = parts.get(1) {
grouped_string.push(decimal_mark);
if group_fractional_part {
let fractional_grouped = groupify_integer(fractional_part.chars(),
grouping_delimiter,
first_group_size,
group_size,
GroupDirection::LeftToRight);
grouped_string.push_str(&fractional_grouped)
} else {
grouped_string.push_str(fractional_part)
}
}
grouped_string
}
pub trait FormatGroup {
fn format_si(&self, decimal_mark: char) -> String;
fn format_commas(&self) -> String;
fn format_custom(&self,
decimal_mark: char,
grouping_delimiter: char,
first_group_size: usize,
group_size: usize,
group_fractional_part: bool)
-> String;
}
#[derive(PartialEq)]
enum GroupDirection {
RightToLeft,
LeftToRight,
}
fn groupify_integer<T>(integral_digits: T,
delimiter: char,
first_group_size: usize,
group_size: usize,
direction: GroupDirection)
-> String
where T: Iterator<Item = char>
{
let integral_digits = integral_digits.collect::<Vec<char>>();
let is_negative = {
match integral_digits.get(0) {
Some(d) => *d == '-',
None => false,
}
};
let skip_negative = {
if is_negative { 1 } else { 0 }
};
let mut delimited_integer = Vec::new();
match direction {
GroupDirection::RightToLeft => {
for digit in integral_digits.iter().skip(skip_negative).rev().take(first_group_size) {
delimited_integer.push(*digit)
}
}
GroupDirection::LeftToRight => {
for digit in integral_digits.iter().skip(skip_negative).take(first_group_size) {
delimited_integer.push(*digit)
}
}
}
match direction {
GroupDirection::RightToLeft => {
for (i, digit) in integral_digits.iter()
.skip(skip_negative)
.rev()
.skip(first_group_size)
.enumerate() {
if i % group_size == 0 {
delimited_integer.push(delimiter);
}
delimited_integer.push(*digit);
}
}
GroupDirection::LeftToRight => {
for (i, digit) in integral_digits.iter()
.skip(skip_negative)
.skip(first_group_size)
.enumerate() {
if i % group_size == 0 {
delimited_integer.push(delimiter);
}
delimited_integer.push(*digit);
}
}
}
if is_negative {
delimited_integer.push('-');
}
if direction == GroupDirection::RightToLeft {
delimited_integer.reverse();
}
String::from_iter(delimited_integer.into_iter())
}
macro_rules! impl_FormatGroup {
($t:ty) => (
impl FormatGroup for $t {
fn format_si(&self, decimal_mark: char) -> String {
self.format_custom(decimal_mark, ' ', 3, 3, true)
}
fn format_commas(&self) -> String {
self.format_custom('.', ',', 3, 3, false)
}
fn format_custom(&self,
decimal_mark: char,
grouping_delimiter: char,
first_group_size: usize,
group_size: usize,
group_fractional_part: bool)
-> String {
let stringy_number = self.to_string();
custom_group(&stringy_number,
decimal_mark,
grouping_delimiter,
first_group_size,
group_size,
group_fractional_part)
}
}
)
}
impl_FormatGroup!(i8);
impl_FormatGroup!(i16);
impl_FormatGroup!(i32);
impl_FormatGroup!(i64);
impl_FormatGroup!(isize);
impl_FormatGroup!(u8);
impl_FormatGroup!(u16);
impl_FormatGroup!(u32);
impl_FormatGroup!(u64);
impl_FormatGroup!(usize);
impl_FormatGroup!(f32);
impl_FormatGroup!(f64);
#[cfg(test)]
mod tests {
use super::{FormatGroup, custom_group};
#[test]
fn u64_si() {
let x: u64 = 1234567891234;
let s = x.format_si('.');
assert_eq!(s, "1 234 567 891 234");
}
#[test]
fn i64_si_negative() {
let x: i64 = -1234567891234;
let s = x.format_si('.');
assert_eq!(s, "-1 234 567 891 234");
}
#[test]
fn f64_si_negative() {
let x: f64 = -123456789.1234567;
let s = x.format_si('.');
assert_eq!(s, "-123 456 789.123 456 7");
}
#[test]
fn f64_si() {
let x: f64 = 123456789.1234567;
let s = x.format_si('.');
assert_eq!(s, "123 456 789.123 456 7");
}
#[test]
fn f64_commas() {
let x: f64 = -123456789.123456;
let s = x.format_commas();
assert_eq!(s, "-123,456,789.123456");
}
#[test]
fn f64_custom() {
let x: f64 = -123456789.123456;
let s = x.format_custom(',', ':', 2, 3, false);
assert_eq!(s, "-1:234:567:89,123456");
}
#[test]
fn custom_standalone() {
let x: f64 = -123456789.123456;
let formatted = format!("{:.*}", 8, x);
let s = custom_group(&formatted, '.', ',', 3, 3, false);
assert_eq!(s, "-123,456,789.12345600");
}
#[test]
fn china() {
let x: f64 = 1234567.89;
let s = x.format_custom('.', ',', 4, 3, false);
assert_eq!(s, "123,4567.89");
}
#[test]
fn india() {
let x: f64 = 1234567.89;
let s = x.format_custom('.', ',', 3, 2, false);
assert_eq!(s, "12,34,567.89");
}
}