View Full Version: Base conversion program

Dozensonline > Number Bases > Base conversion program



Title: Base conversion program


Dan - August 11, 2005 05:03 AM (GMT)
Here's a little Python program I wrote for converting numbers from one base to another, with unlimited precision. Thought you might find it useful.

CODE
#!/usr/bin/env python
# -*- coding: latin-1 -*-

"""Command-line base-conversion utility.

This program converts its arguments from one mathematical base to another.
Floating-point and scientific notation (mant@exp) are supported.

USAGE

   baseconv [-i base] [-o base] [{-e|-f|-g} precision] number ...

OPTIONS

  -i base
     the base in which the arguments are written (default: 10)
  -o base
     the base in which to display the output (default: 10)
  -e precision
     display results in exponential format, e.g. 1.234567@-3
     precision = the number of "decimal places" in the mantissa
  -f precision
     display results in fixed-point format, e.g. 0.001235
     precision = the number of "decimal places"
  -g precision
     display reults in -e or -f format, whichever is shorter
     precision = the number of significant digits
"""

from __future__ import division

import getopt
import math
import sys
from rational import Rational

DIGITS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'


def _atoi(string, base):
  """Convert a string to the corresponding number."""
  return int(string, base)


def _itoa(num, base):
  """Return the string representation of a number in the given base."""

  # to avoid having _itoa(0) == ''
  if num == 0:
     return DIGITS[0]

  # if num is negative, convert the abs and add the sign later
  negative = num < 0
  if negative:
     num = -num

  # build the numeral one digit at a time, from right to left
  digits = []
  while num:
     num, last_digit = divmod(num, base)
     digits.append(DIGITS[last_digit])
  if negative:
     digits.append('-')
  return ''.join(reversed(digits))


def str2num(string, base):
  """Convert a string to its numeric representation as a Rational."""

  string = ''.join(string.split()) # remove all whitespace

  # Check whether the number is negative
  negative = string.startswith('-')
  if negative:
     string = string[1:]

  # Check whether the number is in scientific notation
  # number = mantissa * base ** exponent
  if '@' in string:
     mantissa, exponent = string.split('@')
     exponent = _atoi(exponent, base)
  else:
     mantissa = string
     exponent = 0

  # Check whether the number contains a radix point
  if '.' in mantissa:
     int_part, frac_part = mantissa.split('.')
     exponent -= len(frac_part)
     mantissa = int_part + frac_part

  mantissa = _atoi(mantissa, base)
  if negative:
     mantissa = -mantissa
  if exponent < 0:
     return Rational(mantissa, base ** (-exponent))
  else:
     return Rational(mantissa * base ** exponent)


def _frexp(x, base):
  if x == 0:
     return 0, 0
  mantissa = x
  exponent = 0
  while abs(mantissa) < 1:
     mantissa *= base
     exponent -= 1
  while abs(mantissa) >= base:
     mantissa /= base
     exponent += 1
  return mantissa, exponent


def _format_f(x, base, precision):

  negative = x < 0
  if negative:
     sign = '-'
     x = -x
  else:
     sign = ''

  multiplier = base ** precision
  x *= multiplier
  x = int(x.round(1))
  if precision == 0:
     return _itoa(x, base)
  ipart, fpart = divmod(x, multiplier)
  return '%s%s.%s' % (sign, _itoa(ipart, base),
                      _itoa(fpart, base).zfill(precision))


def _format_e(x, base, precision):
  mantissa, exponent = _frexp(x, base)
  while True:
     result = '%s@%s' % (_format_f(mantissa, base, precision),
                         _itoa(exponent, base))
     # check to see whether rounding created a denormalized number
     if result.lstrip('-').find('.') > 1:
        mantissa /= base
        exponent += 1
     else:
        return result


def _format_g(x, base, precision):
  mantissa, exponent = _frexp(x, base)
  f_format = _format_f(x, base, max(0, precision - exponent - 1))
  e_format = _format_e(x, base, precision - 1)
  if len(e_format) < len(f_format):
     return e_format
  else:
     return f_format


def num2str(x, base, format='g', precision=None):
  """Converts a number to its string representation in the given base."""
  if precision is None:
     precision = int(math.log(2 ** 53, base))
  if not isinstance(x, Rational):
     x = Rational.from_exact_float(x)
  if format == 'f':
     return _format_f(x, base, precision)
  elif format == 'e':
     return _format_e(x, base, precision)
  elif format == 'g':
     return _format_g(x, base, precision)
  else:
     raise ValueError('Unsupported format: %%%s' % format)


