Humanize numbers in Rust — turn raw numbers into the short, friendly strings you see in dashboards, CLIs and reports, and parse them back.
assert_eq!(numan::compact(1_234), "1.2K");
assert_eq!(numan::word(1_200_000), "1.2 million");
assert_eq!(numan::metric(1_500), "1.5 k");
assert_eq!(numan::ordinal(22), "22nd");
assert_eq!(numan::parse("1.5M").unwrap(), 1_500_000.0);Zero dependencies. #![no_std] (needs only alloc). One small, focused crate.
Rust's ecosystem already humanizes file sizes
(humansize) and relative time
(chrono-humanize). What was missing
is the part Python's humanize and Go's
go-humanize are famous for: shortening
the magnitude of plain numbers.
| Need | Reach for |
|---|---|
File sizes (1.2 MiB) |
humansize |
Relative time (3 hours ago) |
chrono-humanize |
Number → words (forty-two) |
num2words |
| Locale thousands grouping | num-format |
Compact / word / metric magnitude (1.2K, 1.2 million, 1.2 k) + parsing |
numan ✅ |
[dependencies]
numan = "0.1"use numan::{compact, word, metric};
assert_eq!(compact(950), "950");
assert_eq!(compact(12_300), "12.3K");
assert_eq!(compact(1_500_000), "1.5M");
assert_eq!(compact(-2_000_000), "-2M");
assert_eq!(word(2_500), "2.5 thousand");
assert_eq!(word(1_000_000), "1 million");
assert_eq!(metric(1_000), "1 k"); // SI: lowercase kilo
assert_eq!(metric(2_200_000), "2.2 M");assert_eq!(numan::ordinal(1), "1st");
assert_eq!(numan::ordinal(112), "112th");use numan::parse;
assert_eq!(parse("1.2K").unwrap(), 1_200.0);
assert_eq!(parse("1.5 million").unwrap(), 1_500_000.0);
assert_eq!(parse("-2.5bn").unwrap(), -2_500_000_000.0);
assert_eq!(parse("1,234").unwrap(), 1_234.0); // separators ignoredGreat for CLI flags like --limit 1.5M.
use numan::Formatter;
let f = Formatter::compact().precision(2).space(true);
assert_eq!(f.format(1_234_567), "1.23 M");
// Keep trailing zeros, force an explicit sign:
let f = Formatter::compact().trim_zeros(false).sign_plus(true);
assert_eq!(f.format(1_000_000), "+1.0M");| Builder method | Default | Effect |
|---|---|---|
precision(n) |
1 |
Max fractional digits |
trim_zeros(bool) |
true |
Drop trailing 0s and a dangling . |
space(bool) |
per-notation | Space between number and suffix |
sign_plus(bool) |
false |
Prefix non-negatives with + |
- Rounding uses Rust's standard round-half-to-even, and correctly carries
into the next suffix (
compact(999_950)→"1M", never"1000K"). - Beyond the table,
compact(above a trillion) falls back to scientific notation (compact(1e36)→"1e36"), which still round-trips throughparse. - Non-finite floats render as
"NaN","inf","-inf". - Negative zero (and negatives that round to zero) render as
"0"— never"-0". - In
parse,m/Mmeans million (compact convention), never SI milli; SI exa (E) is not parsed, to avoid clashing with exponent notation (1e3), sometricoutput at exa returns an error rather than a wrong value.
numan is #![no_std] and only needs alloc (for String). It builds for
bare-metal targets such as thumbv7em-none-eabi.
This project follows the all-contributors specification. Contributions of any kind are welcome — code, docs, bug reports, ideas, reviews! See the emoji key for how each contribution is recognized, and open a PR or issue to get involved.
Thanks goes to these wonderful people:
Tung Tran 💻 🚧 |
Licensed under either of Apache-2.0 or MIT at your option.