libsurfer/translation/
fixed_point.rs

1use num::{BigUint, One, Zero};
2use std::cmp::Ordering;
3use std::ops::BitAnd;
4
5/// Convert a `BigUint` to a string interpreted as unsigned fixed point value.
6///
7/// The output is equivalent to `uint / (2 ** lg_scaling_factor)` (without integer truncation
8/// through division)
9pub(crate) fn big_uint_to_ufixed(uint: &BigUint, lg_scaling_factor: i64) -> String {
10    match lg_scaling_factor.cmp(&0) {
11        Ordering::Less => {
12            format!("{}", uint << (-lg_scaling_factor))
13        }
14        Ordering::Equal => format!("{}", uint),
15        Ordering::Greater => {
16            let mask = (BigUint::one() << lg_scaling_factor) - 1_u32;
17
18            // Split fixed point value into integer and remainder
19            let integer_part = uint >> lg_scaling_factor;
20            let mut remainder = uint.bitand(&mask);
21
22            if remainder.is_zero() {
23                integer_part.to_string() // No fractional part
24            } else {
25                let mut fractional_part = String::new();
26
27                // Scale up the remainder to extract fractional digits
28                for _ in 0..lg_scaling_factor {
29                    remainder *= 10_u32;
30                    let digit = &remainder >> lg_scaling_factor;
31                    fractional_part.push_str(&digit.to_string());
32                    remainder &= &mask;
33
34                    // Stop if the scaled remainder becomes zero
35                    if remainder.is_zero() {
36                        break;
37                    }
38                }
39
40                format!("{}.{}", integer_part, fractional_part)
41            }
42        }
43    }
44}
45
46/// Convert a `BigUint` to a string interpreted as signed fixed point value.
47///
48/// The output is equivalent to `as_signed(uint) / (2 ** lg_scaling_factor)` (without integer
49/// truncation through division)
50/// where `as_signed()` interprets the `uint` as a signed value using two's complement.
51pub(crate) fn big_uint_to_sfixed(uint: &BigUint, num_bits: u64, lg_scaling_factor: i64) -> String {
52    if num_bits == 0 {
53        return "".to_string();
54    }
55    if uint.bit(num_bits - 1) {
56        let inverted_uint = (BigUint::one() << num_bits) - uint;
57        format!("-{}", big_uint_to_ufixed(&inverted_uint, lg_scaling_factor))
58    } else {
59        big_uint_to_ufixed(uint, lg_scaling_factor)
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use std::str::FromStr;
67
68    fn ucheck(value: impl Into<BigUint>, lg_scaling_factor: i64, expected: impl Into<String>) {
69        let value = value.into();
70        let result = big_uint_to_ufixed(&value, lg_scaling_factor);
71        assert_eq!(result, expected.into());
72    }
73
74    fn scheck(
75        value: impl Into<BigUint>,
76        num_bits: u64,
77        lg_scaling_factor: i64,
78        expected: impl Into<String>,
79    ) {
80        let value = value.into();
81        let result = big_uint_to_sfixed(&value, num_bits, lg_scaling_factor);
82        assert_eq!(result, expected.into());
83    }
84
85    #[test]
86    fn zero_scaling_factor() {
87        ucheck(32_u32, 0, "32");
88        scheck(32_u32, 6, 0, "-32");
89    }
90
91    #[test]
92    fn zero_width_signed() {
93        scheck(32_u32, 0, 0, "");
94    }
95
96    #[test]
97    fn test_exact_integer() {
98        ucheck(256_u32, 8, "1");
99    }
100
101    #[test]
102    fn test_fractional_value() {
103        ucheck(48225_u32, 8, "188.37890625");
104        ucheck(100_u32, 10, "0.09765625");
105        ucheck(8192_u32, 15, "0.25");
106        ucheck(16384_u32, 15, "0.5");
107    }
108
109    #[test]
110    fn test_large_value() {
111        ucheck(
112            BigUint::from_str("12345678901234567890").unwrap(),
113            20,
114            "11773756886705.9401416778564453125",
115        )
116    }
117
118    #[test]
119    fn test_value_less_than_one() {
120        ucheck(1_u32, 10, "0.0009765625")
121    }
122
123    #[test]
124    fn test_zero_value() {
125        ucheck(0_u32, 16, "0")
126    }
127
128    #[test]
129    fn test_negative_scaling_factor() {
130        ucheck(500_u32, -1, "1000")
131    }
132
133    #[test]
134    fn test_negative_fractional_value() {
135        scheck(0x1E000_u32, 17, 15, "-0.25");
136        scheck(0x1FF_u32, 9, 7, "-0.0078125");
137    }
138
139    #[test]
140    fn test_negative_exact_integer() {
141        scheck(0xFCC_u32, 12, 2, "-13");
142        scheck(0x100_u32, 9, 7, "-2");
143        scheck(256_u32, 9, 8, "-1")
144    }
145
146    #[test]
147    fn test_negative_non_signed_values() {
148        scheck(0x034_u32, 12, 2, "13");
149        scheck(0x02000_u32, 17, 15, "0.25");
150        scheck(0x0FF_u32, 9, 7, "1.9921875");
151    }
152
153    #[test]
154    fn large_bit_widths() {
155        ucheck(0x123456789ABCDEF0_u64, 20, "1250999896491.8044281005859375");
156    }
157}