Python - Our key to Efficiency

mxNumber - Extended Numeric Types for Python

Introduction

    Moshe Zadka, one of the Python Core Developers, has been pushing for a new rational number type recently (at the IPC9 conference) and also implemented a proof-of-concept implementation of his rational PEP 239.

    Since the GNU Multi-Precision Library (GMP) already has all these number types and also provides what people want most when it comes to numbers: precision and speed, I thought that wrapping these as Python types would be a good idea. I know that Alex Martelli has been working on a similar approach, but that project (http://gmpy.sourceforge.net/) seems to be inactive and I wanted to find out whether the new coercion patches in 2.1 actually make life easier for the extensions writer.

    Anyway, even though the GMP is available for most Unix platforms and MacOS, there was no relyable port for Windows. This was a show-stopper for me, so I decided to port GMP to Windows, which was harder than I thought, but well, it's done now. You can get the GMP Windows port from here.

    The mxNumber package defines the following number types and implements most interoperability features needed to use these as if they were native Python number types:

    Integer
    This is an arbitrary precision integer object (like longs in Python) based on the GMP mpz type.

    Rational
    This is an arbitrary precision rational object based on the GMP mpq type. It uses two Integer objects for numerator and denominator which are always normalized (they don't have common factors except 1).

    Float
    This is a variable precision float object based on the GMP mpf type. The precision can be defined at creation time. Floats created in numeric operations use the packages current default precision as basis.

    More types will eventually show up in the package as time permits. I have a decimal type on my TODO list and there has been discussion about a bitfield type based on Integers on comp.lang.python.

    Conversion from and to other formats

    TBD

    Rounding errors

    TBD

    Immutability

    One other thing to keep in mind when working with mx.Number objects is that they are immutable (like tuples). Once an object is created you can not change its value. Instead, you will have to create a new object with modified values.

    The advantage of having immutable objects is that they can be used as dictionary keys and cached in various ways.

    Interaction with other types

    mx.Number objects can be compared and hashed, making them compatible to the dictionary implementation Python uses (they can be used as keys).

    The copy protocol, standard arithmetic and pickle'ing are also supported.

    String formats

    TBD

    Speed and Memory

    TBD

Interface

    The package provides the following data structures for working with numeric values. These are:

    1. Integer for storing arbitrary precision whole numbers,
    2. Rational for storing exact rational numbers with infinite precision,
    3. Float for storing configurable precision floating point values

    Integer Constructors

      Integer(value)
      Constructs an Integer instance from the given value.

    Integer Methods

      An Integer object has the following methods (yes, numbers can have methods too :-).

      .copy([memo])
      Return a new reference for the instance. This function is used for the copy-protocol. Real copying doesn't take place, since the instances are immutable.

      .even()
      True iff number is even.

      .factorial()
      Return the factorial of the number.

      .gcd(other)
      Return the (positive) GCD of number and other.

      .hamdist(other)
      Return the Hamming Distance between number and other. Both values must be positive.

      .has_root(n)
      Return 1/0 iff number has an (exact) n-th root.

      .is_perfect_power()
      True iff number is a perfect power.

      .is_perfect_square()
      True iff number is a perfect square.

      .jacobi()
      Return the Jacobi symbol for number.

      .lcm(other)
      Return the (positive) LCM of number and other.

      .legendre()
      Return the Legendre symbol for number.

      .odd()
      True iff number is odd.

      .over(k)
      Return the binomial coefficient number over k.

      .popcount()
      Return the population count for number. Number must be positive.

      .prime(reps)
      Return 1 if number is a prime, 2 if number is probably prime and 3 if number surely prime according to the Miller-Rabin test. 0 is returned for non-primes. Higher values for reps increase the probability.

      .root(n)
      Return the (truncated) n-th root of the number.

      .sign()
      Return the sign of the number.

      .sqrt()
      Return the square root of the number.

    Integer Attributes

      Integer objects currently don't have attributes.

    Rational Constructors

      Rational(value[,denominator])
      Constructs a Rational instance from the given value. If denominator is given, value is interpreted as numerator.

      FareyRational(value, maxden)
      Returns a Rational-object reflecting the given value and using maxden as maximum denominator.

    Rational Methods

      An Rational object has the following methods (yes, numbers can have methods too :-).

      .copy([memo])
      Return a new reference for the instance. This function is used for the copy-protocol. Real copying doesn't take place, since the instances are immutable.

      .format(base, precision=0)
      Return a string representing the Rational in the given base.

      For base10, a precision value >= 0 will return an approximate decimal point representation of the Rational, while setting precision to 0 causes the 'nominator/denominator' format to be used. precision has no meaning for non-base10 values.

      Note that representation using the decimal point are only accurate to approximately 17 digits (C doubles are used for the formatting).

      .sign()
      Return the sign of the number.

    Rational Attributes

      An Rational object has the following attributes.

      .denominator
      Denominator Integer-object of the Rational.

      .numerator
      Numerator Integer-object of the Rational.

    Float Constructors

      Float(value[,precision=64])
      Constructs a Float instance from the given value.

      precision gives the number of bits which should at least be used to store the floating point value.

    Float Methods

      A Float object has the following methods (yes, numbers can have methods too :-).

      .sign()
      Return the sign of the Float.

      .format(base, precision=0)
      Return a string representing the Float.

      precision defines the maximum number of significant digits to use, 0 means to let the implementation choose the value depending on the Float's storage precision.

    Float Attributes

      A Float object has the following attributes.

      .precision
      The number of bits used by the object to store the floating point value.

    Constants

      The package defines these constants:

      Error
      This exception will be raised for problems related to the types. Exceptions will normally only be raised by functions, methods or arithmetic operations.

      IntegerType, RationalType, FloatType
      The type objects for the objects.

      mxNumberAPI
      The C API wrapped by a C object. See mxNumber.h for details.

    Helper functions

      The package defines these additional functions:

      XXX()
      XXX

    If you find any bugs, please report them to me so that I can fix them for the next release.

mx.Number Arithmetic

    To be written...

    The different types of this package are coerced in the following ways (whereever possible):

    
                     mx.Number.Float
                           ^
                           |
           --------> Python float
          |                ^
          |                |
          |         mx.Number.Rational
          |                ^
          |                |
    Python long --> mx.Number.Integer
          ^                ^
          |                |
           --------  Python integer
    
            

Submodules

    The package currently does not expose any submodules.

Examples of Use

    TBD

    This snippet demonstrates some of the possible interactions of mxNumber types and Python number types:

    >>> from mx.Number import *
    
    >>> # To be written...
    
    	

    More examples will appear in the Examples subdirectory of the package.

Supported Data Types in the C-API

    Please have look at the file mxNumber.h for details.

    To access the module, do the following (note the similarities with Python's way of accessing functions from a module):

    #include "mxNumber.h"
    
    ...
        PyObject *v;
    
        /* Import the mxNumber module */
        if (mxNumber_ImportModuleAndAPI())
    	goto onError;
    
        /* Access functions from the exported C API through mxNumber */
        v = mxNumber.Integer_FromString("123");
        if (!v)
    	goto onError;
    
        /* Type checking */
        if (mxNumber_Check(v))
            printf("Works.\n");
    
        Py_DECREF(v);
    ...
          

Package Structure

    [Number]
           Doc/
           [Examples]
           [mxNumber]
                  win32/
                  test.py
           LazyModule.py
           Number.py
          

    Names with trailing / are plain directories, ones with []-brackets are Python packages, ones with ".py" extension are Python submodules.

    The package imports all symbols from the extension module and also registers the types so that they become compatible to the pickle and copy mechanisms in Python.

Support

What I'd like to hear from you...

    • Comments, ideas, bug-fixes :-)

Copyright & License

History & Future

    Things that still need to be done (patches are welcome, consulting based work on these issues is also possible via eGenix.com):

    • Write more documentation.

    • Provide some examples.

    • Add more coercion paths.

    • Remove some currently working coercion paths (e.g. auto-coercion of strings).

    • Add a mutable Bitfield type.

    • Add support for integer shift operations.

    • Distant future: add support for MPFR (fixed precision floats) and MPFI (interval arithmetics).

    • Add better Rational() string formatting, e.g. enhance the decimal point output to not rely on C doubles anymore.

    Things that changed from 0.4.0 to 0.5.0:

    • Fixed a bug which only shows up in Python debug builds: _Py_ForgetReference() was not used correctly by the free list implementations.

    Things that changed from 0.3.0 to 0.4.0:

    • Changed the str(Rational) output to match that of Python floats; the repr(Rational) will still generate the 'nom/denom' style output.

    • Added Rational and Float .format() methods

    • Changed the str(Float) output to match that of Python floats; the repr(Float) will still generate the '[-]x.xxxe+yyy' style output.

    Things that changed from 0.2.0 to 0.3.0:

    • Fixed comparison of mx.Number types and standard Python types (by adding coercion functions to all types, sigh).

    • Added more coercion paths. Mixed operations with Floats now work as expected.

    • Added a whole bunch of new Integer methods.

    Things that changed from 0.1.0 to 0.2.0:

    • Fixed string representation of Float(0) to output "0.0e0".

    • Added Rational number string parser and fixed Integer string parser to only recognize numbers which are also accepted by the builtin int() (leading, trailing whitespace, nothing else).

    Version 0.1.0 was the first public release.