< 返回版块

c5soft 发表于 2021-03-18 09:44

Tags:format,currency

各位大咖,有没有办法通过trait,引入一种数据显示类型,让金额显示千分符?

评论区

写评论
作者 c5soft 2021-03-19 08:38

看来是把简单问题搞复杂了,其实写一个简单函数就解决问题了:

use std::fmt::Display;

fn commafy<T: Display>(num: T) -> String {
    let num = format!("{:.2}", num);
    let (signal, num) = num.split_at(num.find("-").map(|x| x + 1).unwrap_or(0));
    let (int, fraction) = num.split_at(num.find(".").unwrap_or(num.len()));
    let len = int.len();
    let c3 = |x| if len >= x * 3 { len - x * 3 } else { 0 };
    let int = (0..len / 3 + 1)
        .map(|i| int.get(c3(i + 1)..c3(i)).unwrap())
        .rev()
        .filter(|x| x.len() > 0)
        .fold("".to_string(), |result, part| {
            let sep = if result.is_empty() { "" } else { "," };
            result + sep + part
        });
    signal.to_string() + &int + &fraction
}
fn main() {
    let n = 12345678901223i64;
    println!("{}", commafy(n));
    let n = -12345678901223i64;
    println!("{}", commafy(n));
    let n = 12345678901223.128;
    println!("{}", commafy(n));
    let n = -123456789012239.19;
    println!("{}", commafy(n));
}

程序运行结果:

12,345,678,901,223
-12,345,678,901,223    
12,345,678,901,223.13  
-123,456,789,012,239.19

各位大咖,还有更精简的写法吗?

作者 c5soft 2021-03-18 14:08

我的意思是:引入一种新的显示类型,比如c,对应一个trait, 比如CommaSep

nothing ⇒ Display
? ⇒ Debug
x? ⇒ Debug with lower-case hexadecimal integers
X? ⇒ Debug with upper-case hexadecimal integers
o ⇒ Octal
x ⇒ LowerHex
X ⇒ UpperHex
p ⇒ Pointer
b ⇒ Binary
e ⇒ LowerExp
E ⇒ UpperExp

c ⇒ CommaSep

use comma_sep:CommaSep;
fn main()  {
    let num=1234567.343f64;
    assert_eq!("1,234,567.34",format!("{:12.2c}",num));
}
Aya0wind 2021-03-18 11:14

可以,加一个trait就行

trait ToThousandSplitStr {
    fn to_thousand_split_str(&self) -> String;
}
struct Amount<T: ToThousandSplitStr>(T);

fn integer_str_to_thousand_split_str(string: &str) -> String {
    let bytes = string.len();
    let reminder = bytes % 3;
    let mut result = String::new();
    result.push_str(&string[..reminder]);
    if reminder != 0 {
        result.push(',');
    }
    for chunk in string[reminder..].as_bytes().chunks_exact(3) {
        unsafe {
            result.push_str(std::str::from_utf8_unchecked(chunk));
        }
        result.push(',');
    }
    result.pop();
    result
}

fn float_str_to_thousand_split_str(string: &str) -> String {
    let (integer, decimal) = string.split_once(|ch| ch == '.').unwrap();
    format!("{}.{}", integer_str_to_thousand_split_str(integer), decimal)
}

impl ToThousandSplitStr for i64 {
    fn to_thousand_split_str(&self) -> String {
        let string = self.to_string();
        integer_str_to_thousand_split_str(&string)
    }
}
impl ToThousandSplitStr for f64 {
    fn to_thousand_split_str(&self) -> String {
        if self.is_nan() {
            return "NaN".into();
        }
        if self.is_infinite() {
            return "INFINITY".into();
        }
        let string = self.to_string();
        float_str_to_thousand_split_str(&string)
    }
}

impl<T: ToThousandSplitStr> std::fmt::Display for Amount<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.0.to_thousand_split_str())
    }
}
fn main() {
    eprintln!("金额:{}", Amount(123448.95678));
    eprintln!("金额:{}", Amount(12345678));
    eprintln!("金额:{}", Amount(0.123456));
    eprintln!("金额:{}", Amount(123));
    eprintln!("金额:{}", Amount(4567));
    eprintln!("金额:{}", Amount(f64::INFINITY));
    eprintln!("金额:{}", Amount(f64::NAN));
}

打印

金额:123,448.95678
金额:12,345,678
金额:0.123456
金额:123
金额:4,567
金额:INFINITY
金额:NaN

给你想要格式化的所有类型都实现这个trait即可,这里就实现一个i64和f64,觉得重复代码多可以用宏来搞。不知道是否符合你的要求。

Nalleyer 2021-03-18 10:53
    println!("{:.3}", 20.0); // 20.000

这样吗

1 共 4 条评论, 1 页