#fixed-significance #metric-prefix #binary-prefix #number-formatter #unit-prefix

signifix

具有公制或二进制前缀的固定有效数字的数字格式化程序

12 个版本 (破坏性)

0.10.1 2020 年 3 月 31 日
0.10.0 2019 年 4 月 12 日
0.9.0 2018 年 5 月 10 日
0.8.0 2017 年 11 月 26 日
0.2.0 2016 年 12 月 17 日

#131 in 值格式化

Download history • Rust 包仓库 435/week @ 2024-04-04 • Rust 包仓库 545/week @ 2024-04-11 • Rust 包仓库 438/week @ 2024-04-18 • Rust 包仓库 428/week @ 2024-04-25 • Rust 包仓库 419/week @ 2024-05-02 • Rust 包仓库 593/week @ 2024-05-09 • Rust 包仓库 472/week @ 2024-05-16 • Rust 包仓库 590/week @ 2024-05-23 • Rust 包仓库 647/week @ 2024-05-30 • Rust 包仓库 527/week @ 2024-06-06 • Rust 包仓库 709/week @ 2024-06-13 • Rust 包仓库 606/week @ 2024-06-20 • Rust 包仓库 668/week @ 2024-06-27 • Rust 包仓库 656/week @ 2024-07-04 • Rust 包仓库 548/week @ 2024-07-11 • Rust 包仓库 541/week @ 2024-07-18 • Rust 包仓库

2,556 每月下载量
用于 3 crates

公平 许可证

56KB
632

signifix

具有公制或二进制前缀的固定有效数字的数字格式化程序

Build Status Downloads Rust Version Documentation License

根据以下定义的三个 Signifix 符号之一格式化给定的数字

  1. 通过确定
  2. 适当的公制或二进制前缀以及

小数点位置,以维持四个有效数字的固定数量。

内容

定义了三种符号,

带有公制前缀

带有公制前缀的两个 Signifix 符号包括

  • 一个从 ±1.000 标准化到 ±999.9 的四位有效数字的有符号尾数,以覆盖特定公制前缀的三次方,这三个数字之间有三个不同的十进制分隔符,以及
  • 公制前缀符号或其占位符,在没有前缀的情况下
    • 要么附加一个空格,如下所示 ±1.234␣k,即默认符号,
    • 要么替换尾数的十进制分隔符,如下所示 ±1k234,即备用符号。

在默认符号中,占位符是另一个空格,如下所示 ±1.234␣␣ 以保持一致,而在备用符号中,它是一个数字符号,如下所示 ±1#234 以明显地将整数与尾数的分数部分分开。默认的本地敏感十进制分隔符是句点。正数的正号是可选的。

带有公制前缀

带有二进制前缀的一个 Signifix 符号包括

  • 一个经过归一化的四位有效数字的有符号尾数,从 ±1.000±999.9,再到 ±1 023,以覆盖特定二进制前缀的四个十的幂,以及在这四个数字之间和千位分隔符之间的三个不同的十进制标记位置。
  • 一个二进制前缀符号或在没有附加前缀的情况下其占位符,后面跟一个空格,例如 ±1.234␣Ki

为了保持一致性,占位符是另外两个空格,例如 ±1.234␣␣␣。区域设置相关的十进制分隔符默认为小数点,而区域设置相关的千位分隔符默认为空格,例如 ±1023␣Ki。正数的加号是可选的。

带有二进制前缀

该库从Rust 1.34稳定渠道开始工作。它位于crates.io,可以通过将signifix添加到项目的Cargo.toml依赖项中来使用。

[dependencies]
signifix = "0.10"

用法

示例

Signifix记数法的结果是固定数量的字符,防止左右跳转,同时最大限度地利用它们占用的空间。

use std::convert::TryFrom;

use signifix::{metric, binary, Result};

let metric = |number| -> Result<(String, String)> {
	let number = metric::Signifix::try_from(number)?;
	Ok((format!("{}", number), format!("{:#}", number)))
};
let binary = |number| -> Result<String> {
	let number = binary::Signifix::try_from(number)?;
	Ok(format!("{}", number))
};

// Three different decimal mark positions covering the three powers of ten
// of a particular metric prefix.
assert_eq!(metric(1E-04), Ok(("100.0 µ".into(), "100µ0".into()))); // 3rd
assert_eq!(metric(1E-03), Ok(("1.000 m".into(), "1m000".into()))); // 1st
assert_eq!(metric(1E-02), Ok(("10.00 m".into(), "10m00".into()))); // 2nd
assert_eq!(metric(1E-01), Ok(("100.0 m".into(), "100m0".into()))); // 3rd
assert_eq!(metric(1E+00), Ok(("1.000  ".into(), "1#000".into()))); // 1st
assert_eq!(metric(1E+01), Ok(("10.00  ".into(), "10#00".into()))); // 2nd
assert_eq!(metric(1E+02), Ok(("100.0  ".into(), "100#0".into()))); // 3rd
assert_eq!(metric(1E+03), Ok(("1.000 k".into(), "1k000".into()))); // 1st
assert_eq!(metric(1E+04), Ok(("10.00 k".into(), "10k00".into()))); // 2nd
assert_eq!(metric(1E+05), Ok(("100.0 k".into(), "100k0".into()))); // 3rd
assert_eq!(metric(1E+06), Ok(("1.000 M".into(), "1M000".into()))); // 1st

// Three different decimal mark positions and a thousands separator covering
// the four powers of ten of a particular binary prefix.
assert_eq!(binary(1_024f64.powi(0) * 1E+00), Ok("1.000   ".into())); // 1st
assert_eq!(binary(1_024f64.powi(0) * 1E+01), Ok("10.00   ".into())); // 2nd
assert_eq!(binary(1_024f64.powi(0) * 1E+02), Ok("100.0   ".into())); // 3rd
assert_eq!(binary(1_024f64.powi(0) * 1E+03), Ok("1 000   ".into())); // 4th
assert_eq!(binary(1_024f64.powi(1) * 1E+00), Ok("1.000 Ki".into())); // 1st
assert_eq!(binary(1_024f64.powi(1) * 1E+01), Ok("10.00 Ki".into())); // 2nd
assert_eq!(binary(1_024f64.powi(1) * 1E+02), Ok("100.0 Ki".into())); // 3rd
assert_eq!(binary(1_024f64.powi(1) * 1E+03), Ok("1 000 Ki".into())); // 4th
assert_eq!(binary(1_024f64.powi(2) * 1E+00), Ok("1.000 Mi".into())); // 1st

// Rounding over prefixes is safe against floating-point inaccuracies.
assert_eq!(metric(999.949_999_999_999_8),
	Ok(("999.9  ".into(), "999#9".into())));
assert_eq!(metric(999.949_999_999_999_9),
	Ok(("1.000 k".into(), "1k000".into())));
assert_eq!(binary(1_023.499_999_999_999_94),
	Ok("1 023   ".into()));
assert_eq!(binary(1_023.499_999_999_999_95),
	Ok("1.000 Ki".into()));

传输速率

这在平滑刷新终端中的传输速率时很有用。

use std::convert::TryFrom;

use std::f64;
use std::time::Duration;
use signifix::metric::{Signifix, Error, DEF_MIN_LEN};

