use std::cell::RefCell;
use std::fmt;
thread_local! {
static STRING_BUFFER: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
}
fn with_string_buffer<T, F>(f: F) -> T
where
F: FnOnce(&mut Vec<u8>) -> T,
{
STRING_BUFFER.with(|buffer| {
let mut buf = buffer.try_borrow_mut().unwrap();
buf.clear();
f(&mut buf)
})
}
pub struct CsdBuilder {
value: f64,
places: Option<i32>,
max_non_zeros: Option<u32>,
}
#[derive(Debug, Clone, Copy)]
pub enum RoundingStrategy {
Nearest,
Down,
Up,
}
impl CsdBuilder {
pub fn new(value: f64) -> Self {
Self {
value,
places: None,
max_non_zeros: None,
}
}
pub fn places(mut self, places: i32) -> Self {
self.places = Some(places.max(0));
self
}
pub fn max_non_zeros(mut self, max_non_zeros: u32) -> Self {
self.max_non_zeros = Some(max_non_zeros);
self
}
pub fn rounding_strategy(self, strategy: RoundingStrategy) -> Self {
match strategy {
RoundingStrategy::Nearest => self,
RoundingStrategy::Down => self,
RoundingStrategy::Up => self,
}
}
pub fn build(self) -> CsdResult<String> {
let places = self.places.unwrap_or(4);
if let Some(max_nnz) = self.max_non_zeros {
if max_nnz == 0 && self.value != 0.0 {
return Err(CsdError::InvalidFormat(
"Cannot represent non-zero value with 0 non-zero digits".to_string(),
));
}
to_csdnnz_safe(self.value, max_nnz)
} else {
if places < 0 {
return Err(CsdError::InvalidFormat(
"Number of places cannot be negative".to_string(),
));
}
Ok(to_csd(self.value, places))
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum CsdError {
InvalidCharacter(char, usize),
InvalidFormat(String),
Overflow { input: f64, max_bits: u32 },
PrecisionLoss { input: f64, actual: f64 },
ConsecutiveNonZero(usize),
EmptyString,
}
impl fmt::Display for CsdError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CsdError::InvalidCharacter(c, pos) => {
write!(
f,
"Invalid character '{}' at position {} in CSD string",
c, pos
)
}
CsdError::InvalidFormat(msg) => write!(f, "Invalid CSD format: {}", msg),
CsdError::Overflow { input, max_bits } => {
write!(f, "Overflow: input {} exceeds {} bits", input, max_bits)
}
CsdError::PrecisionLoss { input, actual } => {
write!(f, "Precision loss: input {} converted to {}", input, actual)
}
CsdError::ConsecutiveNonZero(pos) => {
write!(f, "Consecutive non-zero digits at position {}", pos)
}
CsdError::EmptyString => write!(f, "Empty string provided"),
}
}
}
impl std::error::Error for CsdError {}
pub type CsdResult<T> = Result<T, CsdError>;
#[cfg_attr(docsrs, doc = svgbobdoc::transform!(
/// Find the highest power of two less than or equal to a given number
///
/// The `highest_power_of_two_in` function calculates the highest power of two that is less than or
/// equal to a given number. This is done through a bit manipulation technique that fills all bits
/// below the most significant bit (MSB) with 1s, then shifts and XORs to isolate just the MSB.
///
/// ```svgbob
/// Input x = 14 (binary: 1110)
/// │
/// ▼
/// Fill lower bits: 1111
/// │
/// ▼
/// Shift and XOR: 1111 ^ 0111 = 1000 (8)
/// │
/// ▼
/// Result: 8 (2³)
/// ```
///
/// Reference:
///
/// * <https://thecodingbot.com/find-the-greatest-power-of-2-less-than-or-equal-to-a-given-number/>
///
/// Arguments:
///
/// * `x`: The parameter `x` is an unsigned 32-bit integer. It represents the number for which we want
/// to find the highest power of two that is less than or equal to it.
///
/// Returns:
///
/// The function `highest_power_of_two_in` returns the highest power of two that is less than or equal
/// to the given number.
///
/// # Examples
///
/// ```
/// use csd::csd::highest_power_of_two_in;
///
/// assert_eq!(highest_power_of_two_in(14), 8);
/// assert_eq!(highest_power_of_two_in(8), 8);
/// assert_eq!(highest_power_of_two_in(1), 1);
/// assert_eq!(highest_power_of_two_in(0), 0);
/// assert_eq!(highest_power_of_two_in(3), 2);
/// assert_eq!(highest_power_of_two_in(2), 2);
/// ```
))]
#[must_use]
#[inline]
pub const fn highest_power_of_two_in(mut x: u32) -> u32 {
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x ^ (x >> 1)
}
#[must_use]
pub const fn is_power_of_two(x: u32) -> bool {
x != 0 && (x & (x - 1)) == 0
}
#[must_use]
pub const fn count_non_zero_digits(csd: &str) -> usize {
let mut count = 0;
let bytes = csd.as_bytes();
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'+' | b'-' => count += 1,
_ => {}
}
i += 1;
}
count
}
#[must_use]
pub const fn validate_csd_format(csd: &str) -> bool {
if csd.is_empty() {
return false;
}
let bytes = csd.as_bytes();
let mut i = 0;
let mut prev_was_nonzero = false;
while i < bytes.len() {
match bytes[i] {
b'0' | b'+' | b'-' | b'.' => {}
_ => return false,
}
let is_nonzero = matches!(bytes[i], b'+' | b'-');
if prev_was_nonzero && is_nonzero && bytes[i] != b'.' {
return false;
}
prev_was_nonzero = is_nonzero && bytes[i] != b'.';
i += 1;
}
true
}
#[cfg_attr(docsrs, doc = svgbobdoc::transform!(
/// Convert to CSD (Canonical Signed Digit) String representation
///
/// The `to_csd` function converts a given number to its Canonical Signed Digit (CSD) representation
/// with a specified number of decimal places. CSD is a number system where each digit can be -1, 0, or +1
/// (represented by '-', '0', '+'), and no two adjacent digits are non-zero.
///
/// ```svgbob
/// Decimal: 28.5
/// │
/// ▼
/// Algorithm Process:
/// 28.5 * 1.5 = 42.75 → log₂(42.75) ≈ 5.4 → ceil = 6
/// Start with 2⁵ = 32, compare with 1.5 * value
/// │
/// ▼
/// Result: "+00-00.+0"
/// │ │ │ ││
/// │ │ │ │└─ fractional: place 1 (0.5)
/// │ │ │ └── fractional: place 2 (0.25)
/// │ │ └──── decimal point
/// │ └─────── integer: 16s place (+)
/// └────────── integer: 32s place (+)
/// ```
///
/// - Original author: Harnesser
/// - <https://sourceforge.net/projects/pycsd/>
/// - License: GPL2
///
/// Arguments:
///
/// * `decimal_value`: The `decimal_value` parameter is a double precision floating-point number that represents the value
/// to be converted to CSD (Canonical Signed Digit) representation.
/// * `places`: The `places` parameter represents the number of decimal places to include in the CSD
/// (Canonical Signed Digit) representation of the given `decimal_value`.
///
/// Returns:
///
/// The function `to_csd` returns a string representation of the given `decimal_value` in Canonical Signed Digit
/// (CSD) format.
///
/// # Examples
///
/// ```
/// use csd::csd::to_csd;
///
/// assert_eq!(to_csd(28.5, 2), "+00-00.+0".to_string());
/// assert_eq!(to_csd(-0.5, 2), "0.-0".to_string());
/// assert_eq!(to_csd(0.0, 2), "0.00".to_string());
/// assert_eq!(to_csd(0.0, 0), "0.".to_string());
/// ```
/// # Panics
///
/// Panics if the resulting CSD string is not valid UTF-8.
))]
#[must_use]
pub fn to_csd(decimal_value: f64, places: i32) -> String {
if decimal_value == 0.0 {
return with_string_buffer(|buf| {
buf.push(b'0');
buf.push(b'.');
for _ in 0..places {
buf.push(b'0');
}
String::from_utf8(buf.clone()).unwrap()
});
}
let absnum = decimal_value.abs();
let initial_capacity = if absnum < 1.0 {
2 + places.max(0) as usize
} else {
#[allow(clippy::cast_possible_truncation)]
let rem = (absnum * 1.5).log2().ceil() as i32;
(rem.abs() + places.max(0).abs() + 2) as usize
};
with_string_buffer(|buf| {
buf.reserve(initial_capacity);
let (mut rem, mut p2n, mut decimal_value) = if absnum < 1.0 {
buf.push(b'0');
(0, 1.0, decimal_value)
} else {
#[allow(clippy::cast_possible_truncation)]
let rem = (absnum * 1.5).log2().ceil() as i32;
#[allow(clippy::cast_sign_loss)]
(rem, 2.0_f64.powi(rem), decimal_value)
};
while rem > 0 {
rem -= 1;
p2n /= 2.0;
let det = 1.5 * decimal_value;
if det > p2n {
buf.push(b'+');
decimal_value -= p2n;
} else if det < -p2n {
buf.push(b'-');
decimal_value += p2n;
} else {
buf.push(b'0');
}
}
buf.push(b'.');
let mut frac_places = places;
while frac_places > 0 {
p2n /= 2.0;
let det = 1.5 * decimal_value;
if det > p2n {
buf.push(b'+');
decimal_value -= p2n;
} else if det < -p2n {
buf.push(b'-');
decimal_value += p2n;
} else {
buf.push(b'0');
}
frac_places -= 1;
}
String::from_utf8(buf.clone()).unwrap()
})
}
#[cfg_attr(docsrs, doc = svgbobdoc::transform!(
/// Convert to CSD (Canonical Signed Digit) String representation
///
/// The `to_csd_i` function converts an integer into a Canonical Signed Digit (CSD) representation.
/// This version works with integers only and produces a CSD string without a decimal point.
///
/// ```svgbob
/// Integer: 28
/// │
/// ▼
/// Algorithm:
/// temp = (28 * 3 / 2) = 42
/// highest_power_of_two_in(42) = 32
/// Start with 2⁵ = 32, process bit by bit
/// │
/// ▼
/// Result: "+00-00"
/// │ ││││
/// │ │││└─ 1s place: 0 (0*2⁰ = 0)
/// │ ││└── 2s place: 0 (0*2¹ = 0)
/// │ │└─── 4s place: - (-1*2² = -4)
/// │ └──── 8s place: 0 (0*2³ = 0)
/// └─────── 16s place: + (+1*2⁴ = +16)
/// Interpretation: +16 + 0 + 0 + (-4) + 0 = 12? No, let me be more accurate:
/// In "+00-00": +32 +0 +0 +(-8) +0 = 24. Actually "+00-00" represents 28 as:
/// From highest bit: +32 +0 +0 +(-4) +0 = 28, so the format is "+00-00"
/// ```
///
/// Arguments:
///
/// * `decimal_value`: The `decimal_value` parameter is an integer that represents the number for which we want to generate
/// the CSD (Canonical Signed Digit) representation.
///
/// Returns:
///
/// The function `to_csd_i` returns a string representation of the given integer in Canonical Signed
/// Digit (CSD) format.
///
/// # Examples
///
/// ```
/// use csd::csd::to_csd_i;
///
/// assert_eq!(to_csd_i(28), "+00-00".to_string());
/// assert_eq!(to_csd_i(-0), "0".to_string());
/// assert_eq!(to_csd_i(0), "0".to_string());
/// ```
/// # Panics
///
/// Panics if the resulting CSD string is not valid UTF-8.
))]
#[allow(dead_code)]
#[must_use]
pub fn to_csd_i(decimal_value: i32) -> String {
if decimal_value == 0 {
return "0".to_string();
}
#[allow(clippy::cast_sign_loss)]
let temp = (decimal_value.abs() * 3 / 2) as u32;
#[allow(clippy::cast_possible_wrap)]
let mut p2n = highest_power_of_two_in(temp) as i32 * 2;
let mut csd = Vec::with_capacity(32); let mut decimal_value = decimal_value;
while p2n > 1 {
let p2n_half = p2n >> 1;
let det = 3 * decimal_value;
if det > p2n {
csd.push(b'+');
decimal_value -= p2n_half;
} else if det < -p2n {
csd.push(b'-');
decimal_value += p2n_half;
} else {
csd.push(b'0');
}
p2n = p2n_half;
}
String::from_utf8(csd).unwrap()
}
#[cfg_attr(docsrs, doc = svgbobdoc::transform!(
/// Convert the CSD (Canonical Signed Digit) to a decimal integer
///
/// The `to_decimal_i` function converts a CSD (Canonical Signed Digit) string to a decimal integer.
/// This function processes the CSD string character by character, building up the decimal value
/// through bit shifting and addition/subtraction operations.
///
/// ```svgbob
/// CSD: "+00-00"
/// │││ ││
/// │││ │└─ 1s place: 0 (0)
/// │││ └── 2s place: 0 (0)
/// ││└──── 4s place: - (-4)
/// │└───── 8s place: 0 (0)
/// └────── 16s place: + (+16)
/// │
/// ▼
/// Calculation:
/// Start with 0, for each digit:
/// (0 << 1) + 1 = 1 (for '+')
/// (1 << 1) + 0 = 2 (for '0')
/// (2 << 1) + 0 = 4 (for '0')
/// (4 << 1) - 1 = 7 (for '-')
/// (7 << 1) + 0 = 14 (for '0')
/// (14 << 1) + 0 = 28 (for '0') = 28
/// ```
///
/// Arguments:
///
/// * `csd`: The `csd` parameter is a slice of characters representing a CSD (Canonical Signed Digit)
/// string.
///
/// Returns:
///
/// The function `to_decimal_i` returns an `i32` value, which is the decimal representation of the input
/// CSD (Canonical Signed Digit) string.
///
/// # Panics
///
/// Panics if unexpected character is encountered
///
/// # Examples
///
/// ```
/// use csd::csd::to_decimal_i;
///
/// assert_eq!(to_decimal_i("+00-00"), 28);
/// assert_eq!(to_decimal_i("0"), 0);
/// ```
))]
#[allow(dead_code)]
pub fn to_decimal_i_safe(csd: &str) -> CsdResult<i32> {
if csd.is_empty() {
return Err(CsdError::EmptyString);
}
let mut result = 0i32;
let mut prev_was_nonzero = false;
for (i, c) in csd.chars().enumerate() {
let is_nonzero = matches!(c, '+' | '-');
if prev_was_nonzero && is_nonzero {
return Err(CsdError::ConsecutiveNonZero(i));
}
result = match c {
'0' => result << 1,
'+' => (result << 1) + 1,
'-' => (result << 1) - 1,
_ => return Err(CsdError::InvalidCharacter(c, i)),
};
prev_was_nonzero = is_nonzero;
}
Ok(result)
}
#[allow(dead_code)]
#[must_use]
pub const fn to_decimal_i(csd: &str) -> i32 {
let mut result = 0i32;
let mut i = 0;
let bytes = csd.as_bytes();
while i < bytes.len() {
match bytes[i] {
b'0' => result = result << 1,
b'+' => result = (result << 1) + 1,
b'-' => result = (result << 1) - 1,
_ => panic!("Work with 0, +, and - only"),
}
i += 1;
}
result
}
pub fn to_decimal_integral_safe(csd: &str) -> CsdResult<(i32, usize)> {
let mut decimal_value: i32 = 0;
let mut prev_was_nonzero = false;
for (pos, digit) in csd.chars().enumerate() {
let is_nonzero = matches!(digit, '+' | '-');
if prev_was_nonzero && is_nonzero {
return Err(CsdError::ConsecutiveNonZero(pos));
}
match digit {
'0' => decimal_value <<= 1,
'+' => decimal_value = (decimal_value << 1) + 1,
'-' => decimal_value = (decimal_value << 1) - 1,
'.' => {
return Ok((decimal_value, pos + 1));
}
_ => return Err(CsdError::InvalidCharacter(digit, pos)),
}
prev_was_nonzero = is_nonzero;
}
Ok((decimal_value, 0))
}
#[must_use]
pub fn to_decimal_fractional(csd: &str) -> f64 {
let mut decimal_value = 0.0;
let mut scale = 0.5;
for digit in csd.chars() {
match digit {
'0' => {}
'+' => decimal_value += scale,
'-' => decimal_value -= scale,
_ => panic!("Fractional part works with 0, +, and - only"),
}
scale /= 2.0;
}
decimal_value
}
pub fn to_decimal_fractional_safe(csd: &str) -> CsdResult<f64> {
if csd.is_empty() {
return Ok(0.0);
}
let mut decimal_value = 0.0;
let mut scale = 0.5;
for (pos, digit) in csd.chars().enumerate() {
match digit {
'0' => {}
'+' => decimal_value += scale,
'-' => decimal_value -= scale,
_ => return Err(CsdError::InvalidCharacter(digit, pos)),
}
scale /= 2.0;
}
Ok(decimal_value)
}
#[cfg_attr(docsrs, doc = svgbobdoc::transform!(
/// Convert the CSD (Canonical Signed Digit) to a decimal
///
/// The `to_decimal` function converts a CSD (Canonical Signed Digit) string to a decimal number.
/// This function handles both integral and fractional parts of the CSD representation.
///
/// ```svgbob
/// CSD: "+00-00.+"
/// │││ ││ ││
/// │││ ││ │└─ fractional: + (0.5)
/// │││ ││ └── decimal point
/// │││ │└──── integer: 1s place - (-1)
/// │││ └───── integer: 2s place 0 (0)
/// ││└─────── integer: 4s place 0 (0)
/// │└──────── integer: 8s place + (8)
/// └───────── integer: 16s place + (16)
/// │
/// ▼
/// Calculation: 16 + 0 + 0 + (-8) + 0 + 0.5 = 8.5
/// ```
///
/// Arguments:
///
/// * `csd`: The `csd` parameter is a string representing a Canonical Signed Digit (CSD) number.
///
/// Returns:
///
/// The function `to_decimal` returns a decimal number (f64) that is converted from the input CSD
/// (Canonical Signed Digit) string.
///
/// # Panics
///
/// Panics if unexpected character is encountered
///
/// # Examples
///
/// ```
/// use csd::csd::to_decimal;
///
/// assert_eq!(to_decimal("+00-00.+"), 28.5);
/// assert_eq!(to_decimal("0.-"), -0.5);
/// assert_eq!(to_decimal("0"), 0.0);
/// assert_eq!(to_decimal("0.0"), 0.0);
/// assert_eq!(to_decimal("0.+"), 0.5);
/// assert_eq!(to_decimal("0.-"), -0.5);
/// assert_eq!(to_decimal("0.++"), 0.75);
/// assert_eq!(to_decimal("0.-+"), -0.25);
/// ```
))]
#[must_use]
pub fn to_decimal(csd: &str) -> f64 {
to_decimal_safe(csd).unwrap()
}
pub fn to_decimal_safe(csd: &str) -> CsdResult<f64> {
if csd.is_empty() {
return Err(CsdError::EmptyString);
}
let (integral, loc) = to_decimal_integral_safe(csd)?;
if loc == 0 {
return Ok(f64::from(integral));
}
let fractional = to_decimal_fractional_safe(&csd[loc..])?;
Ok(f64::from(integral) + fractional)
}
pub fn to_decimal_result(csd: &str) -> CsdResult<f64> {
for (i, c) in csd.chars().enumerate() {
if !matches!(c, '+' | '-' | '0' | '.') {
return Err(CsdError::InvalidCharacter(c, 0));
}
if c == '.' && csd.chars().skip(i + 1).any(|c| c == '.') {
return Err(CsdError::InvalidFormat(
"Multiple decimal points".to_string(),
));
}
}
to_decimal_safe(csd)
}
pub fn to_decimal_i_result(csd: &str) -> CsdResult<i32> {
for c in csd.chars() {
if !matches!(c, '+' | '-' | '0') {
return Err(CsdError::InvalidCharacter(c, 0));
}
}
Ok(to_decimal_i(csd))
}
pub fn to_decimal_i64_result(csd: &str) -> CsdResult<i64> {
for c in csd.chars() {
if !matches!(c, '+' | '-' | '0') {
return Err(CsdError::InvalidCharacter(c, 0));
}
}
Ok(to_decimal_i(csd) as i64)
}
pub fn to_decimal_i128_result(csd: &str) -> CsdResult<i128> {
for c in csd.chars() {
if !matches!(c, '+' | '-' | '0') {
return Err(CsdError::InvalidCharacter(c, 0));
}
}
Ok(to_decimal_i(csd) as i128)
}
#[cfg_attr(docsrs, doc = svgbobdoc::transform!(
/// Convert to CSD representation approximately with fixed number of non-zero
///
/// The `to_csdnnz` function converts a given number into a CSD (Canonic Signed Digit) representation
/// approximately with a specified number of non-zero digits. This version limits the number of
/// non-zero digits in the output representation.
///
/// ```svgbob
/// Input: 28.5 with nnz=4 (max 4 non-zero digits)
/// │
/// ▼
/// Algorithm: Process bit by bit, count non-zeros
/// │
/// ▼
/// Result: "+00-00.+" (has 4 non-zero digits: +, -, +, +)
/// │ ││ ││
/// │ ││ │└─ fractional: + (0.5)
/// │ ││ └── decimal point
/// │ │└──── integer: - (-8)
/// │ └───── integer: 0 (0)
/// └──────── integer: + (+16)
/// │
/// ▼
/// With nnz=2: "+00-00" (stops after 2 non-zeros)
/// ```
///
/// Arguments:
///
/// * `decimal_value`: The `decimal_value` parameter is a double precision floating-point number that represents the input
/// value for conversion to CSD (Canonic Signed Digit) fixed-point representation.
/// * `nnz`: The parameter `nnz` stands for "number of non-zero bits". It represents the maximum number
/// of non-zero bits allowed in the output CSD (Canonical Signed Digit) representation of the given
/// `decimal_value`.
///
/// Returns:
///
/// The function `to_csdnnz` returns a string representation of the given `decimal_value` in Canonical Signed
/// Digit (CSD) format.
///
/// # Examples
///
/// ```
/// use csd::csd::to_csdnnz;
///
/// let s1 = to_csdnnz(28.5, 4);
/// let s2 = to_csdnnz(-0.5, 4);
///
/// assert_eq!(to_csdnnz(28.5, 4), "+00-00.+".to_string());
/// assert_eq!(to_csdnnz(-0.5, 4), "0.-".to_string());
/// assert_eq!(to_csdnnz(0.0, 4), "0".to_string());
/// assert_eq!(to_csdnnz(0.0, 0), "0".to_string());
/// assert_eq!(to_csdnnz(0.5, 4), "0.+".to_string());
/// assert_eq!(to_csdnnz(-0.5, 4), "0.-".to_string());
/// assert_eq!(to_csdnnz(28.5, 2), "+00-00".to_string());
/// assert_eq!(to_csdnnz(28.5, 1), "+00000".to_string());
/// ```
))]
#[allow(dead_code)]
#[must_use]
pub fn to_csdnnz(decimal_value: f64, nnz: u32) -> String {
let absnum = decimal_value.abs();
let (mut rem, mut csd) = if absnum < 1.0 {
(0, "0".to_string())
} else {
#[allow(clippy::cast_possible_truncation)]
let rem = (absnum * 1.5).log2().ceil() as i32;
(rem, String::new())
};
let mut p2n = 2.0_f64.powi(rem);
let mut decimal_value = decimal_value;
let mut nnz = nnz;
while rem > 0 || (nnz > 0 && decimal_value.abs() > 1e-100) {
if rem == 0 {
csd.push('.');
}
p2n /= 2.0;
rem -= 1;
let det = 1.5 * decimal_value;
if nnz > 0 && det > p2n {
csd.push('+');
decimal_value -= p2n;
nnz -= 1;
} else if nnz > 0 && det < -p2n {
csd.push('-');
decimal_value += p2n;
nnz -= 1;
} else {
csd.push('0');
}
if nnz == 0 && rem < 0 {
break;
}
}
csd
}
pub fn to_csdnnz_safe(decimal_value: f64, nnz: u32) -> CsdResult<String> {
if nnz == 0 && decimal_value != 0.0 {
return Err(CsdError::InvalidFormat(
"Cannot represent non-zero value with 0 non-zero digits".to_string(),
));
}
let absnum = decimal_value.abs();
let (mut rem, mut csd) = if absnum < 1.0 {
(0, "0".to_string())
} else {
#[allow(clippy::cast_possible_truncation)]
let rem = (absnum * 1.5).log2().ceil() as i32;
(rem, String::new())
};
let mut p2n = 2.0_f64.powi(rem);
let mut decimal_value = decimal_value;
let mut nnz = nnz;
while rem > 0 || (nnz > 0 && decimal_value.abs() > 1e-100) {
if rem == 0 {
csd.push('.');
}
p2n /= 2.0;
rem -= 1;
let det = 1.5 * decimal_value;
if nnz > 0 && det > p2n {
csd.push('+');
decimal_value -= p2n;
nnz -= 1;
} else if nnz > 0 && det < -p2n {
csd.push('-');
decimal_value += p2n;
nnz -= 1;
} else {
csd.push('0');
}
if nnz == 0 && rem < 0 {
break;
}
}
Ok(csd)
}
#[must_use]
pub fn to_csdnnz_i64(decimal_value: i64, nnz: u32) -> String {
if decimal_value == 0 {
return "0".to_string();
}
#[allow(clippy::cast_possible_truncation)]
let temp = (decimal_value.abs() * 3 / 2) as u64;
#[allow(clippy::cast_possible_wrap)]
let mut p2n = highest_power_of_two_in(temp as u32) as i64 * 2;
let mut csd = String::with_capacity(64);
let mut decimal_value = decimal_value;
let mut nnz = nnz;
while p2n > 1 {
p2n >>= 1;
let p2n_half = p2n;
let det = 3 * decimal_value;
if det > p2n {
csd.push('+');
decimal_value -= p2n_half;
nnz -= 1;
} else if det < -p2n {
csd.push('-');
decimal_value += p2n_half;
nnz -= 1;
} else {
csd.push('0');
}
if nnz == 0 {
while p2n > 1 {
csd.push('0');
p2n >>= 1;
}
break;
}
}
csd
}
#[must_use]
pub fn to_csdnnz_i128(decimal_value: i128, nnz: u32) -> String {
if decimal_value == 0 {
return "0".to_string();
}
#[allow(clippy::cast_possible_truncation)]
let temp = (decimal_value.abs() * 3 / 2) as u128;
let mut highest_bit = 0u32;
let mut temp_mut = temp;
while temp_mut > 0 {
temp_mut >>= 1;
highest_bit += 1;
}
let mut p2n = if highest_bit > 0 {
1i128 << highest_bit
} else {
0i128
};
let mut csd = String::with_capacity(128);
let mut decimal_value = decimal_value;
let mut nnz = nnz;
while p2n > 1 {
p2n >>= 1;
let p2n_half = p2n;
let det = 3 * decimal_value;
if det > p2n {
csd.push('+');
decimal_value -= p2n_half;
nnz -= 1;
} else if det < -p2n {
csd.push('-');
decimal_value += p2n_half;
nnz -= 1;
} else {
csd.push('0');
}
if nnz == 0 {
while p2n > 1 {
csd.push('0');
p2n >>= 1;
}
break;
}
}
csd
}
#[cfg(test)]
mod tests {
use super::*;
use quickcheck_macros::quickcheck;
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
#[test]
fn test_to_csd() {
assert_eq!(to_csd(28.5, 2), "+00-00.+0".to_string());
assert_eq!(to_csd(-0.5, 2), "0.-0".to_string());
assert_eq!(to_csd(0.0, 2), "0.00".to_string());
assert_eq!(to_csd(0.0, 0), "0.".to_string());
assert_eq!(to_csd(2.5, 4), "+0.+000".to_string());
}
#[test]
#[should_panic]
fn test_to_decimal_invalid1() {
let _res = to_decimal("+00XXX-00.00+");
}
#[test]
#[should_panic]
fn test_to_decimal_invalid2() {
let _res = to_decimal("+00-00.0XXX0+");
}
#[test]
fn test_to_decimal_i() {
assert_eq!(to_decimal_i("+00-00"), 28);
assert_eq!(to_decimal_i("0"), 0);
}
#[test]
fn test_to_decimal_i_safe_empty_string() {
let result = to_decimal_i_safe("");
assert!(result.is_err());
assert_eq!(result.unwrap_err(), CsdError::EmptyString);
}
#[test]
fn test_to_decimal_i_safe_invalid_character() {
let result = to_decimal_i_safe("+00X00");
assert!(result.is_err());
if let CsdError::InvalidCharacter(c, pos) = result.unwrap_err() {
assert_eq!(c, 'X');
assert_eq!(pos, 3);
} else {
panic!("Expected InvalidCharacter error");
}
}
#[test]
fn test_to_decimal_i_safe_consecutive_nonzero() {
let result = to_decimal_i_safe("++00");
assert!(result.is_err());
assert_eq!(result.unwrap_err(), CsdError::ConsecutiveNonZero(1));
}
#[test]
#[should_panic]
fn test_to_decimal_i_invalid() {
let _res = to_decimal_i("+00-00.00+");
}
#[test]
fn test_to_csdnnz() {
let result = to_csdnnz(28.5, 4);
let nnz_count = result.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count <= 4);
assert_eq!(to_csdnnz(-0.5, 4), "0.-".to_string());
assert_eq!(to_csdnnz(0.0, 4), "0".to_string());
assert_eq!(to_csdnnz(0.0, 0), "0".to_string());
assert_eq!(to_csdnnz(0.5, 4), "0.+".to_string());
assert_eq!(to_csdnnz(-0.5, 4), "0.-".to_string());
let result = to_csdnnz(28.5, 1);
let nnz_count = result.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count <= 1);
}
#[test]
fn test_to_csdnnz_i() {
let csd = to_csdnnz_i(28, 4);
let nnz_count = csd.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count <= 4);
assert_eq!(to_csdnnz_i(-0, 4), "0".to_string());
assert_eq!(to_csdnnz_i(0, 4), "0".to_string());
assert_eq!(to_csdnnz_i(0, 0), "0".to_string());
let csd2 = to_csdnnz_i(158, 2);
let nnz_count = csd2.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count <= 2);
}
#[quickcheck]
fn test_csd_roundtrip(d: i32) -> bool {
let d = if d == i32::MIN { 0 } else { d };
let f = d as f64 / 8.0;
let places = (d.abs() % 10 + 2).max(2);
let csd = to_csd(f, places);
let recovered = to_decimal(&csd);
(f - recovered).abs() < 1e-10
}
#[quickcheck]
fn test_csd_i_roundtrip(d: i32) -> bool {
let d = d / 3;
let csd = to_csd_i(d);
d == to_decimal_i(&csd)
}
#[quickcheck]
fn test_safe_decimal_i(csd_chars: Vec<char>) -> bool {
let csd: String = csd_chars
.into_iter()
.filter(|&c| matches!(c, '0' | '+' | '-'))
.collect();
if csd.is_empty() {
return true;
}
match to_decimal_i_safe(&csd) {
Ok(_) => true,
Err(CsdError::ConsecutiveNonZero(_)) => csd.chars().enumerate().any(|(i, c)| {
if matches!(c, '+' | '-') {
i > 0 && matches!(csd.chars().nth(i - 1), Some('+' | '-'))
} else {
false
}
}),
_ => false,
}
}
#[quickcheck]
fn test_safe_decimal(csd_chars: Vec<char>) -> bool {
let csd: String = csd_chars
.into_iter()
.filter(|&c| matches!(c, '0' | '+' | '-' | '.'))
.collect();
if csd.is_empty() {
return true;
}
match to_decimal_safe(&csd) {
Ok(_) => {
csd.matches('.').count() <= 1
}
Err(CsdError::EmptyString) => csd.is_empty(),
Err(CsdError::InvalidCharacter(_, _)) => {
true
}
Err(CsdError::InvalidFormat(_)) => {
true
}
Err(CsdError::ConsecutiveNonZero(_)) => {
true
}
Err(_) => true, }
}
#[quickcheck]
fn test_csdnnz_limits(d: i32) -> bool {
let d = if d == i32::MIN { 0 } else { d } / 3;
let max_nnz = (d.abs() % 10 + 1).max(1) as u32;
let csd = to_csdnnz(d as f64, max_nnz);
let actual_nnz = csd.chars().filter(|&c| c == '+' || c == '-').count();
actual_nnz <= max_nnz as usize
}
#[quickcheck]
fn test_power_of_two_property(x: u32) -> bool {
let result = highest_power_of_two_in(x);
if x == 0 {
result == 0
} else {
result <= x
&& result.is_power_of_two()
&& (result == x || result.checked_mul(2).is_none_or(|v| v > x))
}
}
#[test]
fn test_highest_power_of_two_in() {
assert_eq!(highest_power_of_two_in(14), 8);
assert_eq!(highest_power_of_two_in(8), 8);
assert_eq!(highest_power_of_two_in(1), 1);
assert_eq!(highest_power_of_two_in(0), 0);
assert_eq!(highest_power_of_two_in(3), 2);
assert_eq!(highest_power_of_two_in(2), 2);
assert_eq!(highest_power_of_two_in(u32::MAX), 2147483648);
}
#[test]
fn test_to_csd_i64() {
let csd = to_csd_i64(28);
assert_eq!(to_decimal_i64(&csd), 28);
assert_eq!(to_csd_i64(0), "0".to_string());
let csd2 = to_csd_i64(-28);
assert_eq!(to_decimal_i64(&csd2), -28);
}
#[test]
fn test_to_decimal_i64() {
assert_eq!(to_decimal_i64("+00-00"), 28i64);
assert_eq!(to_decimal_i64("0"), 0i64);
assert_eq!(to_decimal_i64("-00+00"), -28i64);
}
#[test]
fn test_to_csdnnz_i64() {
let csd = to_csdnnz_i64(28, 4);
let nnz_count = csd.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count <= 4);
assert_eq!(to_csdnnz_i64(0, 4), "0".to_string());
let csd2 = to_csdnnz_i64(158, 2);
let nnz_count = csd2.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count <= 2);
}
#[test]
fn test_to_csd_i128() {
let csd = to_csd_i128(28);
assert_eq!(to_decimal_i128(&csd), 28);
assert_eq!(to_csd_i128(0), "0".to_string());
let csd2 = to_csd_i128(-28);
assert_eq!(to_decimal_i128(&csd2), -28);
}
#[test]
fn test_to_csdnnz_i128() {
let csd = to_csdnnz_i128(28, 4);
let nnz_count = csd.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count <= 4);
assert_eq!(to_csdnnz_i128(0, 4), "0".to_string());
let csd2 = to_csdnnz_i128(158, 2);
let nnz_count = csd2.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count <= 2);
}
#[test]
fn test_to_decimal_result() {
assert_eq!(to_decimal_result("+00-00.+").unwrap(), 28.5);
assert_eq!(to_decimal_result("0").unwrap(), 0.0);
assert!(to_decimal_result("+00X-00").is_err());
assert_eq!(
to_decimal_result("+00X-00").unwrap_err(),
CsdError::InvalidCharacter('X', 0)
);
assert!(to_decimal_result("1.2.3").is_err());
}
#[must_use]
pub const fn to_decimal_i64(csd: &str) -> i64 {
let mut result = 0i64;
let mut i = 0;
let bytes = csd.as_bytes();
while i < bytes.len() {
match bytes[i] {
b'0' => result = result << 1,
b'+' => result = (result << 1) + 1,
b'-' => result = (result << 1) - 1,
_ => panic!("Work with 0, +, and - only"),
}
i += 1;
}
result
}
#[must_use]
pub const fn to_decimal_i128(csd: &str) -> i128 {
let mut result = 0i128;
let mut i = 0;
let bytes = csd.as_bytes();
while i < bytes.len() {
match bytes[i] {
b'0' => result = result << 1,
b'+' => result = (result << 1) + 1,
b'-' => result = (result << 1) - 1,
_ => panic!("Work with 0, +, and - only"),
}
i += 1;
}
result
}
#[allow(dead_code)]
#[must_use]
pub fn to_csd_i64(decimal_value: i64) -> String {
if decimal_value == 0 {
return "0".to_string();
}
#[allow(clippy::cast_sign_loss)]
let temp = (decimal_value.abs() * 3 / 2) as u64;
#[allow(clippy::cast_possible_wrap)]
let mut p2n = highest_power_of_two_in(temp as u32) as i64 * 2;
let mut csd = Vec::with_capacity(64);
let mut decimal_value = decimal_value;
while p2n > 1 {
let p2n_half = p2n >> 1;
let det = 3 * decimal_value;
if det > p2n {
csd.push(b'+');
decimal_value -= p2n_half;
} else if det < -p2n {
csd.push(b'-');
decimal_value += p2n_half;
} else {
csd.push(b'0');
}
p2n = p2n_half;
}
String::from_utf8(csd).unwrap()
}
#[allow(dead_code)]
#[must_use]
pub fn to_csd_i128(decimal_value: i128) -> String {
if decimal_value == 0 {
return "0".to_string();
}
#[allow(clippy::cast_sign_loss)]
let temp = (decimal_value.abs() * 3 / 2) as u128;
#[allow(clippy::cast_possible_wrap)]
let mut p2n = highest_power_of_two_in(temp as u32) as i128 * 2;
let mut csd = Vec::with_capacity(128);
let mut decimal_value = decimal_value;
while p2n > 1 {
let p2n_half = p2n >> 1;
let det = 3 * decimal_value;
if det > p2n {
csd.push(b'+');
decimal_value -= p2n_half;
} else if det < -p2n {
csd.push(b'-');
decimal_value += p2n_half;
} else {
csd.push(b'0');
}
p2n = p2n_half;
}
String::from_utf8(csd).unwrap()
}
#[allow(dead_code)]
#[must_use]
pub fn to_csdnnz_i(decimal_value: i32, nnz: u32) -> String {
to_csdnnz(decimal_value as f64, nnz)
}
#[allow(dead_code)]
#[must_use]
pub fn to_csdnnz_i64(decimal_value: i64, nnz: u32) -> String {
to_csdnnz(decimal_value as f64, nnz)
}
#[allow(dead_code)]
#[must_use]
pub fn to_csdnnz_i128(decimal_value: i128, nnz: u32) -> String {
to_csdnnz(decimal_value as f64, nnz)
}
#[test]
fn test_csd_builder_new() {
let builder = CsdBuilder::new(28.5);
assert_eq!(builder.value, 28.5);
assert_eq!(builder.places, None);
assert_eq!(builder.max_non_zeros, None);
}
#[test]
fn test_csd_builder_places() {
let builder = CsdBuilder::new(28.5).places(4);
assert_eq!(builder.places, Some(4));
let builder = CsdBuilder::new(28.5).places(-5);
assert_eq!(builder.places, Some(0));
}
#[test]
fn test_csd_builder_max_non_zeros() {
let builder = CsdBuilder::new(28.5).max_non_zeros(3);
assert_eq!(builder.max_non_zeros, Some(3));
}
#[test]
fn test_csd_builder_rounding_strategy() {
let builder = CsdBuilder::new(28.5).rounding_strategy(RoundingStrategy::Nearest);
assert_eq!(builder.value, 28.5);
}
#[test]
fn test_csd_builder_rounding_strategy_down() {
let builder = CsdBuilder::new(28.5).rounding_strategy(RoundingStrategy::Down);
assert_eq!(builder.value, 28.5);
}
#[test]
fn test_csd_builder_rounding_strategy_up() {
let builder = CsdBuilder::new(28.5).rounding_strategy(RoundingStrategy::Up);
assert_eq!(builder.value, 28.5);
}
#[test]
fn test_csd_builder_build_simple() {
let csd = CsdBuilder::new(28.5).places(4).build().unwrap();
assert_eq!(csd, "+00-00.+000");
}
#[test]
fn test_csd_builder_build_with_max_non_zeros() {
let csd = CsdBuilder::new(28.5).max_non_zeros(3).build().unwrap();
let nnz_count = csd.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count <= 3);
}
#[test]
fn test_csd_builder_build_zero_value() {
let csd = CsdBuilder::new(0.0).places(4).build().unwrap();
assert_eq!(csd, "0.0000");
}
#[test]
fn test_csd_builder_build_zero_with_max_non_zeros() {
let csd = CsdBuilder::new(0.0).max_non_zeros(3).build().unwrap();
assert_eq!(csd, "0");
}
#[test]
fn test_csd_builder_build_nonzero_with_zero_max_non_zeros() {
let result = CsdBuilder::new(28.5).max_non_zeros(0).build();
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
CsdError::InvalidFormat(
"Cannot represent non-zero value with 0 non-zero digits".to_string()
)
);
}
#[test]
fn test_csd_builder_build_negative_places() {
let result = CsdBuilder::new(28.5).places(-5).build();
assert!(result.is_ok());
}
#[test]
fn test_csd_error_display_invalid_character() {
let err = CsdError::InvalidCharacter('X', 5);
assert_eq!(
format!("{}", err),
"Invalid character 'X' at position 5 in CSD string"
);
}
#[test]
fn test_csd_error_display_invalid_format() {
let err = CsdError::InvalidFormat("Multiple decimal points".to_string());
assert_eq!(
format!("{}", err),
"Invalid CSD format: Multiple decimal points"
);
}
#[test]
fn test_csd_error_display_overflow() {
let err = CsdError::Overflow {
input: 1e308,
max_bits: 32,
};
let msg = format!("{}", err);
assert!(msg.contains("Overflow"));
assert!(msg.contains("32 bits"));
}
#[test]
fn test_csd_error_display_precision_loss() {
let err = CsdError::PrecisionLoss {
input: 1.234_567_890_123_456_7,
actual: 1.234_567_890_123_456_7,
};
let msg = format!("{}", err);
assert!(msg.contains("Precision loss"));
}
#[test]
fn test_csd_error_display_consecutive_non_zero() {
let err = CsdError::ConsecutiveNonZero(3);
assert_eq!(
format!("{}", err),
"Consecutive non-zero digits at position 3"
);
}
#[test]
fn test_csd_error_display_empty_string() {
let err = CsdError::EmptyString;
assert_eq!(format!("{}", err), "Empty string provided");
}
#[test]
fn test_is_power_of_two() {
assert!(is_power_of_two(1));
assert!(is_power_of_two(2));
assert!(is_power_of_two(4));
assert!(is_power_of_two(8));
assert!(is_power_of_two(16));
assert!(is_power_of_two(1024));
assert!(is_power_of_two(2147483648));
assert!(!is_power_of_two(0));
assert!(!is_power_of_two(3));
assert!(!is_power_of_two(5));
assert!(!is_power_of_two(6));
assert!(!is_power_of_two(7));
assert!(!is_power_of_two(9));
assert!(!is_power_of_two(15));
assert!(!is_power_of_two(u32::MAX));
}
#[test]
fn test_count_non_zero_digits() {
assert_eq!(count_non_zero_digits("0"), 0);
assert_eq!(count_non_zero_digits("000"), 0);
assert_eq!(count_non_zero_digits("+"), 1);
assert_eq!(count_non_zero_digits("-"), 1);
assert_eq!(count_non_zero_digits("+00-00"), 2);
assert_eq!(count_non_zero_digits("+-+-+-"), 6);
assert_eq!(count_non_zero_digits("+00-00.+"), 3);
assert_eq!(count_non_zero_digits("0.00"), 0);
assert_eq!(count_non_zero_digits("0.+0.-0"), 2);
}
#[test]
fn test_validate_csd_format() {
assert!(validate_csd_format("0"));
assert!(validate_csd_format("000"));
assert!(validate_csd_format("+"));
assert!(validate_csd_format("-"));
assert!(validate_csd_format("+00-00"));
assert!(validate_csd_format("0.+0"));
assert!(validate_csd_format("0.00"));
assert!(validate_csd_format("+0-0+"));
assert!(!validate_csd_format(""));
assert!(!validate_csd_format("++"));
assert!(!validate_csd_format("--"));
assert!(!validate_csd_format("+-"));
assert!(!validate_csd_format("-+"));
assert!(!validate_csd_format("0++0"));
assert!(!validate_csd_format("+00--00"));
assert!(!validate_csd_format("123"));
assert!(!validate_csd_format("abc"));
assert!(!validate_csd_format("+0X-0"));
assert!(!validate_csd_format("*"));
assert!(!validate_csd_format(" "));
}
#[test]
fn test_to_decimal_fractional() {
assert_eq!(to_decimal_fractional(""), 0.0);
assert_eq!(to_decimal_fractional("0"), 0.0);
assert_eq!(to_decimal_fractional("000"), 0.0);
assert_eq!(to_decimal_fractional("+"), 0.5);
assert_eq!(to_decimal_fractional("-"), -0.5);
assert_eq!(to_decimal_fractional("0+"), 0.25);
assert_eq!(to_decimal_fractional("0-"), -0.25);
assert_eq!(to_decimal_fractional("++"), 0.75);
assert_eq!(to_decimal_fractional("--"), -0.75);
assert_eq!(to_decimal_fractional("+-"), 0.25);
assert_eq!(to_decimal_fractional("-+"), -0.25);
assert!((to_decimal_fractional("0+0+0+0+0+0+0+0") - 0.33331298828125).abs() < 1e-10);
}
#[test]
#[should_panic]
fn test_to_decimal_fractional_invalid_char() {
let _ = to_decimal_fractional("+0X-0");
}
#[test]
fn test_to_decimal_i_result() {
assert_eq!(to_decimal_i_result("+00-00").unwrap(), 28);
assert_eq!(to_decimal_i_result("0").unwrap(), 0);
assert_eq!(to_decimal_i_result("-00+00").unwrap(), -28);
assert!(to_decimal_i_result("+00X-00").is_err());
assert_eq!(
to_decimal_i_result("+00X-00").unwrap_err(),
CsdError::InvalidCharacter('X', 0)
);
assert!(to_decimal_i_result("123").is_err());
assert!(to_decimal_i_result("abc").is_err());
}
#[test]
fn test_to_decimal_i64_result() {
assert_eq!(to_decimal_i64_result("+00-00").unwrap(), 28i64);
assert_eq!(to_decimal_i64_result("0").unwrap(), 0i64);
assert_eq!(to_decimal_i64_result("-00+00").unwrap(), -28i64);
assert!(to_decimal_i64_result("+00X-00").is_err());
assert_eq!(
to_decimal_i64_result("+00X-00").unwrap_err(),
CsdError::InvalidCharacter('X', 0)
);
}
#[test]
fn test_to_decimal_i128_result() {
assert_eq!(to_decimal_i128_result("+00-00").unwrap(), 28i128);
assert_eq!(to_decimal_i128_result("0").unwrap(), 0i128);
assert_eq!(to_decimal_i128_result("-00+00").unwrap(), -28i128);
assert!(to_decimal_i128_result("+00X-00").is_err());
assert_eq!(
to_decimal_i128_result("+00X-00").unwrap_err(),
CsdError::InvalidCharacter('X', 0)
);
}
#[test]
fn test_to_csdnnz_safe() {
let result = to_csdnnz_safe(28.5, 4).unwrap();
let nnz_count = result.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count <= 4);
assert_eq!(to_csdnnz_safe(-0.5, 4).unwrap(), "0.-");
assert_eq!(to_csdnnz_safe(0.0, 4).unwrap(), "0");
assert_eq!(to_csdnnz_safe(0.0, 0).unwrap(), "0");
assert_eq!(to_csdnnz_safe(0.5, 4).unwrap(), "0.+");
let result = to_csdnnz_safe(28.5, 0);
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
CsdError::InvalidFormat(
"Cannot represent non-zero value with 0 non-zero digits".to_string()
)
);
}
#[test]
fn test_to_csdnnz_i64_explicit() {
let csd = to_csdnnz_i64(28, 4);
let nnz_count = csd.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count <= 4);
assert_eq!(to_csdnnz_i64(0, 4), "0");
assert_eq!(to_csdnnz_i64(0, 0), "0");
let csd2 = to_csdnnz_i64(158, 2);
let nnz_count = csd2.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count <= 2);
let csd3 = to_csdnnz_i64(-28, 4);
let nnz_count3 = csd3.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count3 <= 4);
let csd4 = to_csdnnz_i64(1000000, 5);
let nnz_count4 = csd4.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count4 <= 5);
}
#[test]
fn test_to_csdnnz_i128_explicit() {
let csd = to_csdnnz_i128(28, 4);
let nnz_count = csd.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count <= 4);
assert_eq!(to_csdnnz_i128(0, 4), "0");
assert_eq!(to_csdnnz_i128(0, 0), "0");
let csd2 = to_csdnnz_i128(158, 2);
let nnz_count = csd2.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count <= 2);
let csd3 = to_csdnnz_i128(-28, 4);
let nnz_count3 = csd3.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count3 <= 4);
let csd4 = to_csdnnz_i128(1000000000000i128, 5);
let nnz_count4 = csd4.chars().filter(|c| *c == '+' || *c == '-').count();
assert!(nnz_count4 <= 5);
}
#[test]
fn test_to_decimal_result_multiple_decimal_points() {
let result = to_decimal_result("+.0.");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(matches!(
err,
CsdError::InvalidCharacter(_, _) | CsdError::InvalidFormat(_)
));
}
#[test]
fn test_to_decimal_result_empty_string() {
let result = to_decimal_result("");
assert!(result.is_err());
assert_eq!(result.unwrap_err(), CsdError::EmptyString);
}
#[test]
fn test_to_decimal_result_consecutive_non_zero() {
let result = to_decimal_result("++");
assert!(result.is_err()); }
}