This is a Python 3 script that converts any real number (int
s and float
s) from decimal notation to any positional number system representation with an integer base between 2 and 36 and vice versa, meaning it can convert negative fractions.
Technically it can convert any arbitrary base, but I struggle to find a way to represent the output, its output is like hexadecimal, but instead of only letters a to f, all of the English alphabet can be used, so there can be a unique symbol for all numbers up to 35.
I don't know how to represent number systems with bases higher than 36, if I continue to use strings, if I use the Greek alphabet after Latin alphabet, I can represent bases up to 60, but many Greek letters have homoglyph in Latin alphabet... And if I just use a list
, it would be perfectly fine if they only represent positive integers, but here I need a list
for the integral part and a list
for the fractional part, and potentially a str
before the first list
...
The actual code that does the job is extremely simple, but I added many useless validations that slows the execution down tremendously, just so in case someone gives incorrect inputs...
Code
import re
from typing import Union
ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'
def convertfrom_decimal(number: Union[int, float], base: int=16) -> str:
if not isinstance(number, (float, int)):
raise TypeError('Argument `number` must be a numerical value (int or float)')
if not isinstance(base, int):
raise TypeError('Argument `base` must be an integer')
if not 2 <= base <= 36:
raise ValueError('This function can only support bases from 2 to 36')
negative = False
if number < 0:
negative = True
number = -number
whole = int(number)
fraction = number - whole
if whole:
integral = ''
while whole:
whole, digit = divmod(whole, base)
integral = ALPHABET[digit] + integral
else:
integral = '0'
if not fraction:
return integral if not negative else '-'+integral
fractional = ''
bits = 0
while bits < 48:
bit, fraction = divmod(fraction*base, 1.0)
fractional += ALPHABET[int(bit)]
if fraction < 2**-48:
break
bits += 1
result = integral + '.' + fractional
return result if not negative else '-'+result
def convertto_decimal(string: str, base: int=0) -> float:
if not isinstance(string, str):
raise TypeError('Argument `string` must be an instance of `str`')
if not isinstance(base, int):
raise TypeError('Argument `base` must be an instance of `int`')
if base and not 2 <= base <= 36:
raise ValueError('Argument `base` is not between 2 and 36')
if not re.match('^(\-|\+)?[0-9a-z]+(\.[0-9a-z]+)?$', string):
raise ValueError('Argument string is not a valid numerical notation supported by this function')
negative = False
if string[0] == '-':
negative = True
string = string[1:]
fraction = ''
if '.' in string:
whole, fraction = string.split('.')
else:
whole = string
indicator = max(ALPHABET.index(d) for d in string if d in ALPHABET)
if not base:
for b in (2, 8, 16, 36):
if indicator < b:
base = b
break
if indicator >= base:
raise ValueError('Argument `base` is incorrect')
#integral = int(whole, base)
integral = sum(
ALPHABET.index(d)*base**i for i, d in enumerate(whole[::-1])
)
if not fraction:
return integral if not negative else -integral
fractional = sum(
ALPHABET.index(d)*base**-(i+1) for i, d in enumerate(fraction)
)
number = integral + fractional
return number if not negative else -number
if __name__ == '__main__':
for i in range(2, 37):
print(convertfrom_decimal(3.1415926535897932, i))
Output
11.001001000011111101101010100010001000010110100011
10.010211012222010211002111110221222020010102220122
3.021003331222202020112203
3.032322143033432411241211414143234130344233124014
3.050330051415124105232005511454424522431000231043
3.066365143203613410601052256200101122510662105012
3.1103755242102643
3.124188124074427866112818683125147474717151652050
3.141592653589793115997963468544185161590576171875
3.16150702865a484776333a98347444320a009a7420206870
3.184809493b9186459aaa3a83
3.1ac1049052a2c71005161571824ba0969c9c2497709caba9
3.1da75cda813752b70268a34a22a74bc41348ccb8c5b228a7
3.21cd1dc46c2b7ab624ee5cd3a5322906081b7e4cc8822538
3.243f6a8885a3
3.26fag579ed6gdea8g2f5a1a2386be4847dfa9c199955365a
3.29fdeh0g77186b7e590fg494559fgf1df946f3b06h5636d9
3.2d23982975gfh9b5f957e5005e7c16d3dg23bh47838i4h7i
3.2gceg9gbhj9cc1508a2e3jdf
3.2k961edi5h85d7fhkhd2idf09bjkafe8bf0513def9k2hkj3
3.32bek9a809gafkj34f0jlilchkcg9hach0d5acji5geh42gb
3.35kh9k813jk70fjjjl150i0ikg7mffmm4efak0ih8g3i0km4
3.39d911bclk3nn443
3.3dc9fine6e76llndlfjmi7k9n2fcnlh7m95927g497l0oaka
3.3higbebohjh2bh66ka19afih5lahe37b9h5ipiend7np4mjd
3.3m5q3m2dcpq63bohkl3n4gedlg4jjk1ii8g7qi8ngbeablk4
3.3r06liojplq9mr9eq1867957
3.4328n0cjqmic2nrmogpp06ff7hd864qe4kjg48db7da6hkcf
3.47d01ee07qs3an4tkttin4l91k8a7jrh1ot5gjqa431c5imf
3.4c25oe856s2t8tg5rue7psq0h3m72hd7tloa5ja67lj96li8
3.4gvml245kc
3.4m6dn4ow9qwe210nr3u0cdqkcnrbmwlh7kmfeapn9fijt38k
3.4rn5c8ianuxpep3owhg3n4m1o6r595s5kmr3djex4m1k6cph
3.4xfrgmtm53gd8tfed3xnstgi56yfaa7dvfrxe5vb5wq7qe4e
3.53i5ab8p5fc5vayqter60f6r
I have noticed that for binary I can have up to 48 fractional bits, 24 bits for quaternary, 16 bits for octal, 12 bits for hexadecimal and 9.6 bits for base-32, all other bases have precision limits under 48 bits, but interestingly ternary seems to have infinite precision...
So how is the code? Are there any areas that can be improved?