let transfer_rate = |bytes: u64, duration: Duration| -> String {
	let seconds = duration.as_secs() as f64
		+ duration.subsec_nanos() as f64 * 1E-09;
	let bytes_per_second = bytes as f64 / seconds;
	let unit = "B/s";
	let rate = match Signifix::try_from(bytes_per_second) {
		Ok(rate) => if rate.factor() < 1E+00 {
			" - slow - ".into() // instead of mB/s, µB/s, ...
		} else {
			format!("{}{}", rate, unit) // normal rate
		},
		Err(case) => match case {
			Error::OutOfLowerBound(rate) => if rate == 0f64 {
				" - idle - " // no progress at all
			} else {
				" - slow - " // almost no progress
			},
			Error::OutOfUpperBound(rate) => if rate == f64::INFINITY {
				" - ---- - " // zero nanoseconds
			} else {
				" - fast - " // awkwardly fast
			},
			Error::Nan => " - ---- - ", // zero bytes in zero nanoseconds
		}.into(),
	};
	debug_assert_eq!(rate.chars().count(),
		DEF_MIN_LEN + unit.chars().count());
	rate
};

assert_eq!(transfer_rate(42_667, Duration::from_secs(300)), "142.2  B/s");
assert_eq!(transfer_rate(42_667, Duration::from_secs(030)), "1.422 kB/s");
assert_eq!(transfer_rate(42_667, Duration::from_secs(003)), "14.22 kB/s");
assert_eq!(transfer_rate(00_001, Duration::from_secs(003)), " - slow - ");
assert_eq!(transfer_rate(00_000, Duration::from_secs(003)), " - idle - ");
assert_eq!(transfer_rate(42_667, Duration::from_secs(000)), " - ---- - ");

测量安培数

或者监控一个测量的量,如电流,包括其方向,正数用零填充以与负数对齐。

use std::convert::TryFrom;

use signifix::metric::{Signifix, Result, DEF_MAX_LEN};

let measured_amps = |amps| -> Result<String> {
	if let Some(amps) = amps {
		Signifix::try_from(amps)
			.map(|amps| format!("{:>1$}A", amps, DEF_MAX_LEN))
	} else {
		Ok("     0  A".into())
	}
};

assert_eq!(measured_amps(Some( 1.476E-06)), Ok(" 1.476 µA".into()));
assert_eq!(measured_amps(None),             Ok("     0  A".into()));
assert_eq!(measured_amps(Some(-2.927E-06)), Ok("-2.927 µA".into()));

文件大小差异

而为了可视化文件大小的变化,对于正数,可能更喜欢使用加号。

use std::convert::TryFrom;

use signifix::metric::{Signifix, Error, Result};

let filesize_diff = |curr, prev| -> Result<String> {
	Signifix::try_from(curr - prev).map(|diff| format!("{:+#}", diff))
		.or_else(|case| if case == Error::OutOfLowerBound(0f64)
			{ Ok("=const".into()) } else { Err(case) })
};

assert_eq!(filesize_diff(78_346, 57_393), Ok("+20k95".into()));
assert_eq!(filesize_diff(93_837, 93_837), Ok("=const".into()));
assert_eq!(filesize_diff(27_473, 36_839), Ok("-9k366".into()));

边界统计

二进制前缀非常适合于可视化是二的幂次的数量,例如由于二进制寻址导致的内存边界。

use std::convert::TryFrom;

use signifix::binary::{Signifix, Error, Result};

let boundary_stat = |used: u64, size: u64| -> Result<String> {
	if used == 0 {
		let size = Signifix::try_from(size)?;
		return Ok(format!("    0   B (    0 %) of {}B", size));
	}
	let p100 = Signifix::try_from(used as f64 / size as f64 * 100.0)
		.map(|p100| format!("{:.*} %", p100.exponent(), p100.significand()))
		.or_else(|error| if let Error::OutOfLowerBound(_) = error
			{ Ok("  < 1 %".into()) } else { Err(error) })?;
	let used = Signifix::try_from(used)?;
	let size = Signifix::try_from(size)?;
	Ok(format!("{}B ({}) of {}B", used, p100, size))
};

assert_eq!(boundary_stat(0_000u64.pow(1), 1_024u64.pow(3)),
	Ok("    0   B (    0 %) of 1.000 GiB".into()));