def _main(argv=sys.argv):
  opts, args = getopt.getopt(argv[1:], 'e:f:g:i:o:', ['help'])
  opts = dict(opts)
  if '--help' in opts:
     print __doc__
  else:
     try:
        input_base = int(opts.get('-i', 10))
        output_base = int(opts.get('-o', 10))
     except ValueError:
        print >>sys.stderr, 'Error: Bases must be integers.'
        return 1
     if (input_base < 2 or input_base > 36 or
         output_base < 2 or output_base > 36):
        print >>sys.stderr, 'Error: Base must be 2-36.'
        return 1
     format_e = '-e' in opts
     format_f = '-f' in opts
     format_g = '-g' in opts
     precision = None
     if format_e + format_f + format_g > 1:
        print >>sys.stderr, 'Error: Only one format can be specified.'
        return 1
     elif format_e:
        format = 'e'
     elif format_f:
        format = 'f'
     elif format_g:
        format = 'g'
     else:
        format = 'g'
        precision = int(math.log(2 ** 53, output_base))
     if precision is None:
        precision = opts['-' + format]
        try:
           precision = int(precision)
        except ValueError:
           print >>sys.stderr, 'Error: Precision must be an integer.'
           return 1
     if precision < 0:
        print >>sys.stderr, 'Error: Precision must be nonnegative.'
        return 1
     return_value = 0
     for number in args:
        try:
           number = str2num(number, input_base)
           print num2str(number, output_base, format, precision)
        except (ValueError, TypeError), e:
           print >>sys.stderr, 'Invalid number: %s' % number
           return_value = 1
  return 0

if __name__ == '__main__':
  raise SystemExit(_main())


Examples

Convert decimal 123.456 to dozenal:

CODE
~$ baseconv -o12 123.456
A3.557B74854619


Round it to two dozenal places:

CODE
~$ baseconv -o12 -f2 123.456
A3.56


Convert Avogadro's Number to hexadecimal ("@" is used as the scientific notation symbol because "e" is a digit):

CODE
~$ baseconv -o16 6.02214199@23
7.F8618DE5AED9@13


Compute 1/23 (i.e. 0.1 base 23) in dozenal to 36 significant digits

CODE
~$ baseconv -i23 -o12 -g36 0.1
0.0631694842106316948421063169484210632

The Mighty Dozen - August 11, 2005 07:40 PM (GMT)
Okay Dan, I am braindead as far as computer stuff goes. So, how does one use the above program? :huh:

Dan - August 12, 2005 02:53 AM (GMT)
QUOTE (The Mighty Dozen @ Aug 11 2005, 02:40 PM)
Okay Dan, I am braindead as far as computer stuff goes. So, how does one use the above program?  :huh:

First, you'll need to install Python and download the rational numbers module. Copy the contents of that big code box into your text editor (making sure to preserve the indentation), and save it as "baseconv.py".

The program is meant to be executed from the command prompt. The syntax for it is: python baseconv.py options number_to_convert

The possible options are:
  • -i base -- specifies the base to convert from. Must be an integer between 2 and 36, inclusive. The default is 10.
  • -o base -- specifies the base to convert to. Must be an integer between 2 and 36, inclusive. The default is 10.
  • -f precision -- displays the output in fixed-point notation with precision digits after the radix point. (To round to the nearest integer, use -f0.)
  • -e precision -- displays the output in scientific notation with precision digits after the radix point.
  • -g precision -- displays the output in either -e or -f format, whichever is shorter. Precision indicates the number of significant digits to display.

The Mighty Dozen - August 12, 2005 01:29 PM (GMT)
Cheers, Dan. I'll give it a go when I get a spare ten minutes :)

Twinbee - August 12, 2005 04:27 PM (GMT)
Dan, if you have time, make it into an exe and give it a GUI :)

The Mighty Dozen - August 12, 2005 04:48 PM (GMT)
QUOTE (Twinbee @ Aug 12 2005, 04:27 PM)
Dan, if you have time, make it into an exe and give it a GUI :)

...and install it for us, while you're at it ;) :D

Dan - August 15, 2005 03:07 AM (GMT)
QUOTE (Twinbee @ Aug 12 2005, 11:27 AM)
Dan, if you have time, make it into an exe and give it a GUI :)

I'll put on on my website next week when I re-acquire my domain name.

finlay - August 18, 2005 05:25 PM (GMT)
It's fairly obvious why you've done it, but could you possibly implement a system whereby you could use bases higher than 36?
I'm just about to try it, anyway, and then once my sister's come off msn :rolleyes: I can try it on the mac, which allegedly comes with python.
Sounds cool. :)

OK it's not working. Some syntax error? Not sure if it's yours or mine.

