├── rust-toolchain.toml ├── .gitignore ├── CONTRIBUTING.md ├── .github ├── dependabot.yml └── workflows │ ├── test.yml │ └── release.yml ├── tests └── i32.rs ├── LICENSE ├── Changelog.md ├── README.md ├── Cargo.toml ├── benches └── benches.rs └── src └── lib.rs /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.70.0" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | rls.toml 5 | .vscode -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributions are welcome! Just create a pull request. 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: [push, pull_request] 4 | 5 | 6 | jobs: 7 | 8 | linux: 9 | name: Test & Bench Linux 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Test std 17 | run: cargo test 18 | 19 | - name: Test no-std 20 | run: cargo test --no-default-features 21 | 22 | - name: Bench 23 | run: cargo bench 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 8 | 9 | jobs: 10 | 11 | release: 12 | name: Release to crates.io 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | - name: Publish to crates.io 19 | env: 20 | CARGO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 21 | run: | 22 | cargo publish --token "${CARGO_TOKEN}" -------------------------------------------------------------------------------- /tests/i32.rs: -------------------------------------------------------------------------------- 1 | use proptest::prelude::proptest; 2 | use atoi::{FromRadix10, FromRadix10Signed}; 3 | 4 | type N = i32; 5 | 6 | proptest! { 7 | #[test] 8 | fn roundtrip_without_sign(n in 0..=N::MAX) { 9 | let text = n.to_string(); 10 | let (actual, len) = N::from_radix_10(text.as_bytes()); 11 | 12 | assert_eq!(text.len(), len); 13 | assert_eq!(n, actual); 14 | } 15 | 16 | #[test] 17 | fn roundtrip_with_sign(n in N::MIN..=N::MAX) { 18 | let text = n.to_string(); 19 | let (actual, len) = N::from_radix_10_signed(text.as_bytes()); 20 | 21 | assert_eq!(text.len(), len); 22 | assert_eq!(n, actual); 23 | } 24 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 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 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 3.0.0 (next) 5 | ----- 6 | 7 | * Minimal supported compiler is Rust 1.70.0 8 | 9 | 2.0.0 10 | ----- 11 | 12 | * Minimal supported compiler is Rust 1.57.0 13 | * Support for `no-std` by making `std` a default feature which can be opted out of. 14 | 15 | 1.0.0 16 | ----- 17 | 18 | * Minimal supported compiler is Rust 1.56.0 19 | * Changed Rust edition to 2021 20 | * Stabilized interface 21 | 22 | 0.4.0 23 | ----- 24 | 25 | * The `atoi` function now supports parsing signed integers. Use the `FromRadix10` trait directly if 26 | you wish to not allow leading `+` or `-` signs. 27 | 28 | 0.3.3 29 | ----- 30 | 31 | * Introduce `FromRadix10Signed` and `FromRadix10SignedChecked` for parsing signed integers. 32 | 33 | 0.3.2 34 | ----- 35 | 36 | * Add support for hex numbers through `FromRadix16` and `FromRadix16Checked`. 37 | * Fix: Documentation of `FromRadix10Checked` now has code samples using this trait. 38 | 39 | 0.3.1 40 | ----- 41 | 42 | * Fix: Fixed documentation of `atoi`s overflow behaviour. 43 | 44 | 0.3.0 45 | ----- 46 | 47 | * Added `From_radix_10_checked`. 48 | * Breaking change: atoi now returns `None` on overflow 49 | 50 | 0.2.4 51 | ----- 52 | 53 | * Documentation now hints at `FromRadix10` trait. 54 | * Updated to Rust 2018 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # atoi-rs 2 | 3 | Parse integers directly from `[u8]` slices in safe code 4 | 5 | ## Reasons to use this crate 6 | 7 | Starting from a binary or ascii format you can parse an integer around three times as fast as with 8 | the more idiomatic detour over utf8. The crate comes with benchmarks so you can see for yourself. 9 | 10 | The `FromRadix10Checked` trait also provides a way to parse integers very fast and safe, as its 11 | implementation only performs checked arithmetics for the one digit that may actually overflow. 12 | 13 | ## Example 14 | 15 | Parsing from a slice 16 | 17 | ```rust 18 | use atoi::atoi; 19 | assert_eq!(Some(42), atoi::(b"42")); 20 | ``` 21 | 22 | Note that if you want to know how much of the input has been used, you can use the 23 | `FromRadix10` trait, for example: 24 | 25 | ```rust 26 | use atoi::FromRadix10; 27 | 28 | /// Return the parsed integer and remaining slice if successful. 29 | fn atoi_with_rest(text: &[u8]) -> Option<(&[u8], I)> { 30 | match I::from_radix_10(text) { 31 | (_, 0) => None, 32 | (n, used) => Some((&text[used..], n)), 33 | } 34 | } 35 | ``` 36 | 37 | This [crate](https://www.crates.io/crates/atoi) has more to offer! Check out the full documentation 38 | at [docs.rs](https://docs.rs/atoi). 39 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atoi" 3 | version = "2.0.0" 4 | authors = ["Markus Klein"] 5 | license = "MIT" 6 | repository = "https://github.com/pacman82/atoi-rs" 7 | documentation = "https://docs.rs/atoi/" 8 | edition = "2021" 9 | rust-version = "1.70.0" 10 | 11 | # A short blurb about the package. This is not rendered in any format when 12 | # uploaded to crates.io (aka this is not markdown). 13 | description = "Parse integers directly from `[u8]` slices in safe code" 14 | 15 | # This is a list of up to five keywords that describe this crate. Keywords 16 | # are searchable on crates.io, and you may choose any words that would 17 | # help someone find this crate. 18 | keywords = ["atoi", "conversion", "integer"] 19 | 20 | # This is a list of up to five categories where this crate would fit. 21 | # Categories are a fixed list available at crates.io/category_slugs, and 22 | # they must match exactly. 23 | categories = ["parsing"] 24 | 25 | # This points to a file under the package root (relative to this `Cargo.toml`). 26 | # The contents of this file are stored and indexed in the registry. 27 | # crates.io will render this file and place the result on the crate's page. 28 | readme = "README.md" 29 | 30 | [features] 31 | default = ["std"] 32 | std = ["num-traits/std"] 33 | 34 | [dependencies] 35 | num-traits = { version = "0.2.14", default-features = false } 36 | 37 | [dev-dependencies] 38 | criterion = "0.5.1" 39 | proptest = "1.4.0" 40 | 41 | [[bench]] 42 | name = "benches" 43 | harness = false 44 | -------------------------------------------------------------------------------- /benches/benches.rs: -------------------------------------------------------------------------------- 1 | use atoi::{FromRadix10, FromRadix10Checked, FromRadix16, FromRadix16Checked, FromRadix10Signed}; 2 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 3 | use std::str; 4 | 5 | pub fn i32_four_digit_number(c: &mut Criterion) { 6 | c.bench_function("i32 four digit number", |b| { 7 | b.iter(|| i32::from_radix_10(black_box(b"1996"))) 8 | }); 9 | } 10 | 11 | pub fn i32_four_digit_number_checked(c: &mut Criterion) { 12 | c.bench_function("i32 checked four digit number", |b| { 13 | b.iter(|| i32::from_radix_10_checked(black_box(b"1996"))) 14 | }); 15 | } 16 | 17 | pub fn u32_four_digit_number(c: &mut Criterion) { 18 | c.bench_function("u32 four digit number", |b| { 19 | b.iter(|| u32::from_radix_10(black_box(b"1996"))) 20 | }); 21 | } 22 | 23 | pub fn u32_four_digit_number_checked(c: &mut Criterion) { 24 | c.bench_function("u32 checked four digit number", |b| { 25 | b.iter(|| u32::from_radix_10_checked(black_box(b"1996"))) 26 | }); 27 | } 28 | 29 | pub fn i32_four_digit_hex_number(c: &mut Criterion) { 30 | c.bench_function("i32 four digit hex number", |b| { 31 | b.iter(|| i32::from_radix_16(black_box(b"1996"))) 32 | }); 33 | } 34 | 35 | pub fn i32_four_digit_hex_number_checked(c: &mut Criterion) { 36 | c.bench_function("i32 checked four digit hex number", |b| { 37 | b.iter(|| i32::from_radix_16_checked(black_box(b"1996"))) 38 | }); 39 | } 40 | 41 | pub fn u32_four_digit_hex_number(c: &mut Criterion) { 42 | c.bench_function("u32 four digit hex number", |b| { 43 | b.iter(|| u32::from_radix_16(black_box(b"1996"))) 44 | }); 45 | } 46 | 47 | pub fn u32_four_digit_hex_number_checked(c: &mut Criterion) { 48 | c.bench_function("u32 checked four digit hex number", |b| { 49 | b.iter(|| u32::from_radix_16_checked(black_box(b"1996"))) 50 | }); 51 | } 52 | 53 | pub fn i32_negative_four_digit_number(c: &mut Criterion) { 54 | c.bench_function("negative i32 four digit number", |b| { 55 | b.iter(|| i32::from_radix_10_signed(black_box(b"-1996"))) 56 | }); 57 | } 58 | 59 | pub fn i32_signed_four_digit_number(c: &mut Criterion) { 60 | c.bench_function("signed i32 four digit number", |b| { 61 | b.iter(|| i32::from_radix_10_signed(black_box(b"1996"))) 62 | }); 63 | } 64 | 65 | pub fn i32_positive_four_digit_number(c: &mut Criterion) { 66 | c.bench_function("positive i32 four digit number", |b| { 67 | b.iter(|| i32::from_radix_10_signed(black_box(b"+1996"))) 68 | }); 69 | } 70 | 71 | pub fn i128_signed_four_digit_number(c: &mut Criterion) { 72 | c.bench_function("signed i128 four digit number", |b| { 73 | b.iter(|| i128::from_radix_10_signed(black_box(b"1996"))) 74 | }); 75 | } 76 | 77 | pub fn u32_through_utf8(c: &mut Criterion) { 78 | c.bench_function("u32 via UTF-8", |b| { 79 | b.iter(|| { 80 | let s = str::from_utf8(black_box(b"1996")).unwrap(); 81 | s.parse::().unwrap(); 82 | }) 83 | }); 84 | } 85 | 86 | pub fn i128_through_utf8(c: &mut Criterion) { 87 | c.bench_function("i128 via UTF-8", |b| { 88 | b.iter(|| { 89 | let s = str::from_utf8(black_box(b"1996")).unwrap(); 90 | s.parse::().unwrap(); 91 | }) 92 | }); 93 | } 94 | 95 | criterion_group!( 96 | benches, 97 | i32_four_digit_number, 98 | i32_four_digit_number_checked, 99 | u32_four_digit_number, 100 | u32_four_digit_number_checked, 101 | i32_four_digit_hex_number, 102 | i32_four_digit_hex_number_checked, 103 | u32_four_digit_hex_number, 104 | u32_four_digit_hex_number_checked, 105 | i32_signed_four_digit_number, 106 | i32_negative_four_digit_number, 107 | i32_positive_four_digit_number, 108 | i128_signed_four_digit_number, 109 | u32_through_utf8, 110 | i128_through_utf8, 111 | ); 112 | criterion_main!(benches); 113 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A crate for parsing integers directly from ASCII (`[u8]`) without encoding them into utf8 2 | //! first. The name is inspired by the famous C function. 3 | //! 4 | //! Using `str::from_utf8` and `str::parse` 5 | //! is likely to be more idiomatic. Use this crate if you want to avoid decoding bytes into utf8 6 | //! (e.g. for performance reasons). 7 | //! 8 | //! Note that if you want to know how much of the input has been used, you can use the 9 | //! `FromRadix10` trait, for example: 10 | //! 11 | //! ```rust 12 | //! use atoi::FromRadix10; 13 | //! 14 | //! /// Return the parsed integer and remaining slice if successful. 15 | //! fn atoi_with_rest(text: &[u8]) -> ((&[u8], Option)) { 16 | //! match I::from_radix_10(text) { 17 | //! (_, 0) => (text, None), 18 | //! (n, used) => (&text[used..], Some(n)), 19 | //! } 20 | //! } 21 | //! ``` 22 | #![cfg_attr(not(std), no_std)] 23 | 24 | use num_traits::{ 25 | ops::checked::{CheckedAdd, CheckedMul}, 26 | Bounded, CheckedSub, One, Signed, Zero, 27 | }; 28 | use core::{ 29 | cmp::{max, min}, 30 | ops::{AddAssign, DivAssign, MulAssign, SubAssign}, 31 | }; 32 | 33 | /// Parses an integer from a slice. 34 | /// 35 | /// Contrary to its 'C' counterpart atoi is generic and will require a type argument if the type 36 | /// inference can not determine its result. It will also check for overflow / underflow and allow 37 | /// for Signs. 38 | /// 39 | /// Use [`FromRadix10`] or [`FromRadix10Checked`] directly if you do not want to allow signs. Use 40 | /// [`FromRadix10`] or [`FromRadix10Signed`] if you want to opt out overflow / underflow checking. 41 | /// 42 | /// # Example 43 | /// 44 | /// ``` 45 | /// use atoi::atoi; 46 | /// // Parsing to digits from a slice 47 | /// assert_eq!(Some(42), atoi::(b"42")); 48 | /// // Additional bytes after the number are ignored. If you want to know how many bytes were used 49 | /// // to parse the number use `FromRadix10::from_radix_10`. 50 | /// assert_eq!(Some(42), atoi::(b"42 is the answer to life, the universe and everything")); 51 | /// // `None` is returned if the slice does not start with a digit 52 | /// assert_eq!(None, atoi::(b"Sadly we do not know the question")); 53 | /// // While signed integer types are supported... 54 | /// assert_eq!(Some(42), atoi::(b"42")); 55 | /// // Signs are allowed. 56 | /// assert_eq!(Some(-42), atoi::(b"-42")); 57 | /// // Leading zeros are allowed 58 | /// assert_eq!(Some(42), atoi::(b"0042")); 59 | /// // Overflows will return `None` 60 | /// assert_eq!(None, atoi::(b"256")); 61 | /// ``` 62 | /// 63 | /// # Return 64 | /// 65 | /// Returns a a number if the slice started with a number, otherwise `None` is returned. 66 | pub fn atoi(text: &[u8]) -> Option 67 | where 68 | I: FromRadix10SignedChecked, 69 | { 70 | match I::from_radix_10_signed_checked(text) { 71 | (_, 0) | (None, _) => None, 72 | (Some(n), _) => Some(n), 73 | } 74 | } 75 | 76 | /// Types implementing this trait can be parsed from a positional numeral system with radix 10 77 | pub trait FromRadix10: Sized { 78 | /// Parses an integer from a slice. 79 | /// 80 | /// # Example 81 | /// 82 | /// ``` 83 | /// use atoi::FromRadix10; 84 | /// // Parsing to digits from a slice 85 | /// assert_eq!((42,2), u32::from_radix_10(b"42")); 86 | /// // Additional bytes after the number are ignored 87 | /// assert_eq!((42,2), u32::from_radix_10(b"42 is the answer to life, the universe and everything")); 88 | /// // (0,0) is returned if the slice does not start with a digit 89 | /// assert_eq!((0,0), u32::from_radix_10(b"Sadly we do not know the question")); 90 | /// // While signed integer types are supported... 91 | /// assert_eq!((42,2), i32::from_radix_10(b"42")); 92 | /// // Signs are not allowed (even for signed integer types) 93 | /// assert_eq!((0,0), i32::from_radix_10(b"-42")); 94 | /// // Leading zeros are allowed 95 | /// assert_eq!((42,4), u32::from_radix_10(b"0042")); 96 | /// ``` 97 | /// 98 | /// # Return 99 | /// 100 | /// Returns a tuple with two numbers. The first is the integer parsed or zero, the second is the 101 | /// index of the byte right after the parsed number. If the second element is zero the slice 102 | /// did not start with an ASCII digit. 103 | fn from_radix_10(_: &[u8]) -> (Self, usize); 104 | } 105 | 106 | /// Types implementing this trait can be parsed from a positional numeral system with radix 10. 107 | /// Acts much like `FromRadix10`, but performs additional checks for overflows. 108 | pub trait FromRadix10Checked: FromRadix10 { 109 | /// Parses an integer from a slice. 110 | /// 111 | /// # Example 112 | /// 113 | /// ``` 114 | /// use atoi::FromRadix10Checked; 115 | /// // Parsing to digits from a slice 116 | /// assert_eq!((Some(42),2), u32::from_radix_10_checked(b"42")); 117 | /// // Additional bytes after the number are ignored 118 | /// assert_eq!((Some(42),2), u32::from_radix_10_checked(b"42 is the answer to life, the universe and everything")); 119 | /// // (0,0) is returned if the slice does not start with a digit 120 | /// assert_eq!((Some(0),0), u32::from_radix_10_checked(b"Sadly we do not know the question")); 121 | /// // While signed integer types are supported... 122 | /// assert_eq!((Some(42),2), i32::from_radix_10_checked(b"42")); 123 | /// // Signs are not allowed (even for signed integer types) 124 | /// assert_eq!((Some(0),0), i32::from_radix_10_checked(b"-42")); 125 | /// // Leading zeros are allowed 126 | /// assert_eq!((Some(42),4), u32::from_radix_10_checked(b"0042")); 127 | /// // Overflow is indicated by `None` 128 | /// assert_eq!((None, 3), u8::from_radix_10_checked(b"256")); 129 | /// ``` 130 | /// 131 | /// # Return 132 | /// 133 | /// Returns a tuple with two numbers. The first is the integer parsed or zero if no digit has 134 | /// been found. None, if there were too many, or too high dighits and the parsing overflowed. 135 | /// The second is the index of the byte right after the parsed number. If the second element is 136 | /// zero the slice did not start with an ASCII digit. 137 | fn from_radix_10_checked(_: &[u8]) -> (Option, usize); 138 | } 139 | 140 | /// Types implementing this trait can be parsed from a positional numeral system with radix 16 141 | pub trait FromRadix16: Sized { 142 | /// Parses an integer from a slice. 143 | /// 144 | /// # Example 145 | /// 146 | /// ``` 147 | /// use atoi::FromRadix16; 148 | /// // Parsing to digits from a slice 149 | /// assert_eq!((42,2), u32::from_radix_16(b"2a")); 150 | /// // Additional bytes after the number are ignored 151 | /// assert_eq!((42,2), u32::from_radix_16(b"2a is the answer to life, the universe and everything")); 152 | /// // (0,0) is returned if the slice does not start with a digit 153 | /// assert_eq!((0,0), u32::from_radix_16(b"Sadly we do not know the question")); 154 | /// // While signed integer types are supported... 155 | /// assert_eq!((42,2), i32::from_radix_16(b"2a")); 156 | /// // Signs are not allowed (even for signed integer types) 157 | /// assert_eq!((0,0), i32::from_radix_16(b"-2a")); 158 | /// // Leading zeros are allowed 159 | /// assert_eq!((42,4), u32::from_radix_16(b"002a")); 160 | /// // so are uppercase letters 161 | /// assert_eq!((42,4), u32::from_radix_16(b"002A")); 162 | /// ``` 163 | /// 164 | /// # Return 165 | /// 166 | /// Returns a tuple with two numbers. The first is the integer parsed or zero, the second is the 167 | /// index of the byte right after the parsed number. If the second element is zero the slice 168 | /// did not start with an ASCII digit. 169 | fn from_radix_16(_: &[u8]) -> (Self, usize); 170 | } 171 | 172 | /// Types implementing this trait can be parsed from a positional numeral system with radix 16. 173 | /// Acts much like `FromRadix16`, but performs additional checks for overflows. 174 | pub trait FromRadix16Checked: FromRadix16 { 175 | /// Parses an integer from a slice. 176 | /// 177 | /// # Example 178 | /// 179 | /// ``` 180 | /// use atoi::FromRadix16Checked; 181 | /// // Parsing to digits from a slice 182 | /// assert_eq!((Some(42),2), u32::from_radix_16_checked(b"2a")); 183 | /// // Additional bytes after the number are ignored 184 | /// assert_eq!((Some(42),2), u32::from_radix_16_checked(b"2a is the answer to life, the \ 185 | /// universe and everything")); 186 | /// // (0,0) is returned if the slice does not start with a digit 187 | /// assert_eq!((Some(0),0), u32::from_radix_16_checked(b"Sadly we do not know the question")); 188 | /// // While signed integer types are supported... 189 | /// assert_eq!((Some(42),2), i32::from_radix_16_checked(b"2a")); 190 | /// // Signs are not allowed (even for signed integer types) 191 | /// assert_eq!((Some(0),0), i32::from_radix_16_checked(b"-2a")); 192 | /// // Leading zeros are allowed 193 | /// assert_eq!((Some(42),4), u32::from_radix_16_checked(b"002a")); 194 | /// // So are uppercase letters 195 | /// assert_eq!((Some(42),2), u32::from_radix_16_checked(b"2A")) 196 | /// ``` 197 | /// 198 | /// # Return 199 | /// 200 | /// Returns a tuple with two numbers. The first is the integer parsed or zero if no digit has 201 | /// been found. None, if there were too many, or too high dighits and the parsing overflowed. 202 | /// The second is the index of the byte right after the parsed number. If the second element is 203 | /// zero the slice did not start with an ASCII digit. 204 | fn from_radix_16_checked(_: &[u8]) -> (Option, usize); 205 | } 206 | 207 | /// Types implementing this trait can be parsed from a positional numeral system with radix 10. This 208 | /// trait allows for an additional sign character (`+` or `-`) in front of the actual number in 209 | /// order, to allow for parsing negative values. 210 | pub trait FromRadix10Signed: Sized { 211 | /// Parses an integer from a slice. 212 | /// 213 | /// # Example 214 | /// 215 | /// ``` 216 | /// use atoi::FromRadix10Signed; 217 | /// // Parsing to digits from a slice 218 | /// assert_eq!((42,2), i32::from_radix_10_signed(b"42")); 219 | /// // Additional bytes after the number are ignored 220 | /// assert_eq!((42,2), i32::from_radix_10_signed(b"42 is the answer to life, the universe and everything")); 221 | /// // (0,0) is returned if the slice does not start with a digit 222 | /// assert_eq!((0,0), i32::from_radix_10_signed(b"Sadly we do not know the question")); 223 | /// // Signs are allowed 224 | /// assert_eq!((-42,3), i32::from_radix_10_signed(b"-42")); 225 | /// // Signs are allowed 226 | /// assert_eq!((42,3), i32::from_radix_10_signed(b"+42")); 227 | /// // Even on unsigned types. 228 | /// assert_eq!((0,2), u32::from_radix_10_signed(b"-0")); 229 | /// // Leading zeros are allowed 230 | /// assert_eq!((42,4), i32::from_radix_10_signed(b"0042")); 231 | /// ``` 232 | /// 233 | /// # Return 234 | /// 235 | /// Returns a tuple with two numbers. The first is the integer parsed or zero, the second is the 236 | /// index of the byte right after the parsed number. If the second element is zero the slice 237 | /// did not start with an ASCII digit. 238 | fn from_radix_10_signed(_: &[u8]) -> (Self, usize); 239 | } 240 | 241 | /// Types implementing this trait can be parsed from a positional numeral system with radix 10. 242 | /// Acts much like `FromRadix10Signed`, but performs additional checks for overflows. 243 | pub trait FromRadix10SignedChecked: FromRadix10Signed { 244 | /// Parses an integer from a slice. 245 | /// 246 | /// # Example 247 | /// 248 | /// ``` 249 | /// use atoi::FromRadix10SignedChecked; 250 | /// // Parsing to digits from a slice 251 | /// assert_eq!((Some(42),2), u32::from_radix_10_signed_checked(b"42")); 252 | /// // Additional bytes after the number are ignored 253 | /// assert_eq!((Some(42),2), u32::from_radix_10_signed_checked(b"42 is the answer to life, the universe and everything")); 254 | /// // (0,0) is returned if the slice does not start with a digit 255 | /// assert_eq!((Some(0),0), u32::from_radix_10_signed_checked(b"Sadly we do not know the question")); 256 | /// // While signed integer types are supported... 257 | /// assert_eq!((Some(42),2), i32::from_radix_10_signed_checked(b"42")); 258 | /// // Signs are allowed 259 | /// assert_eq!((Some(-42),3), i32::from_radix_10_signed_checked(b"-42")); 260 | /// // -0 is ok, even for an unsigned type 261 | /// assert_eq!((Some(0),2), u32::from_radix_10_signed_checked(b"-0")); 262 | /// // -1 is an Underflow 263 | /// assert_eq!((None,2), u32::from_radix_10_signed_checked(b"-1")); 264 | /// // Negative values for unsigned types are handled as `None`. 265 | /// assert_eq!((None,3), u32::from_radix_10_signed_checked(b"-42")); 266 | /// // Leading zeros are allowed 267 | /// assert_eq!((Some(42),4), u32::from_radix_10_signed_checked(b"0042")); 268 | /// // Overflow is indicated by `None` 269 | /// assert_eq!((None, 3), u8::from_radix_10_signed_checked(b"256")); 270 | /// assert_eq!((None, 4), i8::from_radix_10_signed_checked(b"+128")); 271 | /// assert_eq!((None, 4), i8::from_radix_10_signed_checked(b"-129")); 272 | /// ``` 273 | /// 274 | /// # Return 275 | /// 276 | /// Returns a tuple with two numbers. The first is the integer parsed or zero if no digit has 277 | /// been found. None, if there were too many, or too high dighits and the parsing overflowed. 278 | /// The second is the index of the byte right after the parsed number. If the second element is 279 | /// zero the slice did not start with an ASCII digit. 280 | fn from_radix_10_signed_checked(_: &[u8]) -> (Option, usize); 281 | } 282 | 283 | /// A bounded integer, whose representation can overflow and therefore can only store a maximum 284 | /// number of digits 285 | pub trait MaxNumDigits { 286 | /// Given a representation with a radix character I, what is the maximum number of digits we can 287 | /// parse without the integer overflowing for sure? 288 | fn max_num_digits(radix: Self) -> usize; 289 | 290 | /// Returns the maximum number of digits a negative representation of `I` can have depending on 291 | /// `radix`. 292 | fn max_num_digits_negative(radix: Self) -> usize; 293 | } 294 | 295 | impl MaxNumDigits for I 296 | where 297 | I: Bounded + Zero + DivAssign + Ord + Copy, 298 | { 299 | /// Returns the maximum number of digits a nonnegative representation of `I` can have depending 300 | /// on `radix`. 301 | fn max_num_digits(radix: I) -> usize { 302 | let mut max = I::max_value(); 303 | let mut d = 0; 304 | while max > I::zero() { 305 | d += 1; 306 | max /= radix; 307 | } 308 | d 309 | } 310 | 311 | /// Returns the maximum number of digits a negative representation of `I` can have depending 312 | /// on `radix`. 313 | fn max_num_digits_negative(radix: I) -> usize { 314 | let mut min = I::min_value(); 315 | let mut d = 0; 316 | while min < I::zero() { 317 | d += 1; 318 | min /= radix; 319 | } 320 | d 321 | } 322 | } 323 | 324 | /// Converts an ascii character to digit 325 | /// 326 | /// # Example 327 | /// 328 | /// ``` 329 | /// use atoi::ascii_to_digit; 330 | /// assert_eq!(Some(5), ascii_to_digit(b'5')); 331 | /// assert_eq!(None, ascii_to_digit::(b'x')); 332 | /// ``` 333 | pub fn ascii_to_digit(character: u8) -> Option 334 | where 335 | I: Zero + One, 336 | { 337 | match character { 338 | b'0' => Some(nth(0)), 339 | b'1' => Some(nth(1)), 340 | b'2' => Some(nth(2)), 341 | b'3' => Some(nth(3)), 342 | b'4' => Some(nth(4)), 343 | b'5' => Some(nth(5)), 344 | b'6' => Some(nth(6)), 345 | b'7' => Some(nth(7)), 346 | b'8' => Some(nth(8)), 347 | b'9' => Some(nth(9)), 348 | _ => None, 349 | } 350 | } 351 | 352 | impl FromRadix10 for I 353 | where 354 | I: Zero + One + AddAssign + MulAssign, 355 | { 356 | fn from_radix_10(text: &[u8]) -> (Self, usize) { 357 | let mut index = 0; 358 | let mut number = I::zero(); 359 | while index != text.len() { 360 | if let Some(digit) = ascii_to_digit(text[index]) { 361 | number *= nth(10); 362 | number += digit; 363 | index += 1; 364 | } else { 365 | break; 366 | } 367 | } 368 | (number, index) 369 | } 370 | } 371 | 372 | impl FromRadix10Signed for I 373 | where 374 | I: Zero + One + AddAssign + SubAssign + MulAssign, 375 | { 376 | fn from_radix_10_signed(text: &[u8]) -> (Self, usize) { 377 | let mut index; 378 | let mut number = I::zero(); 379 | 380 | let (sign, offset) = text 381 | .first() 382 | .and_then(|&byte| Sign::try_from(byte)) 383 | .map(|sign| (sign, 1)) 384 | .unwrap_or((Sign::Plus, 0)); 385 | 386 | index = offset; 387 | 388 | // Having two dedicated loops for both the negative and the nonnegative case is rather 389 | // verbose, yet performed up to 40% better then a more terse single loop with 390 | // `number += digit * signum`. 391 | 392 | match sign { 393 | Sign::Plus => { 394 | while index != text.len() { 395 | if let Some(digit) = ascii_to_digit::(text[index]) { 396 | number *= nth(10); 397 | number += digit; 398 | index += 1; 399 | } else { 400 | break; 401 | } 402 | } 403 | } 404 | Sign::Minus => { 405 | while index != text.len() { 406 | if let Some(digit) = ascii_to_digit::(text[index]) { 407 | number *= nth(10); 408 | number -= digit; 409 | index += 1; 410 | } else { 411 | break; 412 | } 413 | } 414 | } 415 | } 416 | 417 | (number, index) 418 | } 419 | } 420 | 421 | impl FromRadix10SignedChecked for I 422 | where 423 | I: Zero 424 | + One 425 | + AddAssign 426 | + MulAssign 427 | + SubAssign 428 | + CheckedAdd 429 | + CheckedSub 430 | + CheckedMul 431 | + MaxNumDigits, 432 | { 433 | fn from_radix_10_signed_checked(text: &[u8]) -> (Option, usize) { 434 | let mut index; 435 | let mut number = I::zero(); 436 | 437 | let (sign, offset) = text 438 | .first() 439 | .and_then(|&byte| Sign::try_from(byte)) 440 | .map(|sign| (sign, 1)) 441 | .unwrap_or((Sign::Plus, 0)); 442 | 443 | index = offset; 444 | 445 | // Having two dedicated loops for both the negative and the nonnegative case is rather 446 | // verbose, yet performed up to 40% better then a more terse single loop with 447 | // `number += digit * signum`. 448 | 449 | match sign { 450 | Sign::Plus => { 451 | let max_safe_digits = max(1, I::max_num_digits(nth(10))) - 1; 452 | let max_safe_index = min(text.len(), max_safe_digits + offset); 453 | while index != max_safe_index { 454 | if let Some(digit) = ascii_to_digit::(text[index]) { 455 | number *= nth(10); 456 | number += digit; 457 | index += 1; 458 | } else { 459 | break; 460 | } 461 | } 462 | // We parsed the digits, which do not need checking now lets see the next one: 463 | let mut number = Some(number); 464 | while index != text.len() { 465 | if let Some(digit) = ascii_to_digit(text[index]) { 466 | number = number.and_then(|n| n.checked_mul(&nth(10))); 467 | number = number.and_then(|n| n.checked_add(&digit)); 468 | index += 1; 469 | } else { 470 | break; 471 | } 472 | } 473 | (number, index) 474 | } 475 | Sign::Minus => { 476 | let max_safe_digits = max(1, I::max_num_digits_negative(nth(10))) - 1; 477 | let max_safe_index = min(text.len(), max_safe_digits + offset); 478 | while index != max_safe_index { 479 | if let Some(digit) = ascii_to_digit::(text[index]) { 480 | number *= nth(10); 481 | number -= digit; 482 | index += 1; 483 | } else { 484 | break; 485 | } 486 | } 487 | // We parsed the digits, which do not need checking now lets see the next one: 488 | let mut number = Some(number); 489 | while index != text.len() { 490 | if let Some(digit) = ascii_to_digit(text[index]) { 491 | number = number.and_then(|n| n.checked_mul(&nth(10))); 492 | number = number.and_then(|n| n.checked_sub(&digit)); 493 | index += 1; 494 | } else { 495 | break; 496 | } 497 | } 498 | (number, index) 499 | } 500 | } 501 | } 502 | } 503 | 504 | impl FromRadix10Checked for I 505 | where 506 | I: Zero + One + FromRadix10 + CheckedMul + CheckedAdd + MaxNumDigits, 507 | { 508 | fn from_radix_10_checked(text: &[u8]) -> (Option, usize) { 509 | let max_safe_digits = max(1, I::max_num_digits_negative(nth(10))) - 1; 510 | let (number, mut index) = I::from_radix_10(&text[..min(text.len(), max_safe_digits)]); 511 | let mut number = Some(number); 512 | // We parsed the digits, which do not need checking now lets see the next one: 513 | while index != text.len() { 514 | if let Some(digit) = ascii_to_digit(text[index]) { 515 | number = number.and_then(|n| n.checked_mul(&nth(10))); 516 | number = number.and_then(|n| n.checked_add(&digit)); 517 | index += 1; 518 | } else { 519 | break; 520 | } 521 | } 522 | (number, index) 523 | } 524 | } 525 | 526 | /// Converts an ascii character to digit 527 | fn ascii_to_hexdigit(character: u8) -> Option 528 | where 529 | I: Zero + One, 530 | { 531 | match character { 532 | b'0' => Some(nth(0)), 533 | b'1' => Some(nth(1)), 534 | b'2' => Some(nth(2)), 535 | b'3' => Some(nth(3)), 536 | b'4' => Some(nth(4)), 537 | b'5' => Some(nth(5)), 538 | b'6' => Some(nth(6)), 539 | b'7' => Some(nth(7)), 540 | b'8' => Some(nth(8)), 541 | b'9' => Some(nth(9)), 542 | b'a' | b'A' => Some(nth(10)), 543 | b'b' | b'B' => Some(nth(11)), 544 | b'c' | b'C' => Some(nth(12)), 545 | b'd' | b'D' => Some(nth(13)), 546 | b'e' | b'E' => Some(nth(14)), 547 | b'f' | b'F' => Some(nth(15)), 548 | _ => None, 549 | } 550 | } 551 | 552 | impl FromRadix16 for I 553 | where 554 | I: Zero + One + AddAssign + MulAssign, 555 | { 556 | fn from_radix_16(text: &[u8]) -> (Self, usize) { 557 | let mut index = 0; 558 | let mut number = I::zero(); 559 | while index != text.len() { 560 | if let Some(digit) = ascii_to_hexdigit(text[index]) { 561 | number *= nth(16); 562 | number += digit; 563 | index += 1; 564 | } else { 565 | break; 566 | } 567 | } 568 | (number, index) 569 | } 570 | } 571 | 572 | impl FromRadix16Checked for I 573 | where 574 | I: Zero + One + FromRadix16 + CheckedMul + CheckedAdd + MaxNumDigits, 575 | { 576 | fn from_radix_16_checked(text: &[u8]) -> (Option, usize) { 577 | let max_safe_digits = max(1, I::max_num_digits_negative(nth(10))) - 1; 578 | let (number, mut index) = I::from_radix_16(&text[..min(text.len(), max_safe_digits)]); 579 | let mut number = Some(number); 580 | // We parsed the digits, which do not need checking now lets see the next one: 581 | while index != text.len() { 582 | if let Some(digit) = ascii_to_hexdigit(text[index]) { 583 | number = number.and_then(|n| n.checked_mul(&nth(16))); 584 | number = number.and_then(|n| n.checked_add(&digit)); 585 | index += 1; 586 | } else { 587 | break; 588 | } 589 | } 590 | (number, index) 591 | } 592 | } 593 | 594 | /// Representation of a numerical sign 595 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 596 | pub enum Sign { 597 | Plus, 598 | Minus, 599 | } 600 | 601 | impl Sign { 602 | /// Trys to convert an ascii character into a `Sign` 603 | /// 604 | /// # Example 605 | /// 606 | /// ``` 607 | /// use atoi::Sign; 608 | /// assert_eq!(Some(Sign::Plus), Sign::try_from(b'+')); 609 | /// assert_eq!(Some(Sign::Minus), Sign::try_from(b'-')); 610 | /// assert_eq!(None, Sign::try_from(b'1')); 611 | /// ``` 612 | pub fn try_from(byte: u8) -> Option { 613 | match byte { 614 | b'+' => Some(Sign::Plus), 615 | b'-' => Some(Sign::Minus), 616 | _ => None, 617 | } 618 | } 619 | 620 | /// Returns either `+1` or `-1` 621 | pub fn signum(self) -> I 622 | where 623 | I: Signed, 624 | { 625 | match self { 626 | Sign::Plus => I::one(), 627 | Sign::Minus => -I::one(), 628 | } 629 | } 630 | } 631 | 632 | // At least for primitive types this function does not incur runtime costs, since it is only called 633 | // with constants 634 | fn nth(n: u8) -> I 635 | where 636 | I: Zero + One, 637 | { 638 | let mut i = I::zero(); 639 | for _ in 0..n { 640 | i = i + I::one(); 641 | } 642 | i 643 | } 644 | 645 | #[cfg(test)] 646 | mod test { 647 | 648 | use super::*; 649 | 650 | #[test] 651 | fn max_digits() { 652 | assert_eq!(10, i32::max_num_digits(10)); 653 | assert_eq!(10, u32::max_num_digits(10)); 654 | assert_eq!(19, i64::max_num_digits(10)); 655 | assert_eq!(20, u64::max_num_digits(10)); 656 | assert_eq!(3, u8::max_num_digits(10)); 657 | assert_eq!(3, i8::max_num_digits(10)); 658 | } 659 | 660 | #[test] 661 | fn max_digits_negative() { 662 | assert_eq!(10, i32::max_num_digits_negative(10)); 663 | assert_eq!(0, u32::max_num_digits_negative(10)); 664 | assert_eq!(19, i64::max_num_digits_negative(10)); 665 | assert_eq!(0, u64::max_num_digits_negative(10)); 666 | assert_eq!(0, u8::max_num_digits_negative(10)); 667 | assert_eq!(3, i8::max_num_digits_negative(10)); 668 | } 669 | 670 | #[test] 671 | fn checked_parsing() { 672 | assert_eq!((Some(255), 3), u8::from_radix_10_checked(b"255")); 673 | assert_eq!((None, 3), u8::from_radix_10_checked(b"256")); 674 | assert_eq!((None, 4), u8::from_radix_10_checked(b"1000")); 675 | assert_eq!((Some(25), 2), u8::from_radix_10_checked(b"25")); 676 | assert_eq!((Some(25), 2), u8::from_radix_10_checked(b"25Blub")); 677 | } 678 | 679 | #[test] 680 | fn checked_parsing_radix_16() { 681 | assert_eq!((Some(255), 2), u8::from_radix_16_checked(b"FF")); 682 | assert_eq!((None, 3), u8::from_radix_16_checked(b"100")); 683 | assert_eq!((None, 4), u8::from_radix_16_checked(b"1000")); 684 | assert_eq!((Some(25), 2), u8::from_radix_16_checked(b"19")); 685 | assert_eq!((Some(25), 2), u8::from_radix_16_checked(b"19!Blub")); 686 | } 687 | } 688 | --------------------------------------------------------------------------------