# Standard Python packages
import math
import numbers
import itertools

# Special dependencies
import numpy

class InfiniteType:
    def __init__(self, multiplier=1.):
        if multiplier == 0.: raise ZeroDivisionError, "Cannot multiply infinity and zero."
        self._multiplier = multiplier

    def __repr__(self):
        if self is Infinity:
            return "Infinity"
        elif self is MinusInfinity:
            return "-Infinity"
        elif self._multiplier > 0.:
            return "Infinity*%g" % self._multiplier
        else:
            return "-Infinity*%g" % abs(self._multiplier)

    def __neg__(self):
        if self is Infinity:
            return MinusInfinity
        elif self is MinusInfinity:
            return Infinity
        else:
            return self * -1.

    def __mul__(self, number):
        if number == 0.: raise ZeroDivisionError, "Cannot multiply infinity and zero."
        return InfiniteType(self._multiplier * number)

    def __div__(self, number):
        if isinstance(number, InfiniteType): raise ZeroDivisionError, "Cannot divide infinity and infinity."
        if number == 0: raise ZeroDivisionError, "Cannot divide infinity and zero."
        return InfiniteType(self._multiplier / number)

    def __truediv__(self, number):
        return self.__div__(number)

#: Symbol representing infinity; can be multiplied by any scalar.
#:
#: Note: in a product, Infinity must *precede* the scalar::
#:
#:    >>> Infinity * -5.     # right
#:    >>> -5. * Infinity     # wrong
Infinity = InfiniteType()
MinusInfinity = InfiniteType(-1.)

#: A small number (1e-5), used to avoid numerical round-off issues in
#: comparisons.
#:
#: The following can be used to set epsilon (without any
#: multiple-reference issues)::
#: 
#:    >>> import cassius
#:    >>> cassius.epsilon = 1e-10
epsilon = 1e-5

######################################################### Utility functions

def _roundlevel_nsigfigs(num, n):
  if num == 0.: return 1
  return n - int(math.ceil(math.log10(abs(num))))

def str_round(num, n):
  """Round a number to n digits and return the result as a string."""
  num = round(num, n)
  format = "%."+str(max(n, 0))+"f"
  return format % num

def round_sigfigs(num, n):
  "Round a number to n significant figures."
  return round(num, _roundlevel_nsigfigs(num, n))

def str_sigfigs(num, n):
  """Round a number to n significant figures and return the result as
a string."""
  level = _roundlevel_nsigfigs(num, n)
  num = round(num, level)
  format = "%."+str(max(level, 0))+"f"
  return format % num

def round_errpair(num, err, n=2):
  """Round a number and its uncertainty to n significant figures in
  the uncertainty (default is two)."""

  level = _roundlevel_nsigfigs(err, n)
  return round(num, level), round(err, level)

def str_errpair(num, err, n=2):
  """Round a number and its uncertainty to n significant figures in the
uncertainty (default is two) and return the result as a string."""
  level = _roundlevel_nsigfigs(err, n)
  num = round(num, level)
  err = round(err, level)