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}