pub(crate) const Q_NUMBER_PREFIX: u8 = 0x80;
pub(crate) const MAX_BYTES_IN_ENCODING: usize = 11;
#[derive(Clone, Copy, Debug)]
pub struct QNumberStack {
bytes: [u8; MAX_BYTES_IN_ENCODING],
len: u8,
}
impl QNumberStack {
#[inline]
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.bytes[..self.len as usize]
}
#[inline]
#[must_use]
pub const fn len(&self) -> usize {
self.len as usize
}
#[inline]
#[must_use]
pub const 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];
bytes[0] = Q_NUMBER_PREFIX;
let mut trailing_zeroes = 0usize;
let mut index = MAX_BYTES_IN_ENCODING - 1;
loop {
let prev = index;
if nb & 0x7f != 0 {
break;
}
trailing_zeroes += 1;
nb >>= 7;
if index <= 1 {
break;
}
index -= 1;
debug_assert!(index < prev, "to_q_number_stack: index must decrease");
}
let content_len = (MAX_BYTES_IN_ENCODING - 1) - trailing_zeroes;
for i in (1..=content_len).rev() {
bytes[i] = (nb & 0x7f) as u8;
nb >>= 7;
}
QNumberStack {
bytes,
len: u8::try_from(1 + content_len)
.expect("Q-number length bounded by MAX_BYTES_IN_ENCODING"),
}
}
#[must_use]
pub fn q_num_stack(f: f64) -> QNumberStack {
to_q_number_stack(numbits_from_f64(f))
}
pub(crate) const fn numbits_from_f64(f: f64) -> u64 {
let u = f.to_bits();
let mask = (u.cast_signed() >> 63).cast_unsigned() | (1 << 63);
u ^ mask
}
pub(crate) fn to_q_number(nb: u64) -> Vec<u8> {
let mut nb = nb;
let mut trailing_zeroes = 0usize;
let max_content_bytes = MAX_BYTES_IN_ENCODING - 1; let mut index = max_content_bytes - 1;
loop {
let prev = index;
if nb & 0x7f != 0 {
break;
}
trailing_zeroes += 1;
nb >>= 7;
if index == 0 {
break;
}
index -= 1;
debug_assert!(index < prev, "to_q_number: index must decrease");
}
let content_len = max_content_bytes - trailing_zeroes;
let mut result = vec![0u8; 1 + content_len];
result[0] = Q_NUMBER_PREFIX;
for i in (1..=content_len).rev() {
result[i] = (nb & 0x7f) as u8;
nb >>= 7;
}
result
}
#[must_use]
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!("{b:02x}"))
.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 unit = f64::from_bits((rng_state >> 12) | 0x3FF0_0000_0000_0000) - 1.0;
let f = unit.mul_add(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: {a} ({na}) should be < {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());
}
#[test]
fn test_q_number_stack_len_matches_slice() {
let values = [
0.0,
1.0,
-1.0,
42.0,
1e15,
-1e-10,
f64::MAX,
f64::MIN_POSITIVE,
];
for &val in &values {
let q = q_num_stack(val);
assert_eq!(
q.len(),
q.as_slice().len(),
"len() disagrees with as_slice().len() for {val}"
);
}
}
#[test]
fn test_q_number_stack_is_empty_synthetic() {
let empty = QNumberStack {
bytes: [0; MAX_BYTES_IN_ENCODING],
len: 0,
};
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
}
#[test]
fn test_encoding_with_zero_numbits() {
let vec_result = to_q_number(0);
assert_eq!(vec_result, vec![Q_NUMBER_PREFIX]);
let stack_result = to_q_number_stack(0);
assert_eq!(stack_result.len(), 1);
assert_eq!(stack_result.as_slice(), &[Q_NUMBER_PREFIX]);
}
}