I want to convert a number like 10.1(010) (which I'm storing as a triple ('10','1','010')
) into decimal, getting 2.6(428571) (or ('2','6','42857')
).
I want to get the exact number, repeating decimals and all, not a float that approximates it.
I can easily convert it to a float, by simply reading each item as an integer and multiplying it by the appropriate place value:
bin_to_float(('10','1','010')
) = 2 + 1 * 2-1 + 2 * 2-1/(23-1) = 2.642857142857143
Which I wrote in python
def read_numeral(self, numeral, base=2) -> float:
if ',' not in numeral: numeral = numeral.replace('r',',r')
integer, finite, period = numeral
offset = len(finite)
n = read_int(integer, base)
if finite: n += read_int(finite, base) * (base ** -offset)
if period: n += read_int(period, base) * base ** -offset / (base**len(period)-1)
return n
I couldn't figure out a way to get the exact decimal representation, though.
Please clarify what you mean by "exact decimal representation". I'm sure you're aware of that many possible inputs cannot be represented by a finite decimal string. Python, alas, does not support infinite strings ;-)
If there's some limit on how many decimal digits you want to get, the decimal
module can be used easily, and configured to use as many digits as you have RAM to store them.
>>> import decimal
>>> from decimal import Decimal as D
>>> decimal.getcontext().prec = 200 # use 200 digits
>>> D2 = D(2)
>>> result = D2 + 1 * D2**-1 + D2 * D2**-1 / (D2**3 - 1)
>>> result
Decimal('2.6428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571')
BTW, while Python doesn't support infinite strings, unbounded sequences are a different story. You could, e.g., write a generator to produce decimal digits one at a time.
Maybe you'll find these building blocks useful. It's best to keep "the math part" separate from "the pretty part". That is, code will quickly become an incomprehensible mess if you try to do everything in one function. So these just do "the math part", and make no assumptions about how, e.g., you want to input or display numbers in base 493921. To this code, they're just lists of integers with elements in range(493921)
.
# Convert int n >= 0 to base. Return list of base `base` digits.
def tobase(n, base):
bs = []
while True:
n, digit = divmod(n, base)
bs.append(digit)
if not n:
break
bs.reverse()
return bs
# Convert rational n/d to base as a list of digits in the base.
# The list contains '.' after the integer part, and if it's a
# repeating fraction the list contains '('. and ')' at the end,
# to delimit the repeating digits.
def rep(n, d, base):
xs = []
n, rem = divmod(n, d)
xs = tobase(n, base)
xs.append('.')
rem2pos = {}
while rem:
if rem in rem2pos:
i = rem2pos[rem]
xs.insert(i, '(')
xs.append(')')
break
rem2pos[rem] = len(xs)
digit, rem = divmod(rem * base, d)
xs.append(digit)
return xs
And then, e.g.,
>>> rep(100, 4, 10) # plain integer
[2, 5, '.']
>>> rep(100, 3, 10) # simple repeat, 33 1/3
[3, 3, '.', '(', 3, ')']
>>> rep(2, 7, 10) # and pure fraction
[0, '.', '(', 2, 8, 5, 7, 1, 4, ')']
>>> rep(1, 37, 10) @ subtler repeat
[0, '.', '(', 0, 2, 7, ')']
>>> 1/37 # yup! looks about right ;-)
0.02702702702702703
>>> rep(1, 64, 10) # terminating fraction
[0, '.', 0, 1, 5, 6, 2, 5]
>>> rep(21, 10, 2) # and use base 2 for a change
[1, 0, '.', 0, '(', 0, 0, 1, 1, ')']
>>> rep(37, 14, 10) # your example
[2, '.', 6, '(', 4, 2, 8, 5, 7, 1, ')']
That last may, or may not, match what you may mean by "exact decimal representation".