在Rust中可以打印带有千位分隔符的数字吗?

jxct1oxe  于 2023-01-09  发布在  其他
关注(0)|答案(8)|浏览(201)

比如说

println!("{}", 10_000_000);

导致

10000000

而我想把它格式化成

10,000,000

我浏览了the fmt module documentation,但没有任何内容可以涵盖这种特殊情况。

println!("{:,i}", 10_000_000);

但它会抛出一个错误

invalid format string: expected `}`, found `,`
xcitsw88

xcitsw881#

num_format crate将为您解决这个问题。添加您的区域设置,它将发挥神奇作用。

9rnv2umw

9rnv2umw2#

  • 使用千位分隔符设置数字格式的最简单方法-但不使用 * 区域设置

使用thousands板条箱

use thousands::Separable;

println!("{}", 10_000_000.separate_with_commas());
  • 一句俏皮话 *

仅使用Rust标准库

let num = 10_000_000.to_string()
    .as_bytes()
    .rchunks(3)
    .rev()
    .map(str::from_utf8)
    .collect::<Result<Vec<&str>, _>>()
    .unwrap()
    .join(",");  // separator

Playground

50pmv0ei

50pmv0ei3#

现在没有,将来也不会有。
根据您所处的位置,千位分隔符也可以像1,00,00,0001.000.000,000或其他变体一样工作。
本地化不是stdlib的工作,加上format!主要在编译时处理(尽管公平地说,这可以很容易地放在它的运行时部分),而且您不希望将一个语言环境硬烤到程序中。

oyjwcjzk

oyjwcjzk4#

下面是一个简单的整数实现

fn pretty_print_int(i: isize) {
    let mut s = String::new();
    let i_str = i.to_string();
    let a = i_str.chars().rev().enumerate();
    for (idx, val) in a {
        if idx != 0 && idx % 3 == 0 {
            s.insert(0, ',');
        }
        s.insert(0, val);
    }
    println!("{}", s);
}

pretty_print_int(10_000_000);
// 10,000,000

如果你想让它更通用于整数,你可以使用num::Integer特征

extern crate num;

use num::Integer;

fn pretty_print_int<T: Integer>(i: T) {
    ...
}
fcwjkofz

fcwjkofz5#

关于自定义函数,我尝试了一下,下面是一些想法:

use std::str;

fn main() {
    let i = 10_000_000i;
    println!("{}", decimal_mark1(i.to_string()));
    println!("{}", decimal_mark2(i.to_string()));
    println!("{}", decimal_mark3(i.to_string()));
}

fn decimal_mark1(s: String) -> String {
    let bytes: Vec<_> = s.bytes().rev().collect();
    let chunks: Vec<_> = bytes.chunks(3).map(|chunk| str::from_utf8(chunk).unwrap()).collect();
    let result: Vec<_> = chunks.connect(" ").bytes().rev().collect();
    String::from_utf8(result).unwrap()
}

fn decimal_mark2(s: String) -> String {
    let mut result = String::with_capacity(s.len() + ((s.len() - 1) / 3));
    let mut i = s.len();
    for c in s.chars() {
        result.push(c);
        i -= 1;
        if i > 0 && i % 3 == 0 {
            result.push(' ');
        }
    }
    result
}

fn decimal_mark3(s: String) -> String {
    let mut result = String::with_capacity(s.len() + ((s.len() - 1) / 3));
    let first = s.len() % 3;
    result.push_str(s.slice_to(first));
    for chunk in s.slice_from(first).as_bytes().chunks(3) {
        if !result.is_empty() {
            result.push(' ');
        }
        result.push_str(str::from_utf8(chunk).unwrap());
    }
    result
}

游戏围栏:http://is.gd/UigzCf
欢迎评论,没有一个感觉真的很好。

06odsfpq

06odsfpq6#

另一个解决方法是使用separator crate,它实现了浮点型、整型和size类型的.separated_string()方法。

extern crate separator;
use separator::Separatable;

fn main() {
    let x1: u16 = 12345;
    let x2: u64 = 4242424242;
    let x3: u64 = 232323232323;
    println!("Unsigned ints:\n{:>20}\n{:>20}\n{:>20}\n", x1.separated_string(), x2.separated_string(), x3.separated_string());

    let x1: i16 = -12345;
    let x2: i64 = -4242424242;
    let x3: i64 = -232323232323;
    println!("Signed ints:\n{:>20}\n{:>20}\n{:>20}\n", x1.separated_string(), x2.separated_string(), x3.separated_string());

    let x1: f32 = -424242.4242;
    let x2: f64 = 23232323.2323;
    println!("Floats:\n{:>20}\n{:>20}\n", x1.separated_string(), x2.separated_string());

    let x1: usize = 424242;
    // let x2: isize = -2323232323;  // Even though the docs say so, the traits seem not to be implemented for isize
    println!("Size types:\n{:>20}\n", x1.separated_string());        
}

它将提供以下输出:

Unsigned ints:
              12,345
       4,242,424,242
     232,323,232,323

Signed ints:
             -12,345
      -4,242,424,242
    -232,323,232,323

Floats:
         -424,242.44
     23,232,323.2323

Size types:
             424,242

注意,这样对齐浮点数并不容易,因为separated_string()返回一个字符串,然而,这是一个相对快速的方法来获得分隔的数字。

e5nqia27

e5nqia277#