Dan - August 18, 2005 07:47 PM (GMT)
QUOTE (finlay @ Aug 18 2005, 12:25 PM)
OK it's not working. Some syntax error? Not sure if it's yours or mine.

What error are you getting?

finlay - August 20, 2005 05:34 PM (GMT)
QUOTE
:~/Desktop/Downloads/baseconv] finlay% python baseconv.py
Traceback (most recent call last):
  File "baseconv.py", line 35, in ?
    from rational import Rational
  File "/Users/finlay/Desktop/Downloads/baseconv/rational.py", line 194
    @staticmethod
    ^
SyntaxError: invalid syntax

Apparently it's neither yours nor mine, unless I'm somehow mistaken.... :\

Dan - August 20, 2005 06:18 PM (GMT)

Dan - July 19, 2007 03:23 AM (GMT)
I guess I should explain my use of @ as the separator between the mantissa and exponent.

The usual notation uses "e", but that's a digit in bases fifteen and higher, so I couldn't use it. Or any letter (or digit, of course), because it would be a digit in some base.

I also didn't want to use any of the symbols !"#%&\'()*+,-./:;<=>?[\]^_{|}~ because each of them has a meaning in C (which, along with its descendants C++ and Java, are the most popular programming languages today).

So, that left only 3 characters left on my keyboard: $, @, and `. But "$" is very commonly used with digits, and ` could be confused with the apostrophe, so the only practical choice was @. Coincidentally, it has a slight resemblance to a lowercase "e".

Ged - July 22, 2007 03:16 PM (GMT)
I had the same problem with Python.

