Source code for csdigit.csd

"""
Canonical Signed Digit (CSD) conversion utilities.

Provides functions to convert between decimal numbers and Canonical Signed Digit
(CSD) representation — a signed-digit representation using only ``+``, ``-``,
and ``0`` symbols with no consecutive non-zero digits.

Functions:
    to_csd: Convert a decimal float to CSD string with specified precision.
    to_csd_i: Convert an integer to CSD string.
    to_decimal: Convert a CSD string to a decimal number.
    to_decimal_using_pow: Convert a CSD string to decimal using pow() (deprecated).
    to_csdnnz: Convert a decimal float to CSD with limited non-zero digits.
    to_csdnnz_i: Convert an integer to CSD with limited non-zero digits.
"""

import logging
from math import fabs, frexp, ldexp


[docs] def to_csd(decimal_value: float, places: int) -> str: """ The `to_csd` function converts a given decimal number to its Canonical Signed Digit (CSD) representation with a specified number of decimal places. Original author: Harnesser <https://sourceforge.net/projects/pycsd/> License: GPL2 :param 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 :type decimal_value: float :param places: The `places` parameter in the `to_csd` function represents the number of decimal places to include in the CSD (Canonical Signed Digit) representation of the given `decimal_value` :type places: int :return: The function `to_csd` returns a string representation of the given `decimal_value` in Canonical Signed Digit (CSD) format. Examples: >>> to_csd(28.5, 2) '+00-00.+0' >>> to_csd(-0.5, 2) '0.-0' >>> to_csd(0.0, 2) '0.00' >>> to_csd(0.0, 0) '0.' """ if decimal_value == 0.0: return "0." + "0" * places if places > 0 else "0." abs_val = fabs(decimal_value) if abs_val < 1.0: remainder = 0 csd_list = ["0"] else: mant, exp = frexp(abs_val * 1.5) remainder = exp - 1 if mant == 0.5 else exp csd_list = [] power_of_two = ldexp(1.0, remainder) for _ in range(remainder): power_of_two /= 2.0 determinant = 1.5 * decimal_value if determinant > power_of_two: csd_list.append("+") decimal_value -= power_of_two elif determinant < -power_of_two: csd_list.append("-") decimal_value += power_of_two logging.debug(f"decimal_value = {decimal_value}") else: csd_list.append("0") csd_list.append(".") for _ in range(places): power_of_two /= 2.0 determinant = 1.5 * decimal_value if determinant > power_of_two: csd_list.append("+") decimal_value -= power_of_two elif determinant < -power_of_two: csd_list.append("-") decimal_value += power_of_two else: csd_list.append("0") return "".join(csd_list)
[docs] def to_csd_i(decimal_value: int) -> str: """ The `to_csd_i` function converts a given integer into a Canonical Signed Digit (CSD) representation. Original author: Harnesser <https://sourceforge.net/projects/pycsd/> License: GPL2 :param decimal_value: The `decimal_value` parameter is an integer that represents the decimal value to be converted to CSD format :type decimal_value: int :return: The function `to_csd_i` returns a string containing the CSD (Canonical Signed Digit) value. Examples: >>> to_csd_i(28) '+00-00' >>> to_csd_i(-0) '0' >>> to_csd_i(0) '0' """ if decimal_value == 0: return "0" remainder = (abs(decimal_value) * 3 // 2).bit_length() power_of_two = 1 << remainder csd_list = [] while power_of_two > 1: power_of_two_half = power_of_two >> 1 determinant = 3 * decimal_value if determinant > power_of_two: csd_list.append("+") decimal_value -= power_of_two_half elif determinant < -power_of_two: csd_list.append("-") decimal_value += power_of_two_half else: csd_list.append("0") power_of_two = power_of_two_half return "".join(csd_list)
[docs] def to_decimal_using_pow(csd: str) -> float: """ The `to_decimal_using_pow` function converts a Canonical Signed Digit (CSD) string to a decimal number using the pow function. .. deprecated:: 0.1.0 Use `to_decimal` instead. Original author: Harnesser <https://sourceforge.net/projects/pycsd/> License: GPL2 :param csd: The `csd` parameter is a string containing the CSD (Canonical Signed Digit) value :type csd: str Examples: >>> to_decimal_using_pow("+00-00.+") 28.5 >>> to_decimal_using_pow("0.-") -0.5 >>> to_decimal_using_pow("0") 0.0 >>> to_decimal_using_pow("0.0") 0.0 >>> to_decimal_using_pow("0.+") 0.5 """ # import warnings # warnings.warn( # "`to_decimal_using_pow` is deprecated, use `to_decimal` instead.", # DeprecationWarning, # stacklevel=2, # ) decimal_value: float = 0.0 location: int = 0 # Tracks position of decimal point for pos, digit in enumerate(csd): if digit == "0": decimal_value *= 2.0 # Shift left (multiply by 2) elif digit == "+": decimal_value = decimal_value * 2.0 + 1.0 # Shift left and add 1 elif digit == "-": decimal_value = decimal_value * 2.0 - 1.0 # Shift left and subtract 1 elif digit == ".": location = pos + 1 # Mark decimal point position else: logging.info(f"Encounter unknown character {digit}") # raise ValueError(ERROR1) if location != 0: # Adjust for fractional part by dividing by appropriate power of 2 decimal_value /= pow(2.0, len(csd) - location) return decimal_value
[docs] def to_decimal(csd: str) -> float: """Convert CSD string to decimal number. Original author: Harnesser <https://sourceforge.net/projects/pycsd/> License: GPL2 :param csd: The `csd` parameter is a string containing the CSD (Canonical Signed Digit) value that we want to convert to a decimal number :type csd: str Examples: >>> to_decimal("+00-00.+") 28.5 >>> to_decimal("0.-") -0.5 >>> to_decimal("0") 0 >>> to_decimal("0.0") 0.0 >>> to_decimal("0.+") 0.5 """ if "." not in csd: integral: int = 0 for digit in csd: integral *= 2 if digit == "+": integral += 1 elif digit == "-": integral -= 1 elif digit != "0": logging.info(f"Encounter unknown character {digit}") # raise ValueError(ERROR1) return integral integral_part, fractional_part = csd.split(".", 1) integral: int = 0 for digit in integral_part: integral *= 2 if digit == "+": integral += 1 elif digit == "-": integral -= 1 elif digit != "0": logging.info(f"Encounter unknown character {digit}") # raise ValueError(ERROR1) fractional: float = 0.0 scale = 0.5 for digit in fractional_part: if digit == "+": fractional += scale elif digit == "-": fractional -= scale elif digit != "0": logging.info(f"Encounter unknown character {digit}") # raise ValueError(ERROR1) scale /= 2.0 return float(integral) + fractional
[docs] def to_csdnnz(decimal_value: float, nnz: int) -> str: """ The `to_csdnnz` function converts a given decimal number into a Canonical Signed Digit (CSD) representation with a specified number of non-zero digits. Original author: Harnesser <https://sourceforge.net/projects/pycsd/> License: GPL2 :param 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 :type decimal_value: float :param 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` :type nnz: int :return: The function `to_csdnnz` returns a string representation of the given `decimal_value` in Canonical Signed Digit (CSD) format. Examples: >>> to_csdnnz(28.5, 4) '+00-00.+' >>> to_csdnnz(-0.5, 4) '0.-' >>> to_csdnnz(0.0, 4) '0' >>> to_csdnnz(0.5, 4) '0.+' """ if decimal_value == 0.0: return "0" abs_val = fabs(decimal_value) if abs_val < 1.0: remainder = 0 csd_list = ["0"] else: mant, exp = frexp(abs_val * 1.5) remainder = exp - 1 if mant == 0.5 else exp csd_list = [] power_of_two = ldexp(1.0, remainder) while remainder > 0 or (nnz > 0 and fabs(decimal_value) > 1e-100): if remainder == 0: csd_list.append(".") power_of_two /= 2 remainder -= 1 if nnz > 0: determinant = 1.5 * decimal_value if determinant > power_of_two: csd_list.append("+") decimal_value -= power_of_two nnz -= 1 elif determinant < -power_of_two: csd_list.append("-") decimal_value += power_of_two nnz -= 1 else: csd_list.append("0") else: csd_list.append("0") # if nnz > 0 and determinant > power_of_two: # csd_list.append("+") # decimal_value -= power_of_two # nnz -= 1 # elif nnz > 0 and determinant < -power_of_two: # csd_list.append("-") # decimal_value += power_of_two # nnz -= 1 # else: # csd_list.append("0") return "".join(csd_list)
[docs] def to_csdnnz_i(decimal_value: int, nnz: int) -> str: """ The `to_csdnnz_i` function converts a given integer into a Canonical Signed Digit (CSD) representation with a specified number of non-zero digits. Original author: Harnesser <https://sourceforge.net/projects/pycsd/> License: GPL2 :param decimal_value: The `decimal_value` parameter is an integer that represents the decimal value to be converted to CSD format :type decimal_value: int :param 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` :type nnz: int :return: The function `to_csdnnz_i` returns a string representation of the given `decimal_value` in Canonical Signed Digit (CSD) format. Examples: >>> to_csdnnz_i(28, 4) '+00-00' >>> to_csdnnz_i(-0, 4) '0' >>> to_csdnnz_i(0, 4) '0' >>> to_csdnnz_i(37, 2) '+00+00' >>> to_csdnnz_i(158, 2) '+0+00000' """ if decimal_value == 0: return "0" remainder = (abs(decimal_value) * 3 // 2).bit_length() power_of_two = 1 << remainder csd_list = [] while power_of_two > 1: power_of_two_half = power_of_two >> 1 determinant = 3 * decimal_value if nnz > 0 and determinant > power_of_two: csd_list.append("+") decimal_value -= power_of_two_half nnz -= 1 elif nnz > 0 and determinant < -power_of_two: csd_list.append("-") decimal_value += power_of_two_half nnz -= 1 else: csd_list.append("0") power_of_two = power_of_two_half return "".join(csd_list)
if __name__ == "__main__": import doctest doctest.testmod()