手动编写代码来完成这一点是非常简单的。下面是一些直接操作字符串的代码。这样做的好处是允许你使用普通的方法来使用小数位格式(我认为现有的选项都不允许这样做)。同时它也避免了为如此简单的东西引入过于复杂的依赖关系。请随意复制/粘贴(公共领域):

/// Add thousands comma separators to a number. The number must match the following
/// regex: `^-?\d*(\.\d*)?$`. Returns None if it does not match that format.
/// Note that empty strings and just `-` are allowed.
pub fn with_comma_separators(s: &str) -> Option<String> {
    // Position of the `.`
    let dot = s.bytes().position(|c| c == b'.').unwrap_or(s.len());
    // Is the number negative (starts with `-`)?
    let negative = s.bytes().next() == Some(b'-');
    // The dot cannot be at the front if it is negative.
    assert!(!(negative && dot == 0));
    // Number of integer digits remaning (between the `-` or start and the `.`).
    let mut integer_digits_remaining = dot - negative as usize;
    // Output. Add capacity for commas. It's a slight over-estimate but that's fine.
    let mut out = String::with_capacity(s.len() + integer_digits_remaining / 3);

    // We can iterate on bytes because everything must be ASCII. Slightly faster.
    for (i, c) in s.bytes().enumerate() {
        match c {
            b'-' => {
                // `-` can only occur at the start of the string.
                if i != 0 {
                    return None;
                }
            }
            b'.' => {
                // Check we only have a dot at the expected position.
                // This return may happen if there are multiple dots.
                if i != dot {
                    return None;
                }
            }
            b'0'..=b'9' => {
                // Possibly add a comma.
                if integer_digits_remaining > 0 {
                    // Don't add a comma at the start of the string.
                    if i != negative as usize && integer_digits_remaining % 3 == 0 {
                        out.push(',');
                    }
                    integer_digits_remaining -= 1;
                }
            }
            _ => {
                // No other characters allowed.
                return None;
            }
        }
        out.push(c as char);
    }
    Some(out)
}

#[cfg(test)]
mod test {
    use super::with_comma_separators;

    #[test]
    fn basic() {
        assert_eq!(with_comma_separators("123.45").as_deref(), Some("123.45"));
        assert_eq!(
            with_comma_separators("1234.56").as_deref(),
            Some("1,234.56")
        );
        assert_eq!(with_comma_separators(".56").as_deref(), Some(".56"));
        assert_eq!(with_comma_separators("56").as_deref(), Some("56"));
        assert_eq!(with_comma_separators("567").as_deref(), Some("567"));
        assert_eq!(with_comma_separators("5678").as_deref(), Some("5,678"));
        assert_eq!(
            with_comma_separators("12345678").as_deref(),
            Some("12,345,678")
        );
        assert_eq!(with_comma_separators("5678.").as_deref(), Some("5,678."));
        assert_eq!(with_comma_separators(".0123").as_deref(), Some(".0123"));

        assert_eq!(with_comma_separators("-123.45").as_deref(), Some("-123.45"));
        assert_eq!(
            with_comma_separators("-1234.56").as_deref(),
            Some("-1,234.56")
        );
        assert_eq!(with_comma_separators("-.56").as_deref(), Some("-.56"));
        assert_eq!(with_comma_separators("-56").as_deref(), Some("-56"));
        assert_eq!(with_comma_separators("-567").as_deref(), Some("-567"));
        assert_eq!(with_comma_separators("-5678").as_deref(), Some("-5,678"));
        assert_eq!(
            with_comma_separators("-12345678").as_deref(),
            Some("-12,345,678")
        );
        assert_eq!(with_comma_separators("-5678.").as_deref(), Some("-5,678."));
        assert_eq!(with_comma_separators("-.0123").as_deref(), Some("-.0123"));

        assert_eq!(with_comma_separators("").as_deref(), Some(""));
        assert_eq!(with_comma_separators("-").as_deref(), Some("-"));

        assert_eq!(with_comma_separators("a").as_deref(), None);
        assert_eq!(with_comma_separators("0-").as_deref(), None);
        assert_eq!(with_comma_separators("0..1").as_deref(), None);
        assert_eq!(with_comma_separators("0..1").as_deref(), None);
        assert_eq!(with_comma_separators("01a").as_deref(), None);
        assert_eq!(with_comma_separators("01.a").as_deref(), None);
        assert_eq!(with_comma_separators(".0.").as_deref(), None);
    }
}
zu0ti5jz

zu0ti5jz8#

如果您不需要立即使用String,并且可能需要变量分组,那么您可能需要考虑基于迭代器的方法。

fn thsep(digits: &str, n: usize) -> impl Iterator<Item = &str> {
    let (chars, tip) = (digits.as_bytes(), digits.len() % n);
    if tip != 0 { Some(&chars[..tip]) } else { None }
        .into_iter()
        .chain(chars[tip..].chunks(n))
        .map(|digits| {
            std::str::from_utf8(digits).expect("unexpected non-utf8 char encountered")
        })
}

fn join(i: impl Iterator<Item = &'static str>) -> String {
    i.collect::<Vec<_>>().join(",")
}

fn main() {
    let val = "1234567890";
    println!("{}", join(thsep(val, 1))); // 1,2,3,4,5,6,7,8,9,0
    println!("{}", join(thsep(val, 2))); // 12,34,56,78,90
    println!("{}", join(thsep(val, 3))); // 1,234,567,890 • 3
    println!("{}", join(thsep(val, 4))); // 12,3456,7890
}

相关问题