assert_eq!(boundary_stat(1_024u64.pow(2), 1_024u64.pow(3)),
	Ok("1.000 MiB (  < 1 %) of 1.000 GiB".into()));
assert_eq!(boundary_stat(3_292u64.pow(2), 1_024u64.pow(3)),
	Ok("10.34 MiB (1.009 %) of 1.000 GiB".into()));
assert_eq!(boundary_stat(8_192u64.pow(2), 1_024u64.pow(3)),
	Ok("64.00 MiB (6.250 %) of 1.000 GiB".into()));
assert_eq!(boundary_stat(1_000u64.pow(3), 1_024u64.pow(3)),
	Ok("953.7 MiB (93.13 %) of 1.000 GiB".into()));
assert_eq!(boundary_stat(1_024u64.pow(3), 1_024u64.pow(3)),
	Ok("1.000 GiB (100.0 %) of 1.000 GiB".into()));

本地化

在Rust有一个推荐的可能隐式本地化系统之前,可以通过将Signifix类型包装成一个区域设置敏感的新类型来实现显式本地化,该新类型通过Signifix::fmt()方法实现Display特质。

use std::convert::TryFrom;

use signifix::binary::{Signifix, Result};

struct SignifixSi(Signifix); // English SI style (default)
struct SignifixEn(Signifix); // English locale (whitespace -> comma)
struct SignifixDe(Signifix); // German locale (comma <-> point)

impl std::fmt::Display for SignifixSi {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		std::fmt::Display::fmt(&self.0, f)
	}
}
impl std::fmt::Display for SignifixEn {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		self.0.fmt(f, ".", ",")
	}
}
impl std::fmt::Display for SignifixDe {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		self.0.fmt(f, ",", ".")
	}
}

let localizations = |number| -> Result<(String, String, String)> {
	Signifix::try_from(number).map(|number| (
		format!("{}", SignifixSi(number)),
		format!("{}", SignifixEn(number)),
		format!("{}", SignifixDe(number)),
	))
};

assert_eq!(localizations(999.9f64 * 1_024f64),
	Ok(("999.9 Ki".into(), "999.9 Ki".into(), "999,9 Ki".into())));
assert_eq!(localizations(1_000f64 * 1_024f64),
	Ok(("1 000 Ki".into(), "1,000 Ki".into(), "1.000 Ki".into())));

自定义

可以通过使用其方法从Signifix类型中提取信息来实现自定义。

use std::convert::TryFrom;

use signifix::metric::{Signifix, Result};

struct SignifixTable<'a>(&'a[Signifix]);

impl<'a> std::fmt::Display for SignifixTable<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		f.pad(" Int Fra   10³\n")?;
		f.pad("---- ---- ----\n")?;
		for entry in self.0 {
			let (integer, fractional) = entry.parts();
			f.pad(&format!("{:4} {:<3}    {:2}\n",
				integer, fractional, entry.prefix() as i32 - 8))?;
		}
		Ok(())
	}
}

let customization = |entries: &[_]| -> Result<String> {
	let mut table = Vec::with_capacity(entries.len());
	for entry in entries {
		table.push(Signifix::try_from(*entry)?);
	}
	Ok(SignifixTable(&table).to_string())
};

assert_eq!(customization(&[
	 1.234E-06,
	 12.34E+00,
	-123.4E+24,
]), Ok(concat!(
	" Int Fra   10³\n",
	"---- ---- ----\n",
	"   1 234    -2\n",
	"  12 34      0\n",
	"-123 4       8\n",
).into()));

许可证

版权所有 (c) 2016-2019 Rouven Spreckels n3vu0r@qu1x.org

使用作品时,须保留此声明,以便使用作品的任何实体都通知此声明。

免责声明:作品无任何保证。

贡献

除非你明确表示否则,你故意提交给作品以供包含的贡献将按上述方式许可,不附加任何其他条款或条件。

依赖项

~1.5MB
~39K SLoC