testnumbat_codec/
num_conv.rs

1use crate::nested_ser_output::NestedEncodeOutput;
2
3/// Adds number to output buffer.
4/// No argument generics here, because we want the executable binary as small as possible.
5/// Smaller types need to be converted to u64 before using this function.
6/// TODO: there might be a quicker version of this using transmute + reverse bytes.
7pub fn using_encoded_number<F: FnOnce(&[u8])>(
8    x: u64,
9    size_in_bits: usize,
10    signed: bool,
11    mut compact: bool,
12    f: F,
13) {
14    let mut result = [0u8; 8];
15    let mut result_size = 0usize;
16    let negative = compact && // only relevant when compact flag
17		signed &&  // only possible when signed flag
18		x >> (size_in_bits - 1) & 1 == 1; // compute by checking first bit
19
20    let irrelevant_byte = if negative { 0xffu8 } else { 0x00u8 };
21    let mut bit_offset = size_in_bits as isize - 8;
22    while bit_offset >= 0 {
23        // going byte by byte from most to least significant
24        let byte = (x >> (bit_offset as usize) & 0xffu64) as u8;
25
26        if compact {
27            // compact means ignoring irrelvant leading bytes
28            // that is 000... for positives and fff... for negatives
29            if byte != irrelevant_byte {
30                result[result_size] = byte;
31                result_size += 1;
32                compact = false;
33            }
34        } else {
35            result[result_size] = byte;
36            result_size += 1;
37        }
38
39        bit_offset -= 8;
40    }
41
42    f(&result[0..result_size])
43}
44
45pub fn top_encode_number_to_output<O: NestedEncodeOutput>(output: &mut O, x: u64, signed: bool) {
46    let bytes_be: [u8; 8] = x.to_be_bytes();
47    if x == 0 {
48        // 0 is a special case
49        return;
50    }
51
52    if signed && x == u64::MAX {
53        // -1 is a special case
54        output.push_byte(0xffu8);
55        return;
56    }
57
58    let negative = signed &&  // only possible when signed flag
59		bytes_be[0] > 0x7fu8; // most significant bit is 1
60
61    let irrelevant_byte = if negative { 0xffu8 } else { 0x00u8 };
62
63    let mut offset = 0usize;
64    while bytes_be[offset] == irrelevant_byte {
65        debug_assert!(offset < 7);
66        offset += 1;
67    }
68
69    if signed && bytes_be[offset] >> 7 != negative as u8 {
70        debug_assert!(offset > 0);
71        offset -= 1;
72    }
73
74    output.write(&bytes_be[offset..]);
75}
76
77/// Handles both signed and unsigned of any length.
78/// No generics here, because we want the executable binary as small as possible.
79#[inline(never)]
80pub fn bytes_to_number(bytes: &[u8], signed: bool) -> u64 {
81    if bytes.is_empty() {
82        return 0;
83    }
84    let negative = signed && bytes[0] >> 7 == 1;
85    let mut result = if negative {
86        // start with all bits set to 1,
87        // to ensure that if there are fewer bytes than the result type width,
88        // the leading bits will be 1 instead of 0
89        u64::MAX
90    } else {
91        0u64
92    };
93    for byte in bytes.iter() {
94        result <<= 8;
95        result |= *byte as u64;
96    }
97    result
98}