├── .gitignore ├── .rustfmt.toml ├── .travis.yml ├── Cargo.toml ├── LICENCE ├── README.md ├── examples └── conversions.rs └── src ├── lib.rs └── parse.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | disable_all_formatting = true 2 | # For now... 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - 1.31.0 4 | - stable 5 | - beta 6 | - nightly 7 | matrix: 8 | allow_failures: 9 | - rust: nightly 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "number_prefix" 3 | description = "Library for numeric prefixes (kilo, giga, kibi)." 4 | version = "0.4.0" 5 | 6 | authors = [ "Benjamin Sago " ] 7 | categories = ["algorithms", "no-std"] 8 | documentation = "https://docs.rs/number_prefix" 9 | exclude = ["/README.md", "/LICENCE", "/.rustfmt.toml", "/.travis.yml"] 10 | repository = "https://github.com/ogham/rust-number-prefix" 11 | keywords = ["mathematics", "numerics"] 12 | license = "MIT" 13 | readme = "README.md" 14 | 15 | [features] 16 | default = ["std"] 17 | std = [] 18 | 19 | [package.metadata.docs.rs] 20 | features = ["std"] 21 | 22 | [[example]] 23 | name = "conversions" 24 | required-features = ["std"] 25 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Benjamin Sago 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-number-prefix [![number_prefix on crates.io](http://meritbadge.herokuapp.com/number_prefix)](https://crates.io/crates/number_prefix) [![Build status](https://travis-ci.org/ogham/rust-number-prefix.svg?branch=master)](https://travis-ci.org/ogham/rust-number-prefix) 2 | 3 | This is a library for formatting numbers with numeric prefixes, such as turning “3000 metres” into “3 kilometres”, or “8705 bytes” into “8.5 KiB”. 4 | 5 | ### [View the Rustdoc](https://docs.rs/number_prefix) 6 | 7 | 8 | # Installation 9 | 10 | This crate works with [Cargo](http://crates.io). Add the following to your `Cargo.toml` dependencies section: 11 | 12 | ```toml 13 | [dependencies] 14 | number_prefix = "0.4" 15 | ``` 16 | 17 | This crate has `no_std` support. To activate it, disable the `std` Cargo feature. 18 | 19 | The earliest version of Rust that this crate is tested against is [Rust v1.31.0](https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html). 20 | 21 | 22 | # Usage 23 | 24 | The function `NumberPrefix::decimal` returns either a pair of the resulting number and its prefix, or a notice that the number was too small to have any prefix applied to it. 25 | For example: 26 | 27 | ```rust 28 | use number_prefix::NumberPrefix; 29 | 30 | let amount = 8542_f32; 31 | let result = match NumberPrefix::decimal(amount) { 32 | NumberPrefix::Standalone(bytes) => { 33 | format!("The file is {} bytes in size", bytes) 34 | } 35 | NumberPrefix::Prefixed(prefix, n) => { 36 | format!("The file is {:.1} {}B in size", n, prefix) 37 | } 38 | }; 39 | 40 | assert_eq!("The file is 8.5 kB in size", result); 41 | ``` 42 | 43 | The `{:.1}` part of the formatting string tells it to restrict the output to only one decimal place. 44 | This value is calculated by repeatedly dividing the number by 1000 until it becomes less than that, which in this case results in 8.542, which gets rounded down. 45 | Because only one division had to take place, the function also returns the decimal prefix `Kilo`, which gets converted to its internationally-recognised symbol when formatted as a string. 46 | 47 | If the value is too small to have any prefixes applied to it — in this case, if it’s under 1000 — then the standalone value will be returned: 48 | 49 | ```rust 50 | use number_prefix::NumberPrefix; 51 | 52 | let amount = 705_f32; 53 | let result = match NumberPrefix::decimal(amount) { 54 | NumberPrefix::Standalone(bytes) => { 55 | format!("The file is {} bytes in size", bytes) 56 | } 57 | NumberPrefix::Prefixed(prefix, n) => { 58 | format!("The file is {:.1} {}B in size", n, prefix) 59 | } 60 | }; 61 | 62 | assert_eq!("The file is 705 bytes in size", result); 63 | ``` 64 | 65 | In this particular example, the user expects different formatting for both bytes and kilobytes: while prefixed values are given more precision, there’s no point using anything other than whole numbers for just byte amounts. 66 | This is why the function pays attention to values without any prefixes — they often need to be special-cased. 67 | 68 | 69 | ## Binary Prefixes 70 | 71 | This library also allows you to use the *binary prefixes*, which use the number 1024 (210) as the multiplier, rather than the more common 1000 (103). 72 | This uses the `NumberPrefix::binary` function. For example: 73 | 74 | ```rust 75 | use number_prefix::NumberPrefix; 76 | 77 | let amount = 8542_f32; 78 | let result = match NumberPrefix::binary(amount) { 79 | NumberPrefix::Standalone(bytes) => { 80 | format!("The file is {} bytes in size", bytes) 81 | } 82 | NumberPrefix::Prefixed(prefix, n) => { 83 | format!("The file is {:.1} {}B in size", n, prefix) 84 | } 85 | }; 86 | 87 | assert_eq!("The file is 8.3 KiB in size", result); 88 | ``` 89 | 90 | A kibibyte is slightly larger than a kilobyte, so the number is smaller in the result; but other than that, it works in exactly the same way, with the binary prefix being converted to a symbol automatically. 91 | 92 | 93 | ## Which type of prefix should I use? 94 | 95 | There is no correct answer to this question! 96 | Current practice is to use the binary prefixes for numbers of *bytes*, while still using the decimal prefixes for everything else. 97 | Computers work with powers of two, rather than powers of ten, and by using the binary prefixes, you get a more accurate representation of the amount of data. 98 | 99 | 100 | ## Prefix Names 101 | 102 | If you need to describe your unit in actual words, rather than just with the symbol, use one of the `upper`, `caps`, `lower`, or `symbol`, which output the prefix in a variety of formats. For example: 103 | 104 | ```rust 105 | use number_prefix::NumberPrefix; 106 | 107 | let amount = 8542_f32; 108 | let result = match NumberPrefix::decimal(amount) { 109 | NumberPrefix::Standalone(bytes) => { 110 | format!("The file is {} bytes in size", bytes) 111 | } 112 | NumberPrefix::Prefixed(prefix, n) => { 113 | format!("The file is {:.1} {}bytes in size", n, prefix.lower()) 114 | } 115 | }; 116 | 117 | assert_eq!("The file is 8.5 kilobytes in size", result); 118 | ``` 119 | 120 | 121 | ## String Parsing 122 | 123 | There is a `FromStr` implementation for `NumberPrefix` that parses strings containing numbers and trailing prefixes, such as `7.5E`. 124 | 125 | Currently, the only supported units are `b` and `B` for bytes, and `m` for metres. 126 | Whitespace is allowed between the number and the rest of the string. 127 | 128 | ```rust 129 | use number_prefix::{NumberPrefix, Prefix}; 130 | 131 | assert_eq!("7.05E".parse::>(), 132 | Ok(NumberPrefix::Prefixed(Prefix::Exa, 7.05_f64))); 133 | 134 | assert_eq!("7.05".parse::>(), 135 | Ok(NumberPrefix::Standalone(7.05_f64))); 136 | 137 | assert_eq!("7.05 GiB".parse::>(), 138 | Ok(NumberPrefix::Prefixed(Prefix::Gibi, 7.05_f64))); 139 | ``` 140 | -------------------------------------------------------------------------------- /examples/conversions.rs: -------------------------------------------------------------------------------- 1 | /// This example prints out the conversions for increasingly-large numbers, to 2 | /// showcase how the numbers change as the input gets bigger. 3 | /// It results in this: 4 | /// 5 | /// ```text 6 | /// 1000 bytes is 1.000 kB and 1000 bytes 7 | /// 1000000 bytes is 1.000 MB and 976.562 KiB 8 | /// 1000000000 bytes is 1.000 GB and 953.674 MiB 9 | /// 1000000000000 bytes is 1.000 TB and 931.323 GiB 10 | /// 1000000000000000 bytes is 1.000 PB and 909.495 TiB 11 | /// 1000000000000000000 bytes is 1.000 EB and 888.178 PiB 12 | /// 1000000000000000000000 bytes is 1.000 ZB and 867.362 EiB 13 | /// 1000000000000000000000000 bytes is 1.000 YB and 847.033 ZiB 14 | /// 15 | /// 1024 bytes is 1.000 KiB and 1.024 kB 16 | /// 1048576 bytes is 1.000 MiB and 1.049 MB 17 | /// 1073741824 bytes is 1.000 GiB and 1.074 GB 18 | /// 1099511627776 bytes is 1.000 TiB and 1.100 TB 19 | /// 1125899906842624 bytes is 1.000 PiB and 1.126 PB 20 | /// 1152921504606847000 bytes is 1.000 EiB and 1.153 EB 21 | /// 1180591620717411300000 bytes is 1.000 ZiB and 1.181 ZB 22 | /// 1208925819614629200000000 bytes is 1.000 YiB and 1.209 YB 23 | /// ``` 24 | 25 | extern crate number_prefix; 26 | use number_prefix::NumberPrefix; 27 | use std::fmt::Display; 28 | 29 | 30 | fn main() { 31 | 32 | // part one, decimal prefixes 33 | let mut n = 1_f64; 34 | for _ in 0 .. 8 { 35 | n *= 1000_f64; 36 | 37 | let decimal = format_prefix(NumberPrefix::decimal(n)); 38 | let binary = format_prefix(NumberPrefix::binary(n)); 39 | println!("{:26} bytes is {} and {:10}", n, decimal, binary); 40 | } 41 | 42 | println!(); 43 | 44 | // part two, binary prefixes 45 | let mut n = 1_f64; 46 | for _ in 0 .. 8 { 47 | n *= 1024_f64; 48 | 49 | let decimal = format_prefix(NumberPrefix::decimal(n)); 50 | let binary = format_prefix(NumberPrefix::binary(n)); 51 | println!("{:26} bytes is {} and {:10}", n, binary, decimal); 52 | } 53 | } 54 | 55 | 56 | fn format_prefix(np: NumberPrefix) -> String { 57 | match np { 58 | NumberPrefix::Prefixed(prefix, n) => format!("{:.3} {}B", n, prefix), 59 | NumberPrefix::Standalone(bytes) => format!("{} bytes", bytes), 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![warn(missing_copy_implementations)] 3 | #![warn(missing_debug_implementations)] 4 | #![warn(missing_docs)] 5 | #![warn(nonstandard_style)] 6 | #![warn(trivial_numeric_casts)] 7 | #![warn(unreachable_pub)] 8 | #![warn(unused)] 9 | 10 | 11 | //! This is a library for formatting numbers with numeric prefixes, such as 12 | //! turning “3000 metres” into “3 kilometres”, or “8705 bytes” into “8.5 KiB”. 13 | //! 14 | //! 15 | //! # Usage 16 | //! 17 | //! The function [`NumberPrefix::decimal`](enum.NumberPrefix.html#method.decimal) 18 | //! returns either a pair of the resulting number and its prefix, or a 19 | //! notice that the number was too small to have any prefix applied to it. For 20 | //! example: 21 | //! 22 | //! ``` 23 | //! use number_prefix::NumberPrefix; 24 | //! 25 | //! let amount = 8542_f32; 26 | //! let result = match NumberPrefix::decimal(amount) { 27 | //! NumberPrefix::Standalone(bytes) => { 28 | //! format!("The file is {} bytes in size", bytes) 29 | //! } 30 | //! NumberPrefix::Prefixed(prefix, n) => { 31 | //! format!("The file is {:.1} {}B in size", n, prefix) 32 | //! } 33 | //! }; 34 | //! 35 | //! assert_eq!("The file is 8.5 kB in size", result); 36 | //! ``` 37 | //! 38 | //! The `{:.1}` part of the formatting string tells it to restrict the 39 | //! output to only one decimal place. This value is calculated by repeatedly 40 | //! dividing the number by 1000 until it becomes less than that, which in this 41 | //! case results in 8.542, which gets rounded down. Because only one division 42 | //! had to take place, the function also returns the decimal prefix `Kilo`, 43 | //! which gets converted to its internationally-recognised symbol when 44 | //! formatted as a string. 45 | //! 46 | //! If the value is too small to have any prefixes applied to it — in this case, 47 | //! if it’s under 1000 — then the standalone value will be returned: 48 | //! 49 | //! ``` 50 | //! use number_prefix::NumberPrefix; 51 | //! 52 | //! let amount = 705_f32; 53 | //! let result = match NumberPrefix::decimal(amount) { 54 | //! NumberPrefix::Standalone(bytes) => { 55 | //! format!("The file is {} bytes in size", bytes) 56 | //! } 57 | //! NumberPrefix::Prefixed(prefix, n) => { 58 | //! format!("The file is {:.1} {}B in size", n, prefix) 59 | //! } 60 | //! }; 61 | //! 62 | //! assert_eq!("The file is 705 bytes in size", result); 63 | //! ``` 64 | //! 65 | //! In this particular example, the user expects different formatting for 66 | //! both bytes and kilobytes: while prefixed values are given more precision, 67 | //! there’s no point using anything other than whole numbers for just byte 68 | //! amounts. This is why the function pays attention to values without any 69 | //! prefixes — they often need to be special-cased. 70 | //! 71 | //! 72 | //! ## Binary Prefixes 73 | //! 74 | //! This library also allows you to use the *binary prefixes*, which use the 75 | //! number 1024 (210) as the multiplier, rather than the more common 1000 76 | //! (103). This uses the 77 | //! [`NumberPrefix::binary`](enum.NumberPrefix.html#method.binary) function. 78 | //! For example: 79 | //! 80 | //! ``` 81 | //! use number_prefix::NumberPrefix; 82 | //! 83 | //! let amount = 8542_f32; 84 | //! let result = match NumberPrefix::binary(amount) { 85 | //! NumberPrefix::Standalone(bytes) => { 86 | //! format!("The file is {} bytes in size", bytes) 87 | //! } 88 | //! NumberPrefix::Prefixed(prefix, n) => { 89 | //! format!("The file is {:.1} {}B in size", n, prefix) 90 | //! } 91 | //! }; 92 | //! 93 | //! assert_eq!("The file is 8.3 KiB in size", result); 94 | //! ``` 95 | //! 96 | //! A kibibyte is slightly larger than a kilobyte, so the number is smaller 97 | //! in the result; but other than that, it works in exactly the same way, with 98 | //! the binary prefix being converted to a symbol automatically. 99 | //! 100 | //! 101 | //! ## Which type of prefix should I use? 102 | //! 103 | //! There is no correct answer this question! Common practice is to use 104 | //! the binary prefixes for numbers of *bytes*, while still using the decimal 105 | //! prefixes for everything else. Computers work with powers of two, rather than 106 | //! powers of ten, and by using the binary prefixes, you get a more accurate 107 | //! representation of the amount of data. 108 | //! 109 | //! 110 | //! ## Prefix Names 111 | //! 112 | //! If you need to describe your unit in actual words, rather than just with the 113 | //! symbol, use one of the `upper`, `caps`, `lower`, or `symbol`, which output the 114 | //! prefix in a variety of formats. For example: 115 | //! 116 | //! ``` 117 | //! use number_prefix::NumberPrefix; 118 | //! 119 | //! let amount = 8542_f32; 120 | //! let result = match NumberPrefix::decimal(amount) { 121 | //! NumberPrefix::Standalone(bytes) => { 122 | //! format!("The file is {} bytes in size", bytes) 123 | //! } 124 | //! NumberPrefix::Prefixed(prefix, n) => { 125 | //! format!("The file is {:.1} {}bytes in size", n, prefix.lower()) 126 | //! } 127 | //! }; 128 | //! 129 | //! assert_eq!("The file is 8.5 kilobytes in size", result); 130 | //! ``` 131 | //! 132 | //! 133 | //! ## String Parsing 134 | //! 135 | //! There is a `FromStr` implementation for `NumberPrefix` that parses 136 | //! strings containing numbers and trailing prefixes, such as `7.5E`. 137 | //! 138 | //! Currently, the only supported units are `b` and `B` for bytes, and `m` for 139 | //! metres. Whitespace is allowed between the number and the rest of the string. 140 | //! 141 | //! ``` 142 | //! use number_prefix::{NumberPrefix, Prefix}; 143 | //! 144 | //! assert_eq!("7.05E".parse::>(), 145 | //! Ok(NumberPrefix::Prefixed(Prefix::Exa, 7.05_f64))); 146 | //! 147 | //! assert_eq!("7.05".parse::>(), 148 | //! Ok(NumberPrefix::Standalone(7.05_f64))); 149 | //! 150 | //! assert_eq!("7.05 GiB".parse::>(), 151 | //! Ok(NumberPrefix::Prefixed(Prefix::Gibi, 7.05_f64))); 152 | //! ``` 153 | 154 | 155 | #![cfg_attr(not(feature = "std"), no_std)] 156 | 157 | #[cfg(feature = "std")] 158 | mod parse; 159 | 160 | #[cfg(not(feature = "std"))] 161 | use core::ops::{Neg, Div}; 162 | 163 | #[cfg(feature = "std")] 164 | use std::{fmt, ops::{Neg, Div}}; 165 | 166 | 167 | /// A numeric prefix, either binary or decimal. 168 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] 169 | pub enum Prefix { 170 | 171 | /// _kilo_, 103 or 10001. 172 | /// From the Greek ‘χίλιοι’ (‘chilioi’), meaning ‘thousand’. 173 | Kilo, 174 | 175 | /// _mega_, 106 or 10002. 176 | /// From the Ancient Greek ‘μέγας’ (‘megas’), meaning ‘great’. 177 | Mega, 178 | 179 | /// _giga_, 109 or 10003. 180 | /// From the Greek ‘γίγας’ (‘gigas’), meaning ‘giant’. 181 | Giga, 182 | 183 | /// _tera_, 1012 or 10004. 184 | /// From the Greek ‘τέρας’ (‘teras’), meaning ‘monster’. 185 | Tera, 186 | 187 | /// _peta_, 1015 or 10005. 188 | /// From the Greek ‘πέντε’ (‘pente’), meaning ‘five’. 189 | Peta, 190 | 191 | /// _exa_, 1018 or 10006. 192 | /// From the Greek ‘ἕξ’ (‘hex’), meaning ‘six’. 193 | Exa, 194 | 195 | /// _zetta_, 1021 or 10007. 196 | /// From the Latin ‘septem’, meaning ‘seven’. 197 | Zetta, 198 | 199 | /// _yotta_, 1024 or 10008. 200 | /// From the Green ‘οκτώ’ (‘okto’), meaning ‘eight’. 201 | Yotta, 202 | 203 | /// _kibi_, 210 or 10241. 204 | /// The binary version of _kilo_. 205 | Kibi, 206 | 207 | /// _mebi_, 220 or 10242. 208 | /// The binary version of _mega_. 209 | Mebi, 210 | 211 | /// _gibi_, 230 or 10243. 212 | /// The binary version of _giga_. 213 | Gibi, 214 | 215 | /// _tebi_, 240 or 10244. 216 | /// The binary version of _tera_. 217 | Tebi, 218 | 219 | /// _pebi_, 250 or 10245. 220 | /// The binary version of _peta_. 221 | Pebi, 222 | 223 | /// _exbi_, 260 or 10246. 224 | /// The binary version of _exa_. 225 | Exbi, 226 | // you can download exa binaries at https://exa.website/#installation 227 | 228 | /// _zebi_, 270 or 10247. 229 | /// The binary version of _zetta_. 230 | Zebi, 231 | 232 | /// _yobi_, 280 or 10248. 233 | /// The binary version of _yotta_. 234 | Yobi, 235 | } 236 | 237 | 238 | /// The result of trying to apply a prefix to a floating-point value. 239 | #[derive(PartialEq, Eq, Clone, Debug)] 240 | pub enum NumberPrefix { 241 | 242 | /// A **standalone** value is returned when the number is too small to 243 | /// have any prefixes applied to it. This is commonly a special case, so 244 | /// is handled separately. 245 | Standalone(F), 246 | 247 | /// A **prefixed** value *is* large enough for prefixes. This holds the 248 | /// prefix, as well as the resulting value. 249 | Prefixed(Prefix, F), 250 | } 251 | 252 | impl NumberPrefix { 253 | 254 | /// Formats the given floating-point number using **decimal** prefixes. 255 | /// 256 | /// This function accepts both `f32` and `f64` values. If you’re trying to 257 | /// format an integer, you’ll have to cast it first. 258 | /// 259 | /// # Examples 260 | /// 261 | /// ``` 262 | /// use number_prefix::{Prefix, NumberPrefix}; 263 | /// 264 | /// assert_eq!(NumberPrefix::decimal(1_000_000_000_f32), 265 | /// NumberPrefix::Prefixed(Prefix::Giga, 1_f32)); 266 | /// ``` 267 | pub fn decimal(amount: F) -> Self { 268 | use self::Prefix::*; 269 | Self::format_number(amount, Amounts::NUM_1000, [Kilo, Mega, Giga, Tera, Peta, Exa, Zetta, Yotta]) 270 | } 271 | 272 | /// Formats the given floating-point number using **binary** prefixes. 273 | /// 274 | /// This function accepts both `f32` and `f64` values. If you’re trying to 275 | /// format an integer, you’ll have to cast it first. 276 | /// 277 | /// # Examples 278 | /// 279 | /// ``` 280 | /// use number_prefix::{Prefix, NumberPrefix}; 281 | /// 282 | /// assert_eq!(NumberPrefix::binary(1_073_741_824_f64), 283 | /// NumberPrefix::Prefixed(Prefix::Gibi, 1_f64)); 284 | /// ``` 285 | pub fn binary(amount: F) -> Self { 286 | use self::Prefix::*; 287 | Self::format_number(amount, Amounts::NUM_1024, [Kibi, Mebi, Gibi, Tebi, Pebi, Exbi, Zebi, Yobi]) 288 | } 289 | 290 | fn format_number(mut amount: F, kilo: F, prefixes: [Prefix; 8]) -> Self { 291 | 292 | // For negative numbers, flip it to positive, do the processing, then 293 | // flip it back to negative again afterwards. 294 | let was_negative = if amount.is_negative() { amount = -amount; true } else { false }; 295 | 296 | let mut prefix = 0; 297 | while amount >= kilo && prefix < 8 { 298 | amount = amount / kilo; 299 | prefix += 1; 300 | } 301 | 302 | if was_negative { 303 | amount = -amount; 304 | } 305 | 306 | if prefix == 0 { 307 | NumberPrefix::Standalone(amount) 308 | } 309 | else { 310 | NumberPrefix::Prefixed(prefixes[prefix - 1], amount) 311 | } 312 | } 313 | } 314 | 315 | #[cfg(feature = "std")] 316 | impl fmt::Display for Prefix { 317 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 318 | write!(f, "{}", self.symbol()) 319 | } 320 | } 321 | 322 | impl Prefix { 323 | 324 | /// Returns the name in uppercase, such as “KILO”. 325 | /// 326 | /// # Examples 327 | /// 328 | /// ``` 329 | /// use number_prefix::Prefix; 330 | /// 331 | /// assert_eq!("GIGA", Prefix::Giga.upper()); 332 | /// assert_eq!("GIBI", Prefix::Gibi.upper()); 333 | /// ``` 334 | pub fn upper(self) -> &'static str { 335 | use self::Prefix::*; 336 | match self { 337 | Kilo => "KILO", Mega => "MEGA", Giga => "GIGA", Tera => "TERA", 338 | Peta => "PETA", Exa => "EXA", Zetta => "ZETTA", Yotta => "YOTTA", 339 | Kibi => "KIBI", Mebi => "MEBI", Gibi => "GIBI", Tebi => "TEBI", 340 | Pebi => "PEBI", Exbi => "EXBI", Zebi => "ZEBI", Yobi => "YOBI", 341 | } 342 | } 343 | 344 | /// Returns the name with the first letter capitalised, such as “Mega”. 345 | /// 346 | /// # Examples 347 | /// 348 | /// ``` 349 | /// use number_prefix::Prefix; 350 | /// 351 | /// assert_eq!("Giga", Prefix::Giga.caps()); 352 | /// assert_eq!("Gibi", Prefix::Gibi.caps()); 353 | /// ``` 354 | pub fn caps(self) -> &'static str { 355 | use self::Prefix::*; 356 | match self { 357 | Kilo => "Kilo", Mega => "Mega", Giga => "Giga", Tera => "Tera", 358 | Peta => "Peta", Exa => "Exa", Zetta => "Zetta", Yotta => "Yotta", 359 | Kibi => "Kibi", Mebi => "Mebi", Gibi => "Gibi", Tebi => "Tebi", 360 | Pebi => "Pebi", Exbi => "Exbi", Zebi => "Zebi", Yobi => "Yobi", 361 | } 362 | } 363 | 364 | /// Returns the name in lowercase, such as “giga”. 365 | /// 366 | /// # Examples 367 | /// 368 | /// ``` 369 | /// use number_prefix::Prefix; 370 | /// 371 | /// assert_eq!("giga", Prefix::Giga.lower()); 372 | /// assert_eq!("gibi", Prefix::Gibi.lower()); 373 | /// ``` 374 | pub fn lower(self) -> &'static str { 375 | use self::Prefix::*; 376 | match self { 377 | Kilo => "kilo", Mega => "mega", Giga => "giga", Tera => "tera", 378 | Peta => "peta", Exa => "exa", Zetta => "zetta", Yotta => "yotta", 379 | Kibi => "kibi", Mebi => "mebi", Gibi => "gibi", Tebi => "tebi", 380 | Pebi => "pebi", Exbi => "exbi", Zebi => "zebi", Yobi => "yobi", 381 | } 382 | } 383 | 384 | /// Returns the short-hand symbol, such as “T” (for “tera”). 385 | /// 386 | /// # Examples 387 | /// 388 | /// ``` 389 | /// use number_prefix::Prefix; 390 | /// 391 | /// assert_eq!("G", Prefix::Giga.symbol()); 392 | /// assert_eq!("Gi", Prefix::Gibi.symbol()); 393 | /// ``` 394 | pub fn symbol(self) -> &'static str { 395 | use self::Prefix::*; 396 | match self { 397 | Kilo => "k", Mega => "M", Giga => "G", Tera => "T", 398 | Peta => "P", Exa => "E", Zetta => "Z", Yotta => "Y", 399 | Kibi => "Ki", Mebi => "Mi", Gibi => "Gi", Tebi => "Ti", 400 | Pebi => "Pi", Exbi => "Ei", Zebi => "Zi", Yobi => "Yi", 401 | } 402 | } 403 | } 404 | 405 | /// Traits for floating-point values for both the possible multipliers. They 406 | /// need to be Copy, have defined 1000 and 1024s, and implement a bunch of 407 | /// operators. 408 | pub trait Amounts: Copy + Sized + PartialOrd + Div + Neg { 409 | 410 | /// The constant representing 1000, for decimal prefixes. 411 | const NUM_1000: Self; 412 | 413 | /// The constant representing 1024, for binary prefixes. 414 | const NUM_1024: Self; 415 | 416 | /// Whether this number is negative. 417 | /// This is used internally. 418 | fn is_negative(self) -> bool; 419 | } 420 | 421 | impl Amounts for f32 { 422 | const NUM_1000: Self = 1000_f32; 423 | const NUM_1024: Self = 1024_f32; 424 | 425 | fn is_negative(self) -> bool { 426 | self.is_sign_negative() 427 | } 428 | } 429 | 430 | impl Amounts for f64 { 431 | const NUM_1000: Self = 1000_f64; 432 | const NUM_1024: Self = 1024_f64; 433 | 434 | fn is_negative(self) -> bool { 435 | self.is_sign_negative() 436 | } 437 | } 438 | 439 | 440 | #[cfg(test)] 441 | mod test { 442 | use super::{NumberPrefix, Prefix}; 443 | 444 | #[test] 445 | fn decimal_minus_one_billion() { 446 | assert_eq!(NumberPrefix::decimal(-1_000_000_000_f64), 447 | NumberPrefix::Prefixed(Prefix::Giga, -1f64)) 448 | } 449 | 450 | #[test] 451 | fn decimal_minus_one() { 452 | assert_eq!(NumberPrefix::decimal(-1f64), 453 | NumberPrefix::Standalone(-1f64)) 454 | } 455 | 456 | #[test] 457 | fn decimal_0() { 458 | assert_eq!(NumberPrefix::decimal(0f64), 459 | NumberPrefix::Standalone(0f64)) 460 | } 461 | 462 | #[test] 463 | fn decimal_999() { 464 | assert_eq!(NumberPrefix::decimal(999f32), 465 | NumberPrefix::Standalone(999f32)) 466 | } 467 | 468 | #[test] 469 | fn decimal_1000() { 470 | assert_eq!(NumberPrefix::decimal(1000f32), 471 | NumberPrefix::Prefixed(Prefix::Kilo, 1f32)) 472 | } 473 | 474 | #[test] 475 | fn decimal_1030() { 476 | assert_eq!(NumberPrefix::decimal(1030f32), 477 | NumberPrefix::Prefixed(Prefix::Kilo, 1.03f32)) 478 | } 479 | 480 | #[test] 481 | fn decimal_1100() { 482 | assert_eq!(NumberPrefix::decimal(1100f64), 483 | NumberPrefix::Prefixed(Prefix::Kilo, 1.1f64)) 484 | } 485 | 486 | #[test] 487 | fn decimal_1111() { 488 | assert_eq!(NumberPrefix::decimal(1111f64), 489 | NumberPrefix::Prefixed(Prefix::Kilo, 1.111f64)) 490 | } 491 | 492 | #[test] 493 | fn binary_126456() { 494 | assert_eq!(NumberPrefix::binary(126_456f32), 495 | NumberPrefix::Prefixed(Prefix::Kibi, 123.492188f32)) 496 | } 497 | 498 | #[test] 499 | fn binary_1048576() { 500 | assert_eq!(NumberPrefix::binary(1_048_576f64), 501 | NumberPrefix::Prefixed(Prefix::Mebi, 1f64)) 502 | } 503 | 504 | #[test] 505 | fn binary_1073741824() { 506 | assert_eq!(NumberPrefix::binary(2_147_483_648f32), 507 | NumberPrefix::Prefixed(Prefix::Gibi, 2f32)) 508 | } 509 | 510 | #[test] 511 | fn giga() { 512 | assert_eq!(NumberPrefix::decimal(1_000_000_000f64), 513 | NumberPrefix::Prefixed(Prefix::Giga, 1f64)) 514 | } 515 | 516 | #[test] 517 | fn tera() { 518 | assert_eq!(NumberPrefix::decimal(1_000_000_000_000f64), 519 | NumberPrefix::Prefixed(Prefix::Tera, 1f64)) 520 | } 521 | 522 | #[test] 523 | fn peta() { 524 | assert_eq!(NumberPrefix::decimal(1_000_000_000_000_000f64), 525 | NumberPrefix::Prefixed(Prefix::Peta, 1f64)) 526 | } 527 | 528 | #[test] 529 | fn exa() { 530 | assert_eq!(NumberPrefix::decimal(1_000_000_000_000_000_000f64), 531 | NumberPrefix::Prefixed(Prefix::Exa, 1f64)) 532 | } 533 | 534 | #[test] 535 | fn zetta() { 536 | assert_eq!(NumberPrefix::decimal(1_000_000_000_000_000_000_000f64), 537 | NumberPrefix::Prefixed(Prefix::Zetta, 1f64)) 538 | } 539 | 540 | #[test] 541 | fn yotta() { 542 | assert_eq!(NumberPrefix::decimal(1_000_000_000_000_000_000_000_000f64), 543 | NumberPrefix::Prefixed(Prefix::Yotta, 1f64)) 544 | } 545 | 546 | #[test] 547 | #[allow(overflowing_literals)] 548 | fn and_so_on() { 549 | // When you hit yotta, don't keep going 550 | assert_eq!(NumberPrefix::decimal(1_000_000_000_000_000_000_000_000_000f64), 551 | NumberPrefix::Prefixed(Prefix::Yotta, 1000f64)) 552 | } 553 | } 554 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fmt, str}; 2 | 3 | use super::{NumberPrefix, Prefix}; 4 | 5 | 6 | impl str::FromStr for NumberPrefix { 7 | type Err = NumberPrefixParseError; 8 | 9 | fn from_str(s: &str) -> Result { 10 | let splitted = s.find(|p| { 11 | p == 'k' || p == 'K' || p == 'M' || p == 'G' || p == 'T' || 12 | p == 'P' || p == 'E' || p == 'Z' || p == 'Y' 13 | }); 14 | 15 | let num_prefix = s.split_at(splitted.unwrap_or(s.len())); 16 | let num = match num_prefix.0.trim().parse::() { 17 | Ok(n) => n, 18 | Err(_) => return Err(NumberPrefixParseError(())), 19 | }; 20 | 21 | let prefix_unit = num_prefix.1.trim_matches(|p| 22 | p == 'b' || p == 'B' || p == 'm' 23 | ); 24 | 25 | let prefix = match prefix_unit { 26 | "k" | 27 | "K" => Prefix::Kilo, 28 | "M" => Prefix::Mega, 29 | "G" => Prefix::Giga, 30 | "T" => Prefix::Tera, 31 | "P" => Prefix::Peta, 32 | "E" => Prefix::Exa, 33 | "Z" => Prefix::Zetta, 34 | "Y" => Prefix::Yotta, 35 | "Ki" => Prefix::Kibi, 36 | "Mi" => Prefix::Mebi, 37 | "Gi" => Prefix::Gibi, 38 | "Ti" => Prefix::Tebi, 39 | "Pi" => Prefix::Pebi, 40 | "Ei" => Prefix::Exbi, 41 | "Zi" => Prefix::Zebi, 42 | "Yi" => Prefix::Yobi, 43 | "" => return Ok(NumberPrefix::Standalone(num)), 44 | _ => return Err(NumberPrefixParseError(())), 45 | }; 46 | 47 | Ok(NumberPrefix::Prefixed(prefix, num)) 48 | } 49 | } 50 | 51 | 52 | /// The error returned when a `NumberPrefix` is failed to be parsed. 53 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 54 | pub struct NumberPrefixParseError(()); 55 | 56 | impl fmt::Display for NumberPrefixParseError { 57 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 58 | fmt.write_str("invalid prefix syntax") 59 | } 60 | } 61 | 62 | impl Error for NumberPrefixParseError { 63 | } 64 | 65 | 66 | #[cfg(test)] 67 | mod test { 68 | use super::*; 69 | 70 | #[test] 71 | fn parse_examples() { 72 | let parse_example_a = "7.05E".parse::>(); 73 | let parse_example_b = "7.05".parse::>(); 74 | let parse_example_c = "7.05 GiB".parse::>(); 75 | 76 | assert_eq!(parse_example_a, Ok(NumberPrefix::Prefixed(Prefix::Exa, 7.05_f64))); 77 | assert_eq!(parse_example_b, Ok(NumberPrefix::Standalone(7.05_f64))); 78 | assert_eq!(parse_example_c, Ok(NumberPrefix::Prefixed(Prefix::Gibi, 7.05_f64))); 79 | } 80 | 81 | #[test] 82 | fn bad_parse() { 83 | let parsed = "bogo meters per second".parse::>(); 84 | 85 | assert_ne!(parsed, Ok(NumberPrefix::Prefixed(Prefix::Kilo, 7.05_f64))); 86 | } 87 | } 88 | --------------------------------------------------------------------------------