pub(crate) const MAX_BYTES_IN_ENCODING: usize = 10;
#[derive(Clone, Copy, Debug)]
pub struct QNumberStack {
bytes: [u8; MAX_BYTES_IN_ENCODING],
len: u8,
}
impl QNumberStack {
#[inline]
pub fn as_slice(&self) -> &[u8] {
&self.bytes[..self.len as usize]
}
#[inline]
pub fn len(&self) -> usize {
self.len as usize
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
pub(crate) fn to_q_number_stack(nb: u64) -> QNumberStack {
let mut nb = nb;
let mut bytes = [0u8; MAX_BYTES_IN_ENCODING];
let mut trailing_zeroes = 0usize;
let mut index = MAX_BYTES_IN_ENCODING - 1;
loop {
if nb & 0x7f != 0 {
break;
}
trailing_zeroes += 1;
nb >>= 7;
if index == 0 {
break;
}
index -= 1;
}
let len = MAX_BYTES_IN_ENCODING - trailing_zeroes;
for i in (0..len).rev() {
bytes[i] = (nb & 0x7f) as u8;
nb >>= 7;
}
QNumberStack {
bytes,
len: len as u8,
}
}
pub fn q_num_stack(f: f64) -> QNumberStack {
to_q_number_stack(numbits_from_f64(f))
}
pub(crate) fn numbits_from_f64(f: f64) -> u64 {
let u = f.to_bits();
let mask = ((u as i64 >> 63) as u64) | (1 << 63);
u ^ mask
}
pub(crate) fn to_q_number(nb: u64) -> Vec<u8> {
let mut nb = nb;
let mut trailing_zeroes = 0usize;
let mut index = MAX_BYTES_IN_ENCODING - 1;
loop {
if nb & 0x7f != 0 {
break;
}
trailing_zeroes += 1;
nb >>= 7;
if index == 0 {
break;
}
index -= 1;
}
let len = MAX_BYTES_IN_ENCODING - trailing_zeroes;
let mut result = vec![0u8; len];
for i in (0..len).rev() {
result[i] = (nb & 0x7f) as u8;
nb >>= 7;
}
result
}
pub fn q_num_from_f64(f: f64) -> Vec<u8> {
to_q_number(numbits_from_f64(f))
}
#[cfg(test)]
mod tests {
use super::*;
fn q_num_from_bytes(bytes: &[u8]) -> Option<Vec<u8>> {
let s = std::str::from_utf8(bytes).ok()?;
let f: f64 = s.parse().ok()?;
Some(q_num_from_f64(f))
}
fn q_num_to_string(q: &[u8]) -> String {
q.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<_>>()
.join("-")
}
#[test]
#[allow(clippy::inconsistent_digit_grouping)]
fn test_wildly_varying_numbers_are_comparable() {
let data: Vec<f64> = vec![
-5_000_000_000.0,
-4_999_999_999.99999,
-4_999_999_999.99998,
-4_999_999_999.99997,
-999999999.99999,
-999999999.99,
-10000.0,
-122.413496,
-0.000002,
0.0,
0.000001,
3.8,
3.9,
11.0,
12.0,
122.415028,
2.5e4,
999999999.999998,
999999999.999999,
4_999_999_999.99997,
4_999_999_999.99998,
4_999_999_999.99999,
5_000_000_000.0,
];
for i in 1..data.len() {
let s0 = q_num_from_f64(data[i - 1]);
let s1 = q_num_from_f64(data[i]);
assert!(
s0 < s1,
"Ordering failed at index {}: {} ({:?}) should be < {} ({:?})",
i,
data[i - 1],
q_num_to_string(&s0),
data[i],
q_num_to_string(&s1)
);
}
}
#[test]
fn test_float_variants() {
let floats: Vec<f64> = vec![350.0, 350.0, 350.0000000000, 3.5e2];
let q_nums: Vec<Vec<u8>> = floats.iter().map(|&f| q_num_from_f64(f)).collect();
for i in 1..q_nums.len() {
assert_eq!(
q_nums[i],
q_nums[i - 1],
"Q-numbers differ for {} vs {}",
floats[i - 1],
floats[i]
);
}
}
#[test]
fn test_byte_variants() {
let strings: Vec<&str> = vec!["350", "350.0", "350.0000", "3.5e2"];
let q_nums: Vec<Vec<u8>> = strings
.iter()
.map(|s| q_num_from_bytes(s.as_bytes()).unwrap())
.collect();
for i in 1..q_nums.len() {
assert_eq!(
q_nums[i],
q_nums[i - 1],
"Q-numbers differ for '{}' vs '{}'",
strings[i - 1],
strings[i]
);
}
}
fn verify_ordering_random(count: usize) {
use std::cmp::Ordering;
let mut floats: Vec<f64> = Vec::new();
let mut rng_state = 12345u64;
for _ in 0..count {
rng_state = rng_state.wrapping_mul(6364136223846793005).wrapping_add(1);
let random_u64 = rng_state;
let f = ((random_u64 as f64) / (u64::MAX as f64)) * 2_000_000_000.0 - 1_000_000_000.0;
floats.push(f);
}
floats.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
let q_nums: Vec<Vec<u8>> = floats.iter().map(|&f| q_num_from_f64(f)).collect();
for i in 1..q_nums.len() {
assert!(
q_nums[i - 1] <= q_nums[i],
"Q-number ordering failed at index {}: {:?} > {:?} (floats: {} > {})",
i,
q_nums[i - 1],
q_nums[i],
floats[i - 1],
floats[i]
);
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_ordering_random() {
verify_ordering_random(10_000);
}
#[test]
fn test_ordering_random_miri_friendly() {
verify_ordering_random(50);
}
#[test]
fn test_bad_numbers() {
let bads = vec!["xy", "- 53", "124x", "1.5ee7"];
for bad in bads {
assert!(
q_num_from_bytes(bad.as_bytes()).is_none(),
"Should reject: {}",
bad
);
}
}
#[test]
fn test_q_number_length() {
let q0 = q_num_from_f64(0.0);
let q_large = q_num_from_f64(1e15);
assert!(q0.len() <= MAX_BYTES_IN_ENCODING);
assert!(q_large.len() <= MAX_BYTES_IN_ENCODING);
}
#[test]
fn test_numbits_ordering_property() {
let pairs: Vec<(f64, f64)> = vec![
(-100.0, -50.0),
(-50.0, 0.0),
(0.0, 50.0),
(50.0, 100.0),
(-1e10, 1e10),
(0.0001, 0.0002),
];
for (a, b) in pairs {
let na = numbits_from_f64(a);
let nb = numbits_from_f64(b);
assert!(
na < nb,
"numbits ordering failed: {} ({}) should be < {} ({})",
a,
na,
b,
nb
);
}
}
#[test]
fn test_zero_handling() {
let q_zero = q_num_from_f64(0.0);
let q_neg_zero = q_num_from_f64(-0.0);
assert!(!q_zero.is_empty());
assert!(!q_neg_zero.is_empty());
}
#[test]
fn test_q_number_variants_equivalence() {
let test_values: Vec<f64> = vec![
0.0,
1.0,
-1.0,
42.0,
-42.0,
999.0,
1000.0,
123456.0,
0.000001,
-0.000001,
std::f64::consts::PI,
-std::f64::consts::PI,
1e10,
-1e10,
1e-10,
-1e-10,
f64::MIN_POSITIVE,
f64::MAX,
f64::MIN,
];
for &val in &test_values {
let vec_result = q_num_from_f64(val);
let stack_result = q_num_stack(val);
assert_eq!(
vec_result.as_slice(),
stack_result.as_slice(),
"Stack variant differs from Vec for value {}",
val
);
}
}
#[test]
fn test_q_number_variants_random() {
let mut rng_state = 54321u64;
for _ in 0..100 {
rng_state = rng_state.wrapping_mul(6364136223846793005).wrapping_add(1);
let f = f64::from_bits(rng_state & 0x7FEFFFFFFFFFFFFF);
if !f.is_finite() {
continue;
}
let vec_result = q_num_from_f64(f);
let stack_result = q_num_stack(f);
assert_eq!(
vec_result.as_slice(),
stack_result.as_slice(),
"Stack variant differs from Vec for value {}",
f
);
}
}
#[test]
fn test_q_number_stack_length() {
let q0 = q_num_stack(0.0);
let q_large = q_num_stack(1e15);
assert!(!q0.is_empty());
assert!(q0.len() <= MAX_BYTES_IN_ENCODING);
assert!(q_large.len() <= MAX_BYTES_IN_ENCODING);
assert!(!q0.is_empty());
}
}