Skip to content

Instantly share code, notes, and snippets.

@etiennecollin
Last active January 18, 2024 06:28
Show Gist options
  • Save etiennecollin/2554cb27cb44d83384b6731e7097835f to your computer and use it in GitHub Desktop.
Save etiennecollin/2554cb27cb44d83384b6731e7097835f to your computer and use it in GitHub Desktop.
Convert the encoding of a number from a BaseX to a BaseY encoding where x and y are each between [2, 36].
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Author: Etienne Collin
# Date: 2022/09/15
# Copyright: Copyright 2022, Etienne Collin
# Email: [email protected]
# License: GPL-3.0
# Version: 1.3.0
"""This program converts the base encoding of a number.
Through this program, the encoding of a number can be converted from a BaseX to a BaseY encoding where x and y are each
between [2, 36].
Typical usage example:
Run the script and use answer the terminal input prompts.
"""
def convertASCII(character: str = None, number: int = None):
"""Converts characters <--> integers using the ASCII table.
Converts an hexadecimal character to its decimal value or a number between 10 and 15 to its hexadecimal character
using the ASCII table. This function requires one of its arguments to be set to None.
Example:
Set the argument <character> to None and assign a value to the argument <number> in order to convert a number to
a character.
Args:
character (str, optional): The character to be converted. Defaults to None.
number (int, optional): The number to be converted. Defaults to None.
Returns either:
int: The decimal value of the input.
str: The hexadecimal char of the input.
"""
if (character is not None) and (number is None):
# Example:
# If <character> = "C", then ord("C")==67
# ===> ord("C") - ord("A") == 67 - 65 == 2
#############################################
try:
# +10 offsets values from [0, 5] to [10, 15].
number = ord(character) - ord("A") + 10
except TypeError:
print("\nERROR: The first argument should be a character in ASCII conversion function.\n")
exit()
return number
elif (character is None) and (number is not None):
# Example:
# If <number> = "12"
# ===> chr((<number> - 10) + ord("A")) == chr((12 - 10) + 65) == chr(67) == "C"
#############################################
try:
# -10 offsets values from [10, 15]. ord("A") is ASCII value 65
character = chr((number - 10) + ord("A"))
except TypeError:
print("\nERROR: The second argument should be an integer in ASCII conversion function.\n")
exit()
return character
else:
print("\nERROR: Exactly one argument should set to None in ASCII conversion function.\n")
def inputCheck(number: str, inputBase: int, outputBase: int = 10):
"""Checks user input for errors and illegal digits/characters.
Args:
number (str): Number to be converted.
inputBase (int): Base in which the input is encoded.
outputBase (int, optional): Base in which to encode the output. Defaults to 10.
"""
# Check that input base and output base are valid
if not (inputBase >= 2 and inputBase <= 36):
print("\nERROR: Input base encoding values are between [2, 36]\n")
exit()
elif not (outputBase >= 2 and outputBase <= 36) and (outputBase != -1 and outputBase != -2 and outputBase != -3):
print(
"\nERROR: Output base encoding values are between [2, 36].\
\nSpecial case for -1, -2 and -3 which output Two's Complement notation, IEEE754 Single Precision notation and IEEE754 Double Precision notation.\n"
)
exit()
if number == "" or number == ".":
print("\nERROR: Input number is empty\n")
exit()
# Check that each digit in input number is valid for specified base
index = 0
letterPresent = False
for digit in number:
if (digit >= "0" and digit <= str(min((inputBase - 1), 9))) or (
digit >= "A" and digit <= convertASCII(None, inputBase - 1) and inputBase > 10
):
pass
elif inputBase == 10 and digit == "E" and letterPresent == False:
# Check that only one E is present in a Base10 number to specify scientific notation.
letterPresent = True
pass
elif digit == "-" and index == 0:
pass
elif digit == ".":
pass
else:
print("\nERROR: Illegal character/digit in input number according to specified input base.\n")
exit()
index += 1
def processNumber(number: str, inputBase: int):
"""Split integer and decimal part of number, and gives its sign.
Args:
number (str): Number to be processed.
inputBase (int): Base in which the input is encoded.
Returns:
str, str, bool: The integer part of the number, the decimal part of the number, number isNegative.
"""
numberInt = ""
numberDecimal = ""
numberExponent = ""
isNegative = False
# Store if number is negative
if "-" in number:
isNegative = True
number = number.strip("-")
else:
isNegative = False
# Check if number is in Base10 and uses scientific notation
# If so, convert it to a non-scientific form
if inputBase == 10 and "E" in number:
number, numberExponent = number.split("E")
number = str(float(number) * 10 ** int(numberExponent))
# Check if number has a decimal part
if "." in number:
numberInt, numberDecimal = number.split(".")
else:
numberInt = number
return numberInt, numberDecimal, isNegative
def convertToBase10(number: str, inputBase: int):
"""Converts a number from BaseX notation with x between [2, 36] to a Base10 notation.
Args:
number (str): Number to be converted.
inputBase (int): BaseX with x between [2, 36] in which the input is encoded between.
Returns:
str: The converted number in Base10 notation.
"""
answerInt = 0
answerDecimal = 0
numberInt, numberDecimal, isNegative = processNumber(number, inputBase)
# Convert integer part of number
for index in range(len(numberInt)):
# Weight of the position of the current digit in the number
power = len(numberInt) - index - 1
# Check if digit is numerical or a hexadecimal char
if numberInt[index] >= "0" and numberInt[index] <= "9":
answerInt += int(numberInt[index]) * inputBase**power
else:
answerInt += convertASCII(numberInt[index], None) * inputBase**power
# Convert decimal part of number
if "." in number:
for index in range(len(numberDecimal)):
# Weight of the position of the current digit in the number
power = -index - 1
# Check if digit is numerical or a hexadecimal char
if numberDecimal[index] >= "0" and numberDecimal[index] <= "9":
answerDecimal += int(numberDecimal[index]) * inputBase**power
else:
answerDecimal += convertASCII(numberDecimal[index], None) * inputBase**power
if isNegative:
return str(-1 * (answerInt + answerDecimal))
else:
return str(answerInt + answerDecimal)
def convertFromBase10(number: str, outputBase: int = 10, maxDecimals: int = 30):
"""Convert a number from Base10 notation to BaseX notation with x between [2, 36].
Args:
number (str): Number in Base10 to be converted.
outputBase (int, optional): Base in which to encode the output. Defaults to 10.
maxDecimals (int, optional): Limit number of decimals computed in irrational number. Defaults to 30.
Returns:
str: The converted number in BaseX notation with x between [2, 36].
"""
answerInt = ""
answerDecimal = ""
numberInt, numberDecimal, isNegative = processNumber(number, 10)
# Makes sure that answerInt isn't empty if number == 0
if number == "0":
answerInt += "0"
# Convert integer part of number
dividend = int(numberInt)
while dividend > 0:
remainder = str(dividend % outputBase)
# Check if digit is numerical or a hexadecimal char
if remainder >= "0" and remainder <= "9" and len(remainder) == 1:
answerInt += remainder
else:
answerInt += convertASCII(None, int(remainder))
# Updating dividend for next loop iteration by keeping only the quotient of the division
dividend //= outputBase
# Reversing computed list according to the conversion algorithm
answerInt = answerInt[::-1]
# Convert decimal part of number
if "." in number:
repetition = 0
# Multiplicand is currently stored in an int format.
# It is converted to a decimal number here.
# Example: 1234 --> 0.1234
multiplicand = int(numberDecimal) * 10 ** (-len(numberDecimal))
# Loop until algorithm is done or until maxDecimals reached in an irrational number
while repetition < maxDecimals and multiplicand != 0:
# The int() is added to convert the float resulting from the floor division (removes the .0 after number).
product = str(int((multiplicand * outputBase) // 1))
# Check if digit is numerical or a hexadecimal char
if product >= "0" and product <= "9" and len(product) == 1:
answerDecimal += product
else:
answerDecimal += convertASCII(None, int(product))
# Updating multiplicand for next loop iteration by keeping only the decimal section of the multiplication
multiplicand = (multiplicand * outputBase) - (multiplicand * outputBase) // 1
repetition += 1
if isNegative:
if answerInt == "":
return str("-0." + answerDecimal)
elif answerDecimal == "":
return str("-" + answerInt)
else:
return str("-" + answerInt + "." + answerDecimal)
else:
if answerInt == "":
return str("0." + answerDecimal)
elif answerDecimal == "":
return str(answerInt)
else:
return str(answerInt + "." + answerDecimal)
def convertToTwoComplement(number: str):
"""Converts a Base2 integer to its Two's Complement encoding.
Args:
number (str): Integer in Base2 to be converted.
Returns:
str: The converted number in Two's Complement encoding.
"""
number, numberDecimal, isNegative = processNumber(number, 2)
number = number
# Check that number is an integer
if numberDecimal != "":
print("ERROR: Only integers may be converted to Two's Complement encoding.")
exit()
# Check if number is in the range of Two's Complement encoding by looking at the length of the binary number.
if isNegative == True:
if (len(number) > 8) or (len(number) == 8 and "1" in number[1:]):
print("ERROR: Only integers between [-128, 127] may be converted to Two's Complement encoding.")
exit()
# Make binary number 8 bits long
while len(number) < 8:
number = "0" + number
# Simulate a bitwise operator NOT
notNumber = ""
for digit in number:
if digit == "1":
notNumber += "0"
else:
notNumber += "1"
# Add 1 to the binary number
notNumberAddition = ""
index = 1 # Not 0 because string is read backwards
for digit in notNumber[::-1]:
if digit == "1":
notNumberAddition = "0" + notNumberAddition
if digit == "0":
notNumberAddition = "1" + notNumberAddition
break
index += 1
# Merge digits that were not affected by addition to affected digits
notNumberAddition = notNumber[0 : (len(notNumber) - index)] + notNumberAddition
return notNumberAddition
elif isNegative == False:
if len(number) > 7:
print("ERROR: Only integers between [-128, 127] may be converted to Two's Complement encoding.")
exit()
# Make binary number 8 bits long
while len(number) < 8:
number = "0" + number
return number
def convertToIEEE754SinglePrecision(number: str):
"""Converts a Base2 number to its IEEE 754 Single Precision encoding.
Args:
number (str): Number in Base2 to be converted.
Returns:
str: The converted number in IEEE 754 Single Precision encoding.
"""
numberInt, numberDecimal, isNegative = processNumber(number, 2)
if isNegative:
sign = "1"
else:
sign = "0"
exponent = convertFromBase10(str(127 + (len(numberInt) - 1)), 2, 8)
# Check if exponent is bigger than max range of IEEE754 Single Precision
if len(exponent) > 8:
exponent = ""
for i in range(8):
exponent += "1"
while len(exponent) < 8:
exponent = exponent + "0"
numberInt = numberInt[1:]
mantissa = numberInt + numberDecimal
# Check if mantissa is bigger than max range of IEEE754 Double Precision
if len(mantissa) > 23:
mantissa = ""
for i in range(23):
mantissa += "1"
while len(mantissa) < 23:
mantissa += "0"
# Check for special cases of IEEE754
if not ("0" in exponent) and not ("1" in mantissa):
return str(int(sign) * -1) + "\u221e"
elif not ("0" in exponent) and ("1" in mantissa):
return "NaN"
elif not ("1" in exponent) and not ("1" in mantissa):
return str(int(sign) * -1) + "0"
else:
return sign + " " + exponent + " " + mantissa
def convertToIEEE754DoublePrecision(number: str):
"""Converts a Base2 number to its IEEE 754 Double Precision encoding.
Args:
number (str): Number in Base2 to be converted.
Returns:
str: The converted number in IEEE 754 Double Precision encoding.
"""
numberInt, numberDecimal, isNegative = processNumber(number, 2)
if isNegative:
sign = "1"
else:
sign = "0"
exponent = convertFromBase10(str(1023 + (len(numberInt) - 1)), 2, 11)
# Check if exponent is bigger than max range of IEEE754 Double Precision
if len(exponent) > 11:
exponent = ""
for i in range(11):
exponent += "1"
while len(exponent) < 11:
exponent = exponent + "0"
numberInt = numberInt[1:]
mantissa = numberInt + numberDecimal
# Check if mantissa is bigger than max range of IEEE754 Double Precision
if len(mantissa) > 52:
mantissa = ""
for i in range(52):
mantissa += "1"
while len(mantissa) < 52:
mantissa += "0"
# Check for special cases of IEEE754
if not ("0" in exponent) and not ("1" in mantissa):
return str(int(sign) * -1) + "\u221e"
elif not ("0" in exponent) and ("1" in mantissa):
return "NaN"
elif not ("1" in exponent) and not ("1" in mantissa):
return str(int(sign) * -1) + "0"
else:
return sign + " " + exponent + " " + mantissa
def convertFromTwoComplement(number: str, outputBase: int = 10, maxDecimals: int = 30):
answerNumber = ""
# CONVERT HERE TO BINARY
if outputBase == 2:
return answerNumber
else:
return convertBase(answerNumber, 2, outputBase, maxDecimals)
def convertFromIEEE754SinglePrecision(number: str, outputBase: int = 10, maxDecimals: int = 30):
answerNumber = ""
# CONVERT HERE TO BINARY
if outputBase == 2:
return answerNumber
else:
return convertBase(answerNumber, 2, outputBase, maxDecimals)
def convertFromIEEE754DoublePrecision(number: str, outputBase: int = 10, maxDecimals: int = 30):
answerNumber = ""
# CONVERT HERE TO BINARY
if outputBase == 2:
return answerNumber
else:
return convertBase(answerNumber, 2, outputBase, maxDecimals)
def convertBase(number: str, inputBase: int, outputBase: int = 10, maxDecimals: int = 30):
"""Main function to call in order to convert a number from BaseX to Base Y with x and y between [2, 36].
Args:
number (str): Number to be converted.
inputBase (int): Base in which the input is encoded.
outputBase (int, optional): Base in which to encode the output. Defaults to 10.
maxDecimals (int, optional): Limit number decimals computed in irrational number. Defaults to 30.
Returns:
str: The converted number in BaseX notation with x between [2, 36].
"""
# Checking input for errors and illegal digits/characters
inputCheck(number, inputBase, outputBase)
# Optimize conversions according to input and output base
if inputBase == 10 and outputBase == 10:
return number
elif inputBase != 10 and outputBase == 10:
return convertToBase10(number, inputBase)
elif inputBase == 10 and outputBase != 10:
if outputBase == -1:
numberBinary = convertFromBase10(number, 2, 8)
return convertToTwoComplement(numberBinary)
elif outputBase == -2:
numberBinary = convertFromBase10(number, 2, 23)
return convertToIEEE754SinglePrecision(numberBinary)
elif outputBase == -3:
numberBinary = convertFromBase10(number, 2, 52)
return convertToIEEE754DoublePrecision(numberBinary)
else:
return convertFromBase10(number, outputBase, maxDecimals)
elif inputBase != 10 and outputBase != 10:
number = convertToBase10(number, inputBase)
if outputBase == -1:
numberBinary = convertFromBase10(number, 2, 8)
return convertToTwoComplement(numberBinary)
elif outputBase == -2:
numberBinary = convertFromBase10(number, 2, 23)
return convertToIEEE754SinglePrecision(numberBinary)
elif outputBase == -3:
numberBinary = convertFromBase10(number, 2, 52)
return convertToIEEE754DoublePrecision(numberBinary)
else:
return convertFromBase10(number, outputBase, maxDecimals)
def main():
number = str(input("Number to convert: ")).strip().upper()
try:
inputBase = int(input("Convert from base x: "))
outputBase = int(input("Convert to base y: "))
except ValueError:
print("\nValid base should be an integer between [2, 36].\n")
exit()
convertedNumber = convertBase(number, inputBase, outputBase, 50)
print(
f"\nBase {inputBase}: {number}\
\nBase {outputBase}: {convertedNumber}\n"
)
if __name__ == "__main__":
# execute only if run as a script
main()
# To add:
# Accept E for scientific notation in base 10 input

Base Encoding Converter

Through this program, the encoding of a number can be converted from a BaseX to a BaseY encoding where x and y are each between [2, 36].

It is also possible to convert a number to two's complement (output base = -1), IEEE 754 Simple Precision (output base = -2), IEEE 754 Double Precision (output base = -3).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment