1

So, I'm trying to get long series of bits from a decimal number as defined by the python decimal module, currently this one:

Decimal('3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303820')

(Just pi to 200 digits, by the way.)

I want the whole thing in binary representation, so I can convert it into an array of hexadecimal numbers representing everything below the 3.

Any ideas on how to do this? I kept looking through the module but it only seems to provide strictly decimal-based functions.

7
  • 3
    Decimal avoids floating point complications by shirking the usual conventions of storing numbers in binary, so I suppose what binary representation are you looking for? What's Decimal('3.14') in binary? Commented Aug 14, 2014 at 16:56
  • What are you trying to do with the "binary" representation? Do you want to implement your own format for large numbers and arithmetic calculations? What "binary" format do you want for a number that is of order 10 to the 200th Commented Aug 14, 2014 at 16:58
  • 1
    Even mpmath and bigfloat only offer value-to-string conversions, I cannot find any arbitrary-precision float to binary representations other than the IEEE single and double formats. You'll have to come up with your own here. Commented Aug 14, 2014 at 17:12
  • @MartijnPieters It can easily be done with gmpy2. I've added an answer, too. Commented Aug 14, 2014 at 18:23
  • @casevh: Presumably that's a proprietary representation then. Commented Aug 14, 2014 at 18:53

2 Answers 2

2

My apologies for the bump, but for anyone who still needs an answer to this (using Decimal), it's possible to write your own conversion method. It would not be highly efficient, but it can be accurate enough if you temporarily increase precision while generating the binary digits. Digits in any base (greater than one) are simply coefficients on the powers of the given base. Commented code below outlines the entire process to convert any number from decimal to any other base (if the new base is greater than one, of course). This algorithm works for any arbitrary-precision python module that can support logarithms and floating point numbers.

from decimal import *

def convert_base(x, new_base, preferred_digits=200):
    # ensure that x and new_base both have arbitrary precision
    x = Decimal(x)
    new_base = Decimal(new_base)

    # compute the log floor of x -- this is the exponent of the power of new_base of which the leading digit of x (in base new_base) is the coefficient
    # for example: pi in base 10 is 3.14159... but in base 2 it's 1*2 + 1*1 + 0*0.5 + ... which means exponent=1 because that sum starts with 2 raised to the power of exponent=1 (1*2^1 + ...)
    # further, log(pi) = 1.65... which means the floor is floor(log(pi)) = 1, so again, p=1.
    # in other words, exponent represents the largest exponent on the power of new_base where the power of new_base is less than or equal to the given x.
    exponent = (x.log10()/Decimal(new_base).log10()).__floor__()

    # we need a copy of this value to return it
    largest_exponent = exponent

    # start at the value of x
    # we will work our way down to 0, or get very close to it
    # ideally, we'll come up with a sufficient number of digits
    v = x
    
    # the list of digits that this function will return after populating it
    digits = []

    # keep decreasing v until it's extremely close to 0 (such that it becomes 0 with the provided precision) or the number of digits we generate reaches or exceeds our preferred_digits
    while v > 0 and len(digits) < preferred_digits:
        # the current power of new_base
        power = new_base ** exponent

        # compute the coefficient of the power (this is the next digit to add to digits)
        # we want this to be an integer, so we'll floor it
        coefficient = int(v / power)
        digits.append(coefficient)

        # since v is a sum of powers of new_base, we can now subtract power * coefficient from v, and we'll be left with the exact same problem as before, but we have obtained one digit
        # so we just rinse and repeat until we have enough digits, or all that there are in the converted representation
        v -= power * coefficient

        # to continue the process, we must make sure that the power of new_base is decreasing (so we increment p, the exponent of new_base, as power has -p as the exponent)
        # the whole point of using -p as the exponent to find the power of the new_base is to use multiplication in this loop instead of division (so it's slightly faster)
        exponent -= 1

    # we return digits, but also the largest exponent so we know where to put the "point" in the number
    # every number in any base is represented by a mantissa and an exponent.  here, largest_exponent is the exponent of the number's representation in base new_base
    # digits are part of the mantissa, so we've essentially converted x into base new_base
    return digits, largest_exponent


# we can also convert back to decimal from any base
# we just sum the products of the digits and the corresponding powers of from_base, based on exponent
def convert_to_decimal(digits, exponent, from_base):
    # again, from_base can have arbitrary precision
    from_base = Decimal(from_base)

    # start with a zero sum
    # this sum will yield our original x that we passed to convert_base()
    x = 0

    # create a copy of the exponent, but we do this so we don't have to modify the argument of the function itself (just a good practice)
    p = exponent

    # now we iterate over the digits, decreasing exponent (p) each time
    for d in digits:
        # compute the power
        power = from_base ** p

        # multiply by coefficient (current digit)
        product = power * d

        # add to the total sum
        x += product

        # decrease the exponent (p)
        p -= 1

    # the sum is now a decimal representation of number that has the given mantissa (digits) and exponent (exponent) in the given base (from_base)
    return x




digits, exponent = convert_base(Decimal('3.1415926535'), 2)

print(digits)
print(exponent)

reconverted = convert_to_decimal(digits, exponent, 2)

print(reconverted)

# some rounding errors of course, but that's nearly impossible to avoid
# one simple workaround is to temporarily increase the digits of precision by 2 or 3 digits for the conversion processes

Okay, so I've technically answered the question. But I really want to emphasize that the decimal module for Python is not the best way to go for arbitrary precision floating point numbers. I strongly recommend mpmath. I've used it in countless personal projects and it never fails me! Here's the same algorithm using mpmath.

from mpmath import *

def convert_base(x, new_base, preferred_digits=200):
    x = mpf(x)
    new_base = mpf(new_base)
    exponent = int(floor(log(x)/log(new_base)))
    largest_exponent = exponent
    v = x
    digits = []
    while v > 0 and len(digits) < preferred_digits:
        power = new_base ** exponent
        coefficient = int(v / power)
        digits.append(coefficient)
        v -= power * coefficient
        exponent -= 1
    return digits, largest_exponent

def convert_to_decimal(digits, exponent, from_base):
    from_base = mpf(from_base)
    x = 0
    p = exponent
    for d in digits:
        power = from_base ** p
        product = power * d
        x += product
        p -= 1
    return x



digits, exponent = convert_base(mpf('3.1415926535'), 2)

print(digits)
print(exponent)

reconverted = convert_to_decimal(digits, exponent, 2)

print(reconverted)

# still some rounding errors with mpmath, but they're not displayed because it's smart

We can even compare execution times.

import timeit

decimal_time = timeit.timeit('using_decimal.convert_base(3.1415926535, 2, 200)', number=10000, setup='import using_decimal')
mpmath_time = timeit.timeit('using_mpmath.convert_base(3.1415926535, 2, 200)', number=10000, setup='import using_mpmath')

print(f'decimal time: {decimal_time} seconds')
print(f'mpmath time: {mpmath_time} seconds')
print('Speed improvement: {:+.1f}%'.format((decimal_time-mpmath_time)*100./mpmath_time))
decimal time: 4.2381332 seconds
mpmath time: 3.4054762 seconds
Speed improvement: +24.5%

Hoping this helps!

Sign up to request clarification or add additional context in comments.

Comments

1

It can be done with gmpy2.

>>> import gmpy2
>>> pi=gmpy2.const_pi(precision=100)
>>> "{0:A}".format(pi)
'0X3.243F6A8885A308D313198A2EP+0'
>>> 

Use help(gmpy2.mpfr().__format__) for details on the formatting options supported by the mpfr type.

Disclaimer: I maintain gmpy2.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.