[go: up one dir, main page]

btoi/
lib.rs

1//! Parse integers from ASCII byte slices.
2//!
3//! Provides functions similar to [`from_str_radix`], but is faster when
4//! parsing directly from byte slices instead of strings.
5//!
6//! Supports `#![no_std]`.
7//!
8//! # Examples
9//!
10//! ```
11//! use btoi::btoi;
12//!
13//! assert_eq!(Ok(42), btoi(b"42"));
14//! assert_eq!(Ok(-1000), btoi(b"-1000"));
15//! ```
16//!
17//! All functions are [generic](https://docs.rs/num-traits/) over integer
18//! types. Use the turbofish syntax if a type cannot be inferred from the
19//! context.
20//!
21//! ```
22//! # use btoi::btoi;
23//! // overflows the selected target type
24//! assert!(btoi::<u32>(b"9876543210").is_err());
25//!
26//! // negatively overflows the selected target type (an unsigned integer)
27//! assert!(btoi::<u32>(b"-1").is_err());
28//! ```
29//!
30//! It is possible to use saturating arithmetic for overflow handling.
31//!
32//! ```
33//! use btoi::btoi_saturating;
34//!
35//! assert_eq!(Ok(0xffff_ffff), btoi_saturating::<u32>(b"9876543210"));
36//! assert_eq!(Ok(0), btoi_saturating::<u32>(b"-1"));
37//! ```
38//!
39//! # Errors
40//!
41//! All functions return [`ParseIntegerError`] for these error conditions:
42//!
43//! * The byte slice does not contain any digits.
44//! * Not all characters are `0-9`, `a-z`, `A-Z`. Leading or trailing
45//!   whitespace is not allowed. The `btoi*` functions accept an optional
46//!   leading `+` or `-` sign. The `btou*` functions respectively do not
47//!   allow signs.
48//! * Not all digits are valid in the given radix.
49//! * The number overflows (positively or negatively) the target type, but
50//!   saturating arithmetic is not used.
51//!
52//! # Panics
53//!
54//! Just like `from_str_radix` functions will panic if the given radix is
55//! not in the range `2..=36` (or in the pathological case that there is
56//! no representation of the radix in the target integer type).
57//!
58//! [`ParseIntegerError`]: struct.ParseIntegerError.html
59//! [`from_str_radix`]: https://doc.rust-lang.org/std/primitive.u32.html#method.from_str_radix
60
61#![doc(html_root_url = "https://docs.rs/btoi/0.5.0")]
62#![forbid(unsafe_code)]
63#![deny(missing_docs)]
64#![deny(missing_debug_implementations)]
65#![no_std]
66
67extern crate num_traits;
68
69#[cfg(feature = "std")]
70extern crate std;
71
72#[cfg(test)]
73#[macro_use]
74extern crate quickcheck;
75
76use core::fmt;
77
78use num_traits::{Bounded, CheckedAdd, CheckedMul, CheckedSub, FromPrimitive, Saturating, Zero};
79
80/// Error that can occur when trying to parse an integer.
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct ParseIntegerError {
83    kind: ParseIntegerErrorKind,
84}
85
86impl ParseIntegerError {
87    /// The specific kind of error that occured.
88    pub fn kind(&self) -> ParseIntegerErrorKind {
89        self.kind
90    }
91}
92
93/// Kinds of errors that can occur when trying to parse an integer.
94#[derive(Debug, Copy, Clone, PartialEq, Eq)]
95pub enum ParseIntegerErrorKind {
96    /// Cannot parse integer without digits.
97    Empty,
98    /// Invalid digit found.
99    InvalidDigit,
100    /// Integer too large to fit in target type.
101    PosOverflow,
102    /// Integer too small to fit in target type.
103    NegOverflow,
104}
105
106impl fmt::Display for ParseIntegerError {
107    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108        f.write_str(match self.kind {
109            ParseIntegerErrorKind::Empty => "cannot parse integer without digits",
110            ParseIntegerErrorKind::InvalidDigit => "invalid digit found",
111            ParseIntegerErrorKind::PosOverflow => "integer too large to fit in target type",
112            ParseIntegerErrorKind::NegOverflow => "integer too small to fit in target type",
113        })
114    }
115}
116
117#[cfg(feature = "std")]
118impl std::error::Error for ParseIntegerError {}
119
120/// Converts a byte slice in a given base to an integer. Signs are not allowed.
121///
122/// # Errors
123///
124/// Returns [`ParseIntegerError`] for any of the following conditions:
125///
126/// * `bytes` is empty
127/// * not all characters of `bytes` are `0-9`, `a-z` or `A-Z`
128/// * not all characters refer to digits in the given `radix`
129/// * the number overflows `I`
130///
131/// # Panics
132///
133/// Panics if `radix` is not in the range `2..=36` (or in the pathological
134/// case that there is no representation of `radix` in `I`).
135///
136/// # Examples
137///
138/// ```
139/// # use btoi::btou_radix;
140/// assert_eq!(Ok(255), btou_radix(b"ff", 16));
141/// assert_eq!(Ok(42), btou_radix(b"101010", 2));
142/// ```
143///
144/// [`ParseIntegerError`]: struct.ParseIntegerError.html
145#[track_caller]
146pub fn btou_radix<I>(bytes: &[u8], radix: u32) -> Result<I, ParseIntegerError>
147where
148    I: FromPrimitive + Zero + CheckedAdd + CheckedMul,
149{
150    assert!(
151        (2..=36).contains(&radix),
152        "radix must lie in the range 2..=36, found {}",
153        radix
154    );
155
156    let base = I::from_u32(radix).expect("radix can be represented as integer");
157
158    if bytes.is_empty() {
159        return Err(ParseIntegerError {
160            kind: ParseIntegerErrorKind::Empty,
161        });
162    }
163
164    let mut result = I::zero();
165
166    for &digit in bytes {
167        let mul = result.checked_mul(&base);
168        let x = match char::from(digit).to_digit(radix).and_then(I::from_u32) {
169            Some(x) => x,
170            None => {
171                return Err(ParseIntegerError {
172                    kind: ParseIntegerErrorKind::InvalidDigit,
173                })
174            }
175        };
176        result = match mul {
177            Some(result) => result,
178            None => {
179                return Err(ParseIntegerError {
180                    kind: ParseIntegerErrorKind::PosOverflow,
181                })
182            }
183        };
184        result = match result.checked_add(&x) {
185            Some(result) => result,
186            None => {
187                return Err(ParseIntegerError {
188                    kind: ParseIntegerErrorKind::PosOverflow,
189                })
190            }
191        };
192    }
193
194    Ok(result)
195}
196
197/// Converts a byte slice to an integer. Signs are not allowed.
198///
199/// # Errors
200///
201/// Returns [`ParseIntegerError`] for any of the following conditions:
202///
203/// * `bytes` is empty
204/// * not all characters of `bytes` are `0-9`
205/// * the number overflows `I`
206///
207/// # Panics
208///
209/// Panics in the pathological case that there is no representation of `10`
210/// in `I`.
211///
212/// # Examples
213///
214/// ```
215/// # use btoi::btou;
216/// assert_eq!(Ok(12345), btou(b"12345"));
217/// assert!(btou::<u8>(b"+1").is_err()); // only btoi allows signs
218/// assert!(btou::<u8>(b"256").is_err()); // overflow
219/// ```
220///
221/// [`ParseIntegerError`]: struct.ParseIntegerError.html
222#[track_caller]
223pub fn btou<I>(bytes: &[u8]) -> Result<I, ParseIntegerError>
224where
225    I: FromPrimitive + Zero + CheckedAdd + CheckedMul,
226{
227    btou_radix(bytes, 10)
228}
229
230/// Converts a byte slice in a given base to an integer.
231///
232/// Like [`btou_radix`], but numbers may optionally start with a sign
233/// (`-` or `+`).
234///
235/// # Errors
236///
237/// Returns [`ParseIntegerError`] for any of the following conditions:
238///
239/// * `bytes` has no digits
240/// * not all characters of `bytes` are `0-9`, `a-z`, `A-Z`, exluding an
241///   optional leading sign
242/// * not all characters refer to digits in the given `radix`, exluding an
243///   optional leading sign
244/// * the number overflows `I` (positively or negatively)
245///
246/// # Panics
247///
248/// Panics if `radix` is not in the range `2..=36` (or in the pathological
249/// case that there is no representation of `radix` in `I`).
250///
251/// # Examples
252///
253/// ```
254/// # use btoi::btoi_radix;
255/// assert_eq!(Ok(10), btoi_radix(b"a", 16));
256/// assert_eq!(Ok(10), btoi_radix(b"+a", 16));
257/// assert_eq!(Ok(-42), btoi_radix(b"-101010", 2));
258/// ```
259///
260/// [`btou_radix`]: fn.btou_radix.html
261/// [`ParseIntegerError`]: struct.ParseIntegerError.html
262#[track_caller]
263pub fn btoi_radix<I>(bytes: &[u8], radix: u32) -> Result<I, ParseIntegerError>
264where
265    I: FromPrimitive + Zero + CheckedAdd + CheckedSub + CheckedMul,
266{
267    assert!(
268        (2..=36).contains(&radix),
269        "radix must lie in the range 2..=36, found {}",
270        radix
271    );
272
273    let base = I::from_u32(radix).expect("radix can be represented as integer");
274
275    if bytes.is_empty() {
276        return Err(ParseIntegerError {
277            kind: ParseIntegerErrorKind::Empty,
278        });
279    }
280
281    let digits = match bytes[0] {
282        b'+' => return btou_radix(&bytes[1..], radix),
283        b'-' => &bytes[1..],
284        _ => return btou_radix(bytes, radix),
285    };
286
287    if digits.is_empty() {
288        return Err(ParseIntegerError {
289            kind: ParseIntegerErrorKind::Empty,
290        });
291    }
292
293    let mut result = I::zero();
294
295    for &digit in digits {
296        let mul = result.checked_mul(&base);
297        let x = match char::from(digit).to_digit(radix).and_then(I::from_u32) {
298            Some(x) => x,
299            None => {
300                return Err(ParseIntegerError {
301                    kind: ParseIntegerErrorKind::InvalidDigit,
302                })
303            }
304        };
305        result = match mul {
306            Some(result) => result,
307            None => {
308                return Err(ParseIntegerError {
309                    kind: ParseIntegerErrorKind::NegOverflow,
310                })
311            }
312        };
313        result = match result.checked_sub(&x) {
314            Some(result) => result,
315            None => {
316                return Err(ParseIntegerError {
317                    kind: ParseIntegerErrorKind::NegOverflow,
318                })
319            }
320        };
321    }
322
323    Ok(result)
324}
325
326/// Converts a byte slice to an integer.
327///
328/// Like [`btou`], but numbers may optionally start with a sign (`-` or `+`).
329///
330/// # Errors
331///
332/// Returns [`ParseIntegerError`] for any of the following conditions:
333///
334/// * `bytes` has no digits
335/// * not all characters of `bytes` are `0-9`, excluding an optional leading
336///   sign
337/// * the number overflows `I` (positively or negatively)
338///
339/// # Panics
340///
341/// Panics in the pathological case that there is no representation of `10`
342/// in `I`.
343///
344/// # Examples
345///
346/// ```
347/// # use btoi::btoi;
348/// assert_eq!(Ok(123), btoi(b"123"));
349/// assert_eq!(Ok(123), btoi(b"+123"));
350/// assert_eq!(Ok(-123), btoi(b"-123"));
351///
352/// assert!(btoi::<i16>(b"123456789").is_err()); // positive overflow
353/// assert!(btoi::<u32>(b"-1").is_err()); // negative overflow
354///
355/// assert!(btoi::<i32>(b" 42").is_err()); // leading space
356/// ```
357///
358/// [`btou`]: fn.btou.html
359/// [`ParseIntegerError`]: struct.ParseIntegerError.html
360#[track_caller]
361pub fn btoi<I>(bytes: &[u8]) -> Result<I, ParseIntegerError>
362where
363    I: FromPrimitive + Zero + CheckedAdd + CheckedSub + CheckedMul,
364{
365    btoi_radix(bytes, 10)
366}
367
368/// Converts a byte slice in a given base to the closest possible integer.
369/// Signs are not allowed.
370///
371/// # Errors
372///
373/// Returns [`ParseIntegerError`] for any of the following conditions:
374///
375/// * `bytes` is empty
376/// * not all characters of `bytes` are `0-9`, `a-z`, `A-Z`
377/// * not all characters refer to digits in the given `radix`
378///
379/// # Panics
380///
381/// Panics if `radix` is not in the range `2..=36` (or in the pathological
382/// case that there is no representation of `radix` in `I`).
383///
384/// # Examples
385///
386/// ```
387/// # use btoi::btou_saturating_radix;
388/// assert_eq!(Ok(255), btou_saturating_radix::<u8>(b"00ff", 16));
389/// assert_eq!(Ok(255), btou_saturating_radix::<u8>(b"0100", 16)); // u8 saturated
390/// assert_eq!(Ok(255), btou_saturating_radix::<u8>(b"0101", 16)); // u8 saturated
391/// ```
392///
393/// [`ParseIntegerError`]: struct.ParseIntegerError.html
394#[track_caller]
395pub fn btou_saturating_radix<I>(bytes: &[u8], radix: u32) -> Result<I, ParseIntegerError>
396where
397    I: FromPrimitive + Zero + CheckedMul + Saturating + Bounded,
398{
399    assert!(
400        (2..=36).contains(&radix),
401        "radix must lie in the range 2..=36, found {}",
402        radix
403    );
404
405    let base = I::from_u32(radix).expect("radix can be represented as integer");
406
407    if bytes.is_empty() {
408        return Err(ParseIntegerError {
409            kind: ParseIntegerErrorKind::Empty,
410        });
411    }
412
413    let mut result = I::zero();
414
415    for &digit in bytes {
416        let mul = result.checked_mul(&base);
417        let x = match char::from(digit).to_digit(radix).and_then(I::from_u32) {
418            Some(x) => x,
419            None => {
420                return Err(ParseIntegerError {
421                    kind: ParseIntegerErrorKind::InvalidDigit,
422                })
423            }
424        };
425        result = match mul {
426            Some(result) => result,
427            None => return Ok(I::max_value()),
428        };
429        result = result.saturating_add(x);
430    }
431
432    Ok(result)
433}
434
435/// Converts a byte slice to the closest possible integer.
436/// Signs are not allowed.
437///
438/// # Errors
439///
440/// Returns [`ParseIntegerError`] for any of the following conditions:
441///
442/// * `bytes` is empty
443/// * not all characters of `bytes` are `0-9`
444///
445/// # Panics
446///
447/// Panics in the pathological case that there is no representation of `10`
448/// in `I`.
449///
450/// # Examples
451///
452/// ```
453/// # use btoi::btou_saturating;
454/// assert_eq!(Ok(65535), btou_saturating::<u16>(b"65535"));
455/// assert_eq!(Ok(65535), btou_saturating::<u16>(b"65536")); // u16 saturated
456/// assert_eq!(Ok(65535), btou_saturating::<u16>(b"65537")); // u16 saturated
457/// ```
458///
459/// [`ParseIntegerError`]: struct.ParseIntegerError.html
460#[track_caller]
461pub fn btou_saturating<I>(bytes: &[u8]) -> Result<I, ParseIntegerError>
462where
463    I: FromPrimitive + Zero + CheckedMul + Saturating + Bounded,
464{
465    btou_saturating_radix(bytes, 10)
466}
467
468/// Converts a byte slice in a given base to the closest possible integer.
469///
470/// Like [`btou_saturating_radix`], but numbers may optionally start with a
471/// sign (`-` or `+`).
472///
473/// # Errors
474///
475/// Returns [`ParseIntegerError`] for any of the following conditions:
476///
477/// * `bytes` has no digits
478/// * not all characters of `bytes` are `0-9`, `a-z`, `A-Z`, excluding an
479///   optional leading sign
480/// * not all characters refer to digits in the given `radix`, excluding an
481///   optional leading sign
482///
483/// # Panics
484///
485/// Panics if `radix` is not in the range `2..=36` (or in the pathological
486/// case that there is no representation of `radix` in `I`).
487///
488/// # Examples
489///
490/// ```
491/// # use btoi::btoi_saturating_radix;
492/// assert_eq!(Ok(127), btoi_saturating_radix::<i8>(b"7f", 16));
493/// assert_eq!(Ok(127), btoi_saturating_radix::<i8>(b"ff", 16)); // no positive overflow
494/// assert_eq!(Ok(-128), btoi_saturating_radix::<i8>(b"-ff", 16)); // no negative overflow
495/// ```
496///
497/// [`btou_saturating_radix`]: fn.btou_saturating_radix.html
498/// [`ParseIntegerError`]: struct.ParseIntegerError.html
499#[track_caller]
500pub fn btoi_saturating_radix<I>(bytes: &[u8], radix: u32) -> Result<I, ParseIntegerError>
501where
502    I: FromPrimitive + Zero + CheckedMul + Saturating + Bounded,
503{
504    assert!(
505        (2..=36).contains(&radix),
506        "radix must lie in the range 2..=36, found {}",
507        radix
508    );
509
510    let base = I::from_u32(radix).expect("radix can be represented as integer");
511
512    if bytes.is_empty() {
513        return Err(ParseIntegerError {
514            kind: ParseIntegerErrorKind::Empty,
515        });
516    }
517
518    let digits = match bytes[0] {
519        b'+' => return btou_saturating_radix(&bytes[1..], radix),
520        b'-' => &bytes[1..],
521        _ => return btou_saturating_radix(bytes, radix),
522    };
523
524    if digits.is_empty() {
525        return Err(ParseIntegerError {
526            kind: ParseIntegerErrorKind::Empty,
527        });
528    }
529
530    let mut result = I::zero();
531
532    for &digit in digits {
533        let mul = result.checked_mul(&base);
534        let x = match char::from(digit).to_digit(radix).and_then(I::from_u32) {
535            Some(x) => x,
536            None => {
537                return Err(ParseIntegerError {
538                    kind: ParseIntegerErrorKind::InvalidDigit,
539                })
540            }
541        };
542        result = match mul {
543            Some(result) => result,
544            None => return Ok(I::min_value()),
545        };
546        result = result.saturating_sub(x);
547    }
548
549    Ok(result)
550}
551
552/// Converts a byte slice to the closest possible integer.
553///
554/// Like [`btou_saturating`], but numbers may optionally start with a sign
555/// (`-` or `+`).
556///
557/// # Errors
558///
559/// Returns [`ParseIntegerError`] for any of the following conditions:
560///
561/// * `bytes` has no digits
562/// * not all characters of `bytes` are `0-9`, excluding an optional leading
563///   sign
564///
565/// # Panics
566///
567/// Panics in the pathological case that there is no representation of `10`
568/// in `I`.
569///
570/// # Examples
571///
572/// ```
573/// # use btoi::btoi_saturating;
574/// assert_eq!(Ok(127), btoi_saturating::<i8>(b"127"));
575/// assert_eq!(Ok(127), btoi_saturating::<i8>(b"128")); // i8 saturated
576/// assert_eq!(Ok(127), btoi_saturating::<i8>(b"+1024")); // i8 saturated
577/// assert_eq!(Ok(-128), btoi_saturating::<i8>(b"-128"));
578/// assert_eq!(Ok(-128), btoi_saturating::<i8>(b"-129")); // i8 saturated
579///
580/// assert_eq!(Ok(0), btoi_saturating::<u32>(b"-123")); // unsigned integer saturated
581/// ```
582///
583/// [`btou_saturating`]: fn.btou_saturating.html
584/// [`ParseIntegerError`]: struct.ParseIntegerError.html
585#[track_caller]
586pub fn btoi_saturating<I>(bytes: &[u8]) -> Result<I, ParseIntegerError>
587where
588    I: FromPrimitive + Zero + CheckedMul + Saturating + Bounded,
589{
590    btoi_saturating_radix(bytes, 10)
591}
592
593#[cfg(test)]
594mod tests {
595    #[cfg(feature = "std")]
596    extern crate std;
597
598    #[cfg(feature = "std")]
599    use std::{format, str, string::ToString as _, vec::Vec};
600
601    use super::*;
602
603    quickcheck! {
604        #[cfg(feature = "std")]
605        fn btou_identity(n: u32) -> bool {
606            Ok(n) == btou(n.to_string().as_bytes())
607        }
608
609        #[cfg(feature = "std")]
610        fn btou_binary_identity(n: u64) -> bool {
611            Ok(n) == btou_radix(format!("{:b}", n).as_bytes(), 2)
612        }
613
614        #[cfg(feature = "std")]
615        fn btou_octal_identity(n: u64) -> bool {
616            Ok(n) == btou_radix(format!("{:o}", n).as_bytes(), 8)
617        }
618
619        #[cfg(feature = "std")]
620        fn btou_lower_hex_identity(n: u64) -> bool {
621            Ok(n) == btou_radix(format!("{:x}", n).as_bytes(), 16)
622        }
623
624        #[cfg(feature = "std")]
625        fn btou_upper_hex_identity(n: u64) -> bool {
626            Ok(n) == btou_radix(format!("{:X}", n).as_bytes(), 16)
627        }
628
629        #[cfg(feature = "std")]
630        fn btoi_identity(n: i32) -> bool {
631            Ok(n) == btoi(n.to_string().as_bytes())
632        }
633
634        #[cfg(feature = "std")]
635        fn btou_saturating_identity(n: u32) -> bool {
636            Ok(n) == btou_saturating(n.to_string().as_bytes())
637        }
638
639        #[cfg(feature = "std")]
640        fn btoi_saturating_identity(n: i32) -> bool {
641            Ok(n) == btoi_saturating(n.to_string().as_bytes())
642        }
643
644        #[cfg(feature = "std")]
645        fn btoi_radix_std(bytes: Vec<u8>, radix: u32) -> bool {
646            let radix = radix % 35 + 2; // panic unless 2 <= radix <= 36
647            str::from_utf8(&bytes).ok().and_then(|src| i32::from_str_radix(src, radix).ok()) ==
648                btoi_radix(&bytes, radix).ok()
649        }
650    }
651
652    #[test]
653    fn test_lone_minus() {
654        assert!(btoi::<isize>(b"-").is_err());
655        assert!(btoi_radix::<isize>(b"-", 16).is_err());
656        assert!(btoi_saturating::<isize>(b"-").is_err());
657        assert!(btoi_saturating_radix::<isize>(b"-", 8).is_err());
658
659        assert!(btou::<isize>(b"-").is_err());
660        assert!(btou_radix::<isize>(b"-", 16).is_err());
661        assert!(btou_saturating::<isize>(b"-").is_err());
662        assert!(btou_saturating_radix::<isize>(b"-", 8).is_err());
663    }
664}