However I have written a base conversion programme in GUI using C++,
but it only works on Windows :(

So if anybody is interested in it let me know.

Dan - July 23, 2007 12:20 AM (GMT)
QUOTE (Ged @ Jul 22 2007, 09:16 AM)
I had the same problem with Python.

Here's one that uses the GMPY rational number class instead of the one I wrote.

CODE
#!/usr/bin/python -Wignore
# -*- coding: latin-1 -*-

"""Command-line base-conversion utility.

This program converts its arguments from one mathematical base to another.
Floating-point and scientific notation (mant@exp) are supported.

USAGE

   baseconv [-i base] [-o base] [{-e|-f|-g} precision] number ...

OPTIONS

  -i base
     the base in which the arguments are written (default: 10)
  -o base
     the base in which to display the output (default: 10)
  -e precision
     display results in exponential format, e.g. 1.234567@-3
     precision = the number of "decimal places" in the mantissa
  -f precision
     display results in fixed-point format, e.g. 0.001235
     precision = the number of "decimal places"
  -g precision
     display reults in -e or -f format, whichever is shorter
     precision = the number of significant digits
"""

from __future__ import division

import getopt
import math
import sys
import gmpy

DIGITS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'


def _atoi(string, base):
   """Convert a string to the corresponding number."""
   return int(string, base)


def _itoa(num, base):
   """Return the string representation of a number in the given base."""

   # to avoid having _itoa(0) == ''
   if num == 0:
       return DIGITS[0]

   # if num is negative, convert the abs and add the sign later
   negative = num < 0
   if negative:
       num = -num

   # build the numeral one digit at a time, from right to left
   digits = []
   while num:
       num, last_digit = divmod(num, base)
       digits.append(DIGITS[last_digit])
   if negative:
       digits.append('-')
   return ''.join(reversed(digits))


def str2num(string, base):
   """Convert a string to its numeric representation."""

   string = ''.join(string.split()) # remove all whitespace

   # Check whether the number is negative
   negative = string.startswith('-')
   if negative:
       string = string[1:]

   # Check whether the number is in scientific notation
   # number = mantissa * base ** exponent
   if '@' in string:
       mantissa, exponent = string.split('@')
       exponent = _atoi(exponent, base)
   else:
       mantissa = string
       exponent = 0

   # Check whether the number contains a radix point
   if '.' in mantissa:
       int_part, frac_part = mantissa.split('.')
       exponent -= len(frac_part)
       mantissa = int_part + frac_part

   mantissa = _atoi(mantissa, base)
   if negative:
       mantissa = -mantissa
   if exponent < 0:
       return gmpy.mpq(mantissa, base ** (-exponent))
   else:
       return gmpy.mpq(mantissa * base ** exponent)


def _frexp(num, base):
   """Return (mant, exp) such that num == mant * base ** exp"""
   if num == 0:
       return gmpy.mpq(0), 0
   mantissa = num
   exponent = 0
   while abs(mantissa) < 1:
       mantissa *= base
       exponent -= 1
   while abs(mantissa) >= base:
       mantissa /= base
       exponent += 1
   return gmpy.mpq(mantissa), exponent

def _round(num):
   """Round num to the nearest integer using round-to-even."""
   negative = num < 0
   if negative:
       num = -num
   int_part = long(num)
   frac_part = num - int_part
   if frac_part == gmpy.mpq(1, 2):
       int_part += int_part & 1
   elif frac_part > gmpy.mpq(1, 2):
       int_part += 1
   if negative:
       int_part = -int_part
   return int_part


def _format_f(num, base, precision):
   """Equivalent of '%.*f' % (num, precision) in the given base."""

   negative = num < 0
   if negative:
       sign = '-'
       num = -num
   else:
       sign = ''

   multiplier = base ** precision
   num *= multiplier
   num = _round(num)
   if precision == 0:
       return _itoa(num, base)
   ipart, fpart = divmod(num, multiplier)
   return '%s%s.%s' % (sign, _itoa(ipart, base),
                      _itoa(fpart, base).zfill(precision))


def _format_e(num, base, precision):
   """Equivalent of '%.*e' % (num, precision) in the given base."""
   mantissa, exponent = _frexp(num, base)
   while True:
       result = '%s@%s' % (_format_f(mantissa, base, precision),
                           _itoa(exponent, base))
       # check to see whether rounding created a denormalized number
       if result.lstrip('-').find('.') > 1:
           mantissa /= base
           exponent += 1
       else:
           return result


def _format_g(num, base, precision):
   """Equivalent of '%.*g' % (num, precision) in the given base."""
   exponent = _frexp(num, base)[1]
   f_format = _format_f(num, base, max(0, precision - exponent - 1))
   e_format = _format_e(num, base, precision - 1)
   if len(e_format) < len(f_format):
       return e_format
   else:
       return f_format


def num2str(num, base, format='g', precision=None):
   """Converts a number to its string representation in the given base."""
   if precision is None:
       precision = int(math.log(2 ** 53, base))
   num = gmpy.mpq(num)
   if format == 'f':
       return _format_f(num, base, precision)
   elif format == 'e':
       return _format_e(num, base, precision)
   elif format == 'g':
       return _format_g(num, base, precision)
   else:
       raise ValueError('Unsupported format: %%%s' % format)


def _main(argv=None):
   """Executed when this file is run as a script."""
   if argv is None:
       argv = sys.argv
   opts, args = getopt.getopt(argv[1:], 'e:f:g:i:o:', ['help'])
   opts = dict(opts)
   if '--help' in opts:
       print __doc__
   else:
       try:
           input_base = int(opts.get('-i', 10))
           output_base = int(opts.get('-o', 10))
       except ValueError:
           print >> sys.stderr, 'Error: Bases must be integers.'
           return 1
       if (input_base < 2 or input_base > 36 or
           output_base < 2 or output_base > 36):
           print >> sys.stderr, 'Error: Base must be 2-36.'
           return 1
       format_e = '-e' in opts
       format_f = '-f' in opts
       format_g = '-g' in opts
       precision = None
       if format_e + format_f + format_g > 1:
           print >> sys.stderr, 'Error: Only one format can be specified.'
           return 1
       elif format_e:
           format = 'e'
       elif format_f:
           format = 'f'
       elif format_g:
           format = 'g'
       else:
           format = 'g'
           precision = int(math.log(2 ** 53, output_base))
       if precision is None:
           precision = opts['-' + format]
           try:
               precision = int(precision)
           except ValueError:
               print >> sys.stderr, 'Error: Precision must be an integer.'
               return 1
       if precision < 0:
           print >> sys.stderr, 'Error: Precision must be nonnegative.'
           return 1
       return_value = 0
       for number in args:
           try:
               number = str2num(number, input_base)
               print num2str(number, output_base, format, precision)
           except (ValueError, TypeError):
               print >> sys.stderr, 'Invalid number: %s' % number
               return_value = 1
   return return_value


if __name__ == '__main__':
   raise SystemExit(_main())

JDozen - August 23, 2007 12:23 PM (GMT)
There should be a fairly simple way of base conversation for EXCEL. I'll bring it in when I'll find it. I think more folks are familiar with EXCEL than with python ;)

Dan - August 24, 2007 12:59 AM (GMT)
QUOTE (JDozen @ Aug 23 2007, 06:23 AM)
There should be a fairly simple way of base conversation for EXCEL. I'll bring it in when I'll find it. I think more folks are familiar with EXCEL than with python ;)

I don't have Excel on my home PC, but OpenOffice has BASE and DECIMAL functions. But they only work for integers.




Hosted for free by InvisionFree