bcmyers / num-format

A Rust crate for producing string representations of numbers, formatted according to international standards
Apache License 2.0
123 stars 22 forks source link

num-format

Crates.io Documentation License

A Rust crate for producing string representations of numbers, formatted according to international standards, e.g.

Creating a string representation

num-format offers three principal APIs...

ToFormattedString

The ToFormattedString trait is the simplist of the three APIs. Just call to_formatted_string on a type that implements it (all the integer types in the standard library implement it) while providing a desired format (see picking a format below). That said, using ToFormattedString will always heap allocate; so it is the slowest of the three APIs and cannot be used in a no_std environment.

use num_format::{Locale, ToFormattedString};

fn main() {
    let s = 1000000.to_formatted_string(&Locale::en);
    assert_eq!(&s, "1,000,000");
}

Buffer

Using the Buffer type is the fastest API, as it does not heap allocate. Instead, the formatted representation is written into a stack-allocated buffer. As such, you can use it in a no_std environment.

Although this API is available for all the integer types in the standard library, it is not available for types like num_bigint::BigInt whose maximum size cannot be known in advance.

use num_format::{Buffer, Locale};

fn main() {
    // Create a stack-allocated buffer...
    let mut buf = Buffer::default();

    // Write "1,000,000" into the buffer...
    buf.write_formatted(&1000000, &Locale::en);

    // Get a view into the buffer as a &str...
    let s = buf.as_str();

    // Do what you want with the &str...
    assert_eq!("1,000,000", s);
}

WriteFormatted

The WriteFormatted trait is in between the other two APIs. You can write a formatted representation into any type that implements WriteFormatted (all the types in the standard library that implement io::Write or fmt::Write implement WriteFormatted, such as Vec, String, File, etc.).

If you're writing a number type that can use the Buffer API, there is no heap allocation. That said, the io::Write and fmt::Write machinery adds a bit of overhead; so it's faster to use the Buffer type directly. This trait is not available in a no_std environment.

use num_format::{Locale, WriteFormatted};

fn main() {
    // Create a writer...
    let mut writer = String::new(); // Could also be Vec::new(), File::open(...), ...

    // Write "1,000,000" into the writer...
    writer.write_formatted(&1000000, &Locale::en);

    assert_eq!(&writer, "1,000,000");
}

Picking a format

Formatting options (e.g. which thousands separator to use, what the minus sign looks like, etc.) are represented by the Format trait. This crate offers three concrete implementations of the Format trait...

Locale

The Locale type is a programatically generated enum representing formatting standards from the Common Locale Data Repository, which is maintained by the Unicode Consortium and used by Apple in macOS and iOS, by LibreOffice, by IBM in AIX, among others.

use num_format::{Grouping, Locale};

fn main() {
    let locale = Locale::en;
    assert_eq!(locale.grouping(), Grouping::Standard);
    assert_eq!(locale.minus_sign(), "-");
    assert_eq!(locale.name(), "en");
    assert_eq!(locale.separator(), ",");

    let locale2 = Locale::from_name("en").unwrap();
    assert_eq!(locale, locale2);

    let available = Locale::available_names();
    println!("All of the locale names available in the Unicode database are...");
    println!("{:#?}", available);
}

SystemLocale (available behind feature flag with-system-locale)

The SystemLocale type is another type that implements Format. It allows you to access your OS's locale information. It has a very similar API to Locale and should work on all major operating systems (i.e. macOS, linux, the BSDs, and Windows).

Since this type requires several dependencies (especially on Windows), it is behind a feature flag. To use it, include num-format = { version = "0.4.3", features = ["with-system-locale"] } in your Cargo.toml. Additionally, on Windows (but only on Windows), using SystemLocale requires Clang 3.9 or higher.

use num_format::SystemLocale;

fn main() {
    let locale = SystemLocale::default().unwrap();
    println!("My system's default locale is...");
    println!("{:#?}", &locale);

    let available = SystemLocale::available_names().unwrap();
    println!("My available locale names are...");
    println!("{:#?}", available);

    match SystemLocale::from_name("en_US") {
        Ok(_) => println!("My system has the 'en_US' locale."),
        Err(_) => println!("The 'en_US' locale is not included with my system."),
    }
}

CustomFormat

CustomFormat is the third and final type that implements Format. You can use it to build your own custom formats.

use num_format::{Buffer, Error, CustomFormat, Grouping};

fn main() -> Result<(), Error> {
    let format = CustomFormat::builder()
        .grouping(Grouping::Indian)
        .minus_sign("🙌")
        .separator("😀")
        .build()?;

    let mut buf = Buffer::new();
    buf.write_formatted(&(-1000000), &format);
    assert_eq!("🙌10😀00😀000", buf.as_str());

    Ok(())
}

Requirements

Extra features

Available features What to put in your Cargo.toml
no_std num-format = { version = "0.4.3", default-features = false }
with-num-bigint num-format = { version = "0.4.3", features = ["with-num-bigint"] }
with-serde num-format = { version = "0.4.3", features = ["with-serde"] }
with-system-locale num-format = { version = "0.4.3", features = ["with-system-locale"] }

License

num-format is licensed under either of:

at your option.