csd/
csd.rs

1/// Find the highest power of two less than or equal to a given number
2///
3/// The `highest_power_of_two_in` function calculates the highest power of two that is less than or
4/// equal to a given number. This is done through a bit manipulation technique that fills all bits
5/// below the most significant bit (MSB) with 1s, then shifts and XORs to isolate just the MSB.
6///
7/// Reference:
8///
9/// * <https://thecodingbot.com/find-the-greatest-power-of-2-less-than-or-equal-to-a-given-number/>
10///
11/// Arguments:
12///
13/// * `x`: The parameter `x` is an unsigned 32-bit integer. It represents the number for which we want
14///        to find the highest power of two that is less than or equal to it.
15///
16/// Returns:
17///
18/// The function `highest_power_of_two_in` returns the highest power of two that is less than or equal
19/// to the given number.
20///
21/// # Examples
22///
23/// ```
24/// use csd::csd::highest_power_of_two_in;
25///
26/// assert_eq!(highest_power_of_two_in(14), 8);
27/// assert_eq!(highest_power_of_two_in(8), 8);
28/// assert_eq!(highest_power_of_two_in(1), 1);
29/// assert_eq!(highest_power_of_two_in(0), 0);
30/// assert_eq!(highest_power_of_two_in(3), 2);
31/// assert_eq!(highest_power_of_two_in(2), 2);
32/// ```
33#[inline]
34pub const fn highest_power_of_two_in(mut x: u32) -> u32 {
35    // Propagate the highest set bit to all lower bits
36    x |= x >> 1;
37    x |= x >> 2;
38    x |= x >> 4;
39    x |= x >> 8;
40    x |= x >> 16;
41    // Isolate the highest bit by XORing with the value shifted right by 1
42    x ^ (x >> 1)
43}
44
45/// Convert to CSD (Canonical Signed Digit) String representation
46///
47/// The `to_csd` function converts a given number to its Canonical Signed Digit (CSD) representation
48/// with a specified number of decimal places. CSD is a number system where each digit can be -1, 0, or +1
49/// (represented by '-', '0', '+'), and no two adjacent digits are non-zero.
50///
51/// - Original author: Harnesser
52/// - <https://sourceforge.net/projects/pycsd/>
53/// - License: GPL2
54///
55/// Arguments:
56///
57/// * `decimal_value`: The `decimal_value` parameter is a double precision floating-point number that represents the value
58///         to be converted to CSD (Canonical Signed Digit) representation.
59/// * `places`: The `places` parameter represents the number of decimal places to include in the CSD
60///         (Canonical Signed Digit) representation of the given `decimal_value`.
61///
62/// Returns:
63///
64/// The function `to_csd` returns a string representation of the given `decimal_value` in Canonical Signed Digit
65/// (CSD) format.
66///
67/// # Examples
68///
69/// ```
70/// use csd::csd::to_csd;
71///
72/// assert_eq!(to_csd(28.5, 2), "+00-00.+0".to_string());
73/// assert_eq!(to_csd(-0.5, 2), "0.-0".to_string());
74/// assert_eq!(to_csd(0.0, 2), "0.00".to_string());
75/// assert_eq!(to_csd(0.0, 0), "0.".to_string());
76/// ```
77pub fn to_csd(decimal_value: f64, places: i32) -> String {
78    let absnum = decimal_value.abs();
79    // Handle numbers less than 1.0 specially
80    let (mut rem, mut csd) = if absnum < 1.0 {
81        (0, String::from("0"))
82    } else {
83        // Calculate the highest power of two needed
84        let rem = (absnum * 1.5).log2().ceil() as i32;
85        (
86            rem,
87            String::with_capacity((rem.abs() + places.abs() + 2) as usize),
88        ) // +2 for '.' and potential sign
89    };
90    let mut p2n = 2.0_f64.powi(rem);
91    let mut decimal_value = decimal_value;
92    // Closure to handle both integer and fractional parts
93    let mut loop_fn = |value: i32, csd: &mut String| {
94        while rem > value {
95            rem -= 1;
96            p2n /= 2.0;
97            let det = 1.5 * decimal_value;
98            if det > p2n {
99                csd.push('+');
100                decimal_value -= p2n;
101            } else if det < -p2n {
102                csd.push('-');
103                decimal_value += p2n;
104            } else {
105                csd.push('0');
106            }
107        }
108    };
109    // Process integer part
110    loop_fn(0, &mut csd);
111    csd.push('.');
112    // Process fractional part
113    loop_fn(-places, &mut csd);
114
115    csd
116}
117
118/// Convert to CSD (Canonical Signed Digit) String representation
119///
120/// The `to_csd_i` function converts an integer into a Canonical Signed Digit (CSD) representation.
121/// This version works with integers only and produces a CSD string without a decimal point.
122///
123/// Arguments:
124///
125/// * `decimal_value`: The `decimal_value` parameter is an integer that represents the number for which we want to generate
126///         the CSD (Canonical Signed Digit) representation.
127///
128/// Returns:
129///
130/// The function `to_csd_i` returns a string representation of the given integer in Canonical Signed
131/// Digit (CSD) format.
132///
133/// # Examples
134///
135/// ```
136/// use csd::csd::to_csd_i;
137///
138/// assert_eq!(to_csd_i(28), "+00-00".to_string());
139/// assert_eq!(to_csd_i(-0), "0".to_string());
140/// assert_eq!(to_csd_i(0), "0".to_string());
141/// ```
142#[allow(dead_code)]
143pub fn to_csd_i(decimal_value: i32) -> String {
144    if decimal_value == 0 {
145        return "0".to_string();
146    }
147
148    // Calculate the highest power of two needed
149    let temp = (decimal_value.abs() * 3 / 2) as u32;
150    let mut p2n = highest_power_of_two_in(temp) as i32 * 2;
151    let mut csd = String::with_capacity(32); // Max 32 chars for i32
152    let mut decimal_value = decimal_value;
153
154    while p2n > 1 {
155        let p2n_half = p2n >> 1;
156        let det = 3 * decimal_value;
157        if det > p2n {
158            csd += "+";
159            decimal_value -= p2n_half;
160        } else if det < -p2n {
161            csd += "-";
162            decimal_value += p2n_half;
163        } else {
164            csd += "0";
165        }
166        p2n = p2n_half;
167    }
168
169    csd
170}
171
172/// Convert the CSD (Canonical Signed Digit) to a decimal
173///
174/// The `to_decimal_i` function converts a CSD (Canonical Signed Digit) string to a decimal integer.
175/// This function processes the CSD string character by character, building up the decimal value
176/// through bit shifting and addition/subtraction operations.
177///
178/// Arguments:
179///
180/// * `csd`: The `csd` parameter is a slice of characters representing a CSD (Canonical Signed Digit)
181///          string.
182///
183/// Returns:
184///
185/// The function `to_decimal_i` returns an `i32` value, which is the decimal representation of the input
186/// CSD (Canonical Signed Digit) string.
187///
188/// # Panics
189///
190/// Panics if unexpected character is encountered
191///
192/// # Examples
193///
194/// ```
195/// use csd::csd::to_decimal_i;
196///
197/// assert_eq!(to_decimal_i("+00-00"), 28);
198/// assert_eq!(to_decimal_i("0"), 0);
199/// ```
200#[allow(dead_code)]
201pub fn to_decimal_i(csd: &str) -> i32 {
202    csd.chars().fold(0, |acc, digit| match digit {
203        '0' => acc << 1,
204        '+' => (acc << 1) + 1,
205        '-' => (acc << 1) - 1,
206        _ => panic!("Work with 0, +, and - only"),
207    })
208}
209
210/// Helper function to convert the integral part of a CSD string to decimal
211///
212/// This function processes the integral part (before the decimal point) of a CSD string,
213/// returning both the converted value and the position of the decimal point if found.
214pub fn to_decimal_integral(csd: &str) -> (i32, usize) {
215    let mut decimal_value: i32 = 0;
216
217    for (pos, digit) in csd.chars().enumerate() {
218        match digit {
219            '0' => decimal_value <<= 1,
220            '+' => decimal_value = (decimal_value << 1) + 1,
221            '-' => decimal_value = (decimal_value << 1) - 1,
222            '.' => {
223                return (decimal_value, pos + 1);
224            }
225            _ => panic!("Work with 0, +, -, and . only"),
226        }
227    }
228
229    (decimal_value, 0)
230}
231
232/// Helper function to convert the fractional part of a CSD string to decimal
233///
234/// This function processes the fractional part (after the decimal point) of a CSD string,
235/// building up the decimal value by progressively halving the scale factor for each digit.
236pub fn to_decimal_fractional(csd: &str) -> f64 {
237    let mut decimal_value = 0.0;
238    let mut scale = 0.5; // Start with 2^-1
239
240    for digit in csd.chars() {
241        match digit {
242            '0' => {} // No change to value
243            '+' => decimal_value += scale,
244            '-' => decimal_value -= scale,
245            _ => panic!("Fractional part works with 0, +, and - only"),
246        }
247        scale /= 2.0; // Move to next fractional bit
248    }
249
250    decimal_value
251}
252
253/// Convert the CSD (Canonical Signed Digit) to a decimal
254///
255/// The `to_decimal` function converts a CSD (Canonical Signed Digit) string to a decimal number.
256/// This function handles both integral and fractional parts of the CSD representation.
257///
258/// Arguments:
259///
260/// * `csd`: The `csd` parameter is a string representing a Canonical Signed Digit (CSD) number.
261///
262/// Returns:
263///
264/// The function `to_decimal` returns a decimal number (f64) that is converted from the input CSD
265/// (Canonical Signed Digit) string.
266///
267/// # Panics
268///
269/// Panics if unexpected character is encountered
270///
271/// # Examples
272///
273/// ```
274/// use csd::csd::to_decimal;
275///
276/// assert_eq!(to_decimal("+00-00.+"), 28.5);
277/// assert_eq!(to_decimal("0.-"), -0.5);
278/// assert_eq!(to_decimal("0"), 0.0);
279/// assert_eq!(to_decimal("0.0"), 0.0);
280/// assert_eq!(to_decimal("0.+"), 0.5);
281/// assert_eq!(to_decimal("0.-"), -0.5);
282/// assert_eq!(to_decimal("0.++"), 0.75);
283/// assert_eq!(to_decimal("0.-+"), -0.25);
284/// ```
285pub fn to_decimal(csd: &str) -> f64 {
286    // First convert the integral part
287    let (integral, loc) = to_decimal_integral(csd);
288
289    if loc == 0 {
290        return integral as f64;
291    }
292
293    // Then convert the fractional part if present
294    let fractional = to_decimal_fractional(&csd[loc..]);
295    integral as f64 + fractional
296}
297
298/// Convert to CSD representation approximately with fixed number of non-zero
299///
300/// The `to_csdnnz` function converts a given number into a CSD (Canonic Signed Digit) representation
301/// approximately with a specified number of non-zero digits. This version limits the number of
302/// non-zero digits in the output representation.
303///
304/// Arguments:
305///
306/// * `decimal_value`: The `decimal_value` parameter is a double precision floating-point number that represents the input
307///          value for conversion to CSD (Canonic Signed Digit) fixed-point representation.
308/// * `nnz`: The parameter `nnz` stands for "number of non-zero bits". It represents the maximum number
309///          of non-zero bits allowed in the output CSD (Canonical Signed Digit) representation of the given
310///          `decimal_value`.
311///
312/// Returns:
313///
314/// The function `to_csdnnz` returns a string representation of the given `decimal_value` in Canonical Signed
315/// Digit (CSD) format.
316///
317/// # Examples
318///
319/// ```
320/// use csd::csd::to_csdnnz;
321///
322/// let s1 = to_csdnnz(28.5, 4);
323/// let s2 = to_csdnnz(-0.5, 4);
324///
325/// assert_eq!(to_csdnnz(28.5, 4), "+00-00.+".to_string());
326/// assert_eq!(to_csdnnz(-0.5, 4), "0.-".to_string());
327/// assert_eq!(to_csdnnz(0.0, 4), "0".to_string());
328/// assert_eq!(to_csdnnz(0.0, 0), "0".to_string());
329/// assert_eq!(to_csdnnz(0.5, 4), "0.+".to_string());
330/// assert_eq!(to_csdnnz(-0.5, 4), "0.-".to_string());
331/// assert_eq!(to_csdnnz(28.5, 2), "+00-00".to_string());
332/// assert_eq!(to_csdnnz(28.5, 1), "+00000".to_string());
333/// ```
334#[allow(dead_code)]
335pub fn to_csdnnz(decimal_value: f64, nnz: u32) -> String {
336    let absnum = decimal_value.abs();
337    let (mut rem, mut csd) = if absnum < 1.0 {
338        (0, "0".to_string())
339    } else {
340        let rem = (absnum * 1.5).log2().ceil() as i32;
341        (rem, "".to_string())
342    };
343    let mut p2n = 2.0_f64.powi(rem);
344    let mut decimal_value = decimal_value;
345    let mut nnz = nnz;
346
347    // Process both integer and fractional parts while respecting the nnz limit
348    while rem > 0 || (nnz > 0 && decimal_value.abs() > 1e-100) {
349        if rem == 0 {
350            csd.push('.');
351        }
352        p2n /= 2.0;
353        rem -= 1;
354        let det = 1.5 * decimal_value;
355        if det > p2n {
356            csd.push('+');
357            decimal_value -= p2n;
358            nnz -= 1;
359        } else if det < -p2n {
360            csd += "-";
361            decimal_value += p2n;
362            nnz -= 1;
363        } else {
364            csd.push('0');
365        }
366        // Stop processing if we've used all non-zero digits
367        if nnz == 0 {
368            decimal_value = 0.0;
369        }
370    }
371
372    csd
373}
374
375/// Convert to CSD (Canonical Signed Digit) String representation
376///
377/// The `to_csdnnz_i` function converts an integer into a Canonical Signed Digit (CSD) representation
378/// approximately with a specified number of non-zero digits. This is the integer version of to_csdnnz.
379///
380/// Arguments:
381///
382/// * `decimal_value`: The `decimal_value` parameter is an integer that represents the number for which we want to generate
383///         the CSD (Canonical Signed Digit) representation.
384/// * `nnz`: The parameter `nnz` stands for "number of non-zero bits". It represents the maximum number
385///          of non-zero bits allowed in the output CSD (Canonical Signed Digit) representation of the given
386///          `decimal_value`.
387///
388/// Returns:
389///
390/// The function `to_csdnnz_i` returns a string representation of the given integer in Canonical Signed
391/// Digit (CSD) format.
392///
393/// # Examples
394///
395/// ```
396/// use csd::csd::to_csdnnz_i;
397///
398/// assert_eq!(to_csdnnz_i(28, 4), "+00-00".to_string());
399/// assert_eq!(to_csdnnz_i(-0, 4), "0".to_string());
400/// assert_eq!(to_csdnnz_i(0, 4), "0".to_string());
401/// assert_eq!(to_csdnnz_i(158, 2), "+0+00000".to_string());
402/// ```
403#[allow(dead_code)]
404pub fn to_csdnnz_i(decimal_value: i32, nnz: u32) -> String {
405    if decimal_value == 0 {
406        return "0".to_string();
407    }
408
409    // Calculate the highest power of two needed
410    let temp = (decimal_value.abs() * 3 / 2) as u32;
411    let mut p2n = highest_power_of_two_in(temp) as i32 * 2;
412    let mut csd = String::with_capacity(32); // Max 32 chars for i32
413    let mut decimal_value = decimal_value;
414    let mut nnz = nnz;
415
416    while p2n > 1 {
417        let p2n_half = p2n >> 1;
418        let det = 3 * decimal_value;
419        if det > p2n {
420            csd += "+";
421            decimal_value -= p2n_half;
422            nnz -= 1;
423        } else if det < -p2n {
424            csd += "-";
425            decimal_value += p2n_half;
426            nnz -= 1;
427        } else {
428            csd += "0";
429        }
430        p2n = p2n_half;
431        // Stop processing if we've used all non-zero digits
432        if nnz == 0 {
433            decimal_value = 0;
434        }
435    }
436
437    csd
438}
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443    use quickcheck_macros::quickcheck;
444
445    #[test]
446    fn it_works() {
447        let result = 2 + 2;
448        assert_eq!(result, 4);
449    }
450
451    #[test]
452    fn test_to_csd() {
453        assert_eq!(to_csd(28.5, 2), "+00-00.+0".to_string());
454        assert_eq!(to_csd(-0.5, 2), "0.-0".to_string());
455        assert_eq!(to_csd(0.0, 2), "0.00".to_string());
456        assert_eq!(to_csd(0.0, 0), "0.".to_string());
457        assert_eq!(to_csd(2.5, 4), "+0.+000".to_string());
458    }
459
460    #[test]
461    #[should_panic]
462    fn test_to_decimal_invalid1() {
463        let _res = to_decimal("+00XXX-00.00+");
464    }
465
466    #[test]
467    #[should_panic]
468    fn test_to_decimal_invalid2() {
469        let _res = to_decimal("+00-00.0XXX0+");
470    }
471
472    #[test]
473    fn test_to_decimal_i() {
474        assert_eq!(to_decimal_i("+00-00"), 28);
475        assert_eq!(to_decimal_i("0"), 0);
476    }
477
478    #[test]
479    #[should_panic]
480    fn test_to_decimal_i_invalid() {
481        let _res = to_decimal_i("+00-00.00+");
482    }
483
484    #[test]
485    fn test_to_csdnnz() {
486        assert_eq!(to_csdnnz(28.5, 4), "+00-00.+".to_string());
487        assert_eq!(to_csdnnz(-0.5, 4), "0.-".to_string());
488        assert_eq!(to_csdnnz(0.0, 4), "0".to_string());
489        assert_eq!(to_csdnnz(0.0, 0), "0".to_string());
490        assert_eq!(to_csdnnz(0.5, 4), "0.+".to_string());
491        assert_eq!(to_csdnnz(-0.5, 4), "0.-".to_string());
492        assert_eq!(to_csdnnz(28.5, 2), "+00-00".to_string());
493        assert_eq!(to_csdnnz(28.5, 1), "+00000".to_string());
494    }
495
496    #[test]
497    fn test_to_csdnnz_i() {
498        assert_eq!(to_csdnnz_i(28, 4), "+00-00".to_string());
499        assert_eq!(to_csdnnz_i(-0, 4), "0".to_string());
500        assert_eq!(to_csdnnz_i(0, 4), "0".to_string());
501        assert_eq!(to_csdnnz_i(0, 0), "0".to_string());
502        assert_eq!(to_csdnnz_i(158, 2), "+0+00000".to_string());
503    }
504
505    #[quickcheck]
506    fn test_csd(d: i32) -> bool {
507        let f = d as f64 / 8.0;
508        f == to_decimal(&to_csd(f, 4))
509    }
510
511    #[quickcheck]
512    fn test_csd_i(d: i32) -> bool {
513        let d = d / 3; // prevent overflow
514        let csd = to_csd_i(d);
515        d == to_decimal_i(&csd)
516    }
517
518    // #[quickcheck]
519    // fn test_csdnnz(d: i32) -> bool {
520    //     let f = d as f64 / 8.0;
521    //     let csd = to_csdnnz(f, 4);
522    //     let f_hat = to_decimal(&csd);
523    //     (f - f_hat).abs() <= 1.5
524    // }
525
526    // #[quickcheck]
527    // fn test_csdnnz_i(d: i32) -> bool {
528    //     let d = d / 3; // prevent overflow
529    //     let csd = to_csdnnz_i(d, 4);
530    //     let d_hat = to_decimal(&csd);
531    //     (d as f64 - d_hat).abs() <= 1.5
532    // }
533
534    #[test]
535    fn test_highest_power_of_two_in() {
536        assert_eq!(highest_power_of_two_in(14), 8);
537        assert_eq!(highest_power_of_two_in(8), 8);
538        assert_eq!(highest_power_of_two_in(1), 1);
539        assert_eq!(highest_power_of_two_in(0), 0);
540        assert_eq!(highest_power_of_two_in(3), 2);
541        assert_eq!(highest_power_of_two_in(2), 2);
542        assert_eq!(highest_power_of_two_in(u32::MAX), 2147483648);
543    }
544}