Last active
March 25, 2021 21:02
-
-
Save CTimmerman/0c6add51e28d7c1c318d56a466df5249 to your computer and use it in GitHub Desktop.
Convert floats to any base and back.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Converts floats to/from any base. | |
2021-03-25 v1.1 by Cees Timmerman""" | |
import math | |
NUMERALS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" | |
def numeral(i): | |
try: | |
i = int(i) | |
except ValueError: | |
return i | |
if i >= len(NUMERALS): | |
return f"({i})" | |
return NUMERALS[i] | |
def list2str(a, sep=""): | |
return sep.join(map(numeral, a)) | |
def int2list(num, base=2): | |
"""Convert a decimal integer >= 1 to base > 0. | |
>>> int2list(255, 16) | |
[15, 15] | |
>>> int2list(3, 1) | |
[1, 1, 1] | |
>>> int2list(3, 0.5) | |
[1, '.', 1] | |
>>> int2list(10, 0.5) | |
[0, '.', 1, 0, 1] | |
>>> int2list(5.0625, 1.5) | |
[1, 0, 0, 0] | |
>>> int2list(1, 1.5) | |
[1] | |
""" | |
if base == 1: | |
return [1] * num | |
invert = False | |
if 0 < base < 1: | |
base = 1 / base | |
invert = True | |
a = [] | |
if num == 0: | |
return [0] | |
while num != 0: | |
num, rem = divmod(num, base) | |
a.insert(0, int(rem)) | |
if invert: | |
a.reverse() | |
a.insert(1, ".") | |
return a | |
def fraction2list(num, base=2, depth=1): | |
"""Convert a 0 <= decimal < 1 to 0 < base != 1. | |
>>> fraction2list(0, 1.5) | |
[0] | |
>>> fraction2list(0.6666666666666666, 1.5) | |
[1] | |
>>> fraction2list(0.4444444444444444, 1.5) | |
[0, 1] | |
>>> fraction2list(0.5, 2) | |
[1] | |
>>> fraction2list(0.25, 0.5) | |
[1, 0, 0] | |
""" | |
invert = False | |
if 0 < base < 1: | |
base = 1 / base | |
invert = True | |
a = [] | |
if num == 0: | |
return [0] | |
try: | |
depth = 0 | |
while True: | |
depth += 1 | |
if depth > 100: | |
break | |
num, rem = divmod(num, 1 / base ** depth) | |
a.append(int(num)) | |
if rem: | |
num = rem | |
else: | |
break | |
except ZeroDivisionError: | |
pass | |
if invert: | |
a.reverse() | |
a.append(0) | |
return a | |
def float2list(num, base=2): | |
"""Convert a decimal float to base > 1. | |
>>> float2list(10.1, 16) | |
[10, '.', 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8] | |
>>> float2list(41.5, 42) | |
[41, '.', 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 38, 10, 39, 41, 34, 24, 40, 40, 6, 4, 24, 13, 37, 15, 16, 41, 22, 13, 30, 26, 39, 11, 29, 12, 25, 29, 21, 9, 33, 2, 37, 1, 23, 13, 6, 25, 37, 41, 0, 6, 39, 24, 5, 12, 7, 26, 36, 25, 41, 25, 22, 10, 10, 35, 30, 37, 32, 23, 39, 38, 5, 13, 40, 15, 32, 35, 3, 21, 26, 21, 5, 26, 41, 16, 18, 31, 25, 13, 0, 34, 18, 40, 38, 5, 25, 9, 38, 14, 1] | |
>>> float2list(0) | |
[0, '.', 0] | |
>>> float2list(-1.25) | |
['-', 1, '.', 0, 1] | |
>>> float2list(-0.75) | |
['-', 0, '.', 1, 1] | |
""" | |
sign = [] | |
if num < 0: | |
sign = ['-'] | |
num = abs(num) | |
fractional, whole = math.modf(num) | |
return sign + int2list(whole, base) + ["."] + fraction2list(fractional, base) | |
def list2float(num, base=2): | |
""" | |
>>> list2float([1, 1]) | |
3.0 | |
>>> list2float([1, 41, '.', 21], 42) | |
83.5 | |
>>> list2float(['-', 1, 0, '.', 0, 8], 16) | |
-16.03125 | |
""" | |
sign = 1 | |
if '-' in num: | |
num = num[1:] | |
sign = -1 | |
total = 0.0 | |
try: | |
dot = num.index(".") | |
except ValueError: | |
dot = len(num) | |
whole = num[:dot] | |
fraction = num[dot + 1 :] | |
for i, numeral in enumerate(whole + fraction): | |
if type(numeral) == str: | |
numeral = NUMERALS.index(numeral) | |
total += numeral * base ** (dot - i - 1) | |
return sign * total | |
def str2float(s, base=2): | |
"""Convert any base string to decimal float. | |
>>> str2float("1010.1") | |
10.5 | |
>>> str2float("1010.0001100110011001100110011001100110011001100110011") | |
10.1 | |
>>> str2float("A.1999999999998", 16) | |
10.1 | |
>>> str2float("2B.7C9", 15) | |
41.52266666666667 | |
>>> str2float('111', 1) | |
3.0 | |
>>> str2float('10', 1.5) | |
1.5 | |
>>> str2float('100', 1.5) | |
2.25 | |
>>> str2float('0.1', 1.5) | |
0.6666666666666666 | |
>>> str2float('ABCDEFG', 0) | |
16.0 | |
>>> str2float('FF.FF', -1) | |
0.0 | |
>>> str2float('FF.FF', -1.5) | |
-10.833333333333334 | |
""" | |
return list2float(list(s), base) | |
def float2str(num, base=2): | |
"""Convert a decimal float to base >= 1. | |
>>> float2str(10.5) | |
'1010.1' | |
>>> float2str(10.5, 16) | |
'A.8' | |
>>> float2str(10.1) | |
'1010.0001100110011001100110011001100110011001100110011' | |
>>> float2str(10.1, 16) | |
'A.1999999999998' | |
>>> float2str(41.5, 42) | |
'(41).L000000000J(38)A(39)(41)YO(40)(40)64OD(37)FG(41)MDUQ(39)BTCPTL9X2(37)1ND6P(37)(41)06(39)O5C7Q(36)P(41)PMAAZU(37)WN(39)(38)5D(40)FWZ3LQL5Q(41)GIVPD0YI(40)(38)5P9(38)E1' | |
>>> float2str(0) | |
'0.0' | |
""" | |
return list2str(float2list(num, base)) | |
if __name__ == "__main__": | |
import doctest | |
print(doctest.testmod()) | |
if 0: | |
import timeit | |
print(timeit.timeit("int2str(255)", globals=globals())) # 2.5032687 | |
print(timeit.timeit("int2list(255)", globals=globals())) # 1.4733017 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment