Skip to content

added char() implementations and tests #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,67 @@ impl Rng {
let i = self.u8(..len);
CHARS[i as usize] as char
}

/// Generates a random `char` in the given range.
///
/// Panics if the range is empty.
#[inline]
pub fn char(&self, range: impl RangeBounds<char>) -> char {
use std::convert::{TryFrom, TryInto};

let panic_empty_range = || {
panic!(
"empty range: {:?}..{:?}",
range.start_bound(),
range.end_bound()
)
};

let surrogate_start = 0xd800u32;
let surrogate_len = 0x800u32;

let low = match range.start_bound() {
Bound::Unbounded => 0u8 as char,
Bound::Included(&x) => x,
Bound::Excluded(&x) => {
let scalar = if x as u32 == surrogate_start - 1 {
surrogate_start + surrogate_len
} else {
x as u32 + 1
};
char::try_from(scalar).unwrap_or_else(|_| panic_empty_range())
}
};

let high = match range.end_bound() {
Bound::Unbounded => std::char::MAX,
Bound::Included(&x) => x,
Bound::Excluded(&x) => {
let scalar = if x as u32 == surrogate_start + surrogate_len {
surrogate_start - 1
} else {
(x as u32).wrapping_sub(1)
};
char::try_from(scalar).unwrap_or_else(|_| panic_empty_range())
}
};

if low > high {
panic_empty_range();
}

let gap = if (low as u32) < surrogate_start && (high as u32) >= surrogate_start {
surrogate_len
} else {
0
};
let range = high as u32 - low as u32 - gap;
let mut val = self.u32(0..=range) + low as u32;
if val >= surrogate_start {
val += gap;
}
val.try_into().unwrap()
}
}

/// Initializes the thread-local generator with the given seed.
Expand Down Expand Up @@ -594,6 +655,7 @@ integer!(u128, "Generates a random `u128` in the given range.");
integer!(i128, "Generates a random `i128` in the given range.");
integer!(usize, "Generates a random `usize` in the given range.");
integer!(isize, "Generates a random `isize` in the given range.");
integer!(char, "Generates a random `char` in the given range.");

/// Generates a random `f32` in range `0..1`.
pub fn f32() -> f32 {
Expand Down
44 changes: 44 additions & 0 deletions tests/char.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use std::convert::TryFrom;
use std::ops::RangeBounds;

fn test_char_coverage<R>(n: usize, range: R)
where
R: Iterator<Item = char> + RangeBounds<char> + Clone,
{
use std::collections::HashSet;

let all: HashSet<char> = range.clone().collect();
let mut covered = HashSet::new();
for _ in 0..n {
let c = fastrand::char(range.clone());
assert!(all.contains(&c));
covered.insert(c);
}
assert_eq!(covered, all);
}

#[test]
fn test_char() {
// ASCII control chars.
let nul = 0u8 as char;
let soh = 1u8 as char;
let stx = 2u8 as char;
// Some undefined Hangul Jamo codepoints just before
// the surrogate area.
let last_jamo = char::try_from(0xd7ffu32).unwrap();
let penultimate_jamo = char::try_from(last_jamo as u32 - 1).unwrap();
// Private-use codepoints just after the surrogate area.
let first_private = char::try_from(0xe000u32).unwrap();
let second_private = char::try_from(first_private as u32 + 1).unwrap();
// Private-use codepoints at the end of Unicode space.
let last_private = std::char::MAX;
let penultimate_private = char::try_from(last_private as u32 - 1).unwrap();

test_char_coverage(100, nul..stx);
test_char_coverage(100, nul..=soh);

test_char_coverage(400, penultimate_jamo..second_private);
test_char_coverage(400, penultimate_jamo..=second_private);

test_char_coverage(100, penultimate_private..=last_private);
}