Emulating Numbers

Registering Methods

Define a variable of type PyNumberMethods and include a pointer to it in the tp_as_number slot in the PyTypeObject definition of a new type.

PyNumberMethods counter_as_number = {
    counter_add,               /* binaryfunc nb_add;         /* __add__ */
    counter_sub,               /* binaryfunc nb_subtract;    /* __sub__ */
    counter_mul,               /* binaryfunc nb_multiply;    /* __mul__ */
    counter_div,               /* binaryfunc nb_divide;      /* __div__ */
    counter_mod,               /* binaryfunc nb_remainder;   /* __mod__ */
    counter_divmod,            /* binaryfunc nb_divmod;      /* __divmod__ */
    counter_pow,               /* ternaryfunc nb_power;      /* __pow__ */
    counter_neg,               /* unaryfunc nb_negative;     /* __neg__ */
    counter_pos,               /* unaryfunc nb_positive;     /* __pos__ */
    counter_abs,               /* unaryfunc nb_absolute;     /* __abs__ */
    counter_nonzero,           /* inquiry nb_nonzero;        /* __nonzero__ */
    counter_invert,            /* unaryfunc nb_invert;       /* __invert__ */
    counter_lshift,            /* binaryfunc nb_lshift;      /* __lshift__ */
    counter_rshift,            /* binaryfunc nb_rshift;      /* __rshift__ */
    counter_and,               /* binaryfunc nb_and;         /* __and__ */
    counter_xor,               /* binaryfunc nb_xor;         /* __xor__ */
    counter_or,                /* binaryfunc nb_or;          /* __or__ */
    counter_coerce,            /* coercion nb_coerce;        /* __coerce__ */
    counter_int,               /* unaryfunc nb_int;          /* __int__ */
    counter_long,              /* unaryfunc nb_long;         /* __long__ */
    counter_float,             /* unaryfunc nb_float;        /* __float__ */
    counter_oct,               /* unaryfunc nb_oct;          /* __oct__ */
    counter_hex,               /* unaryfunc nb_hex;          /* __hex__ */
};
Each callback which returns a pointer to a PyObject should return a new object of the same type. But this is not required.

If the nb_coerce slot contains a function, then coercion is attempted before other methods are called. Writing a good coercion routine can be difficult (in Python or C), but will save rewriting the little "add" and "multiply" functions.

If a slot does not contain a function, replace it with 0.

Using a helper function

For conversion of arguments, it is sometimes useful to have a helper function or two. This is to convert the data to a consistent format; for example, to an integer or float.

int object2int(obj, value)
  PyObject *obj;
  long *value;
  { PyObject *tmp;

    if (Counter_check(obj))
      *value = Counter_value(obj);
    else if (PyInt_Check(obj))
      *value = PyInt_AsLong(obj);
    else {
      tmp = PyNumber_Int(obj);
      if (tmp == NULL) {
        PyErr_SetString(PyExc_TypeError, "expecting number");
        return 0;
      }
      *value = PyInt_AsLong(tmp);
      Py_DECREF(tmp);
    }
    return 1;
  }

/* return the integer values of the two objects given */
int objects2ints(obj1, obj2, value1, value2)
  PyObject *obj1, *obj2;
  long *value1, *value2;
  {
    if (Counter_Check(obj1) && Counter_Check(obj2)) {
      *value1 = Counter_Value(obj1);
      *value2 = Counter_Value(obj2);
    } else if (Counter_Check(obj1)) {
      *value1 = Counter_Value(obj1);
      if (!object2int(obj2, value2))
        return 0;
    } else {
      *value2 = Counter_Value(obj2);
      if (!object2int(obj1, value1))
        return 0;
    }
    return 1;
  }

Arithmetic Functions

All the arithmetic operations are the forward operations ("__add__" vs. "__radd__"). Coercion is the proper mechanism to handle the reverse operations. But writing the functions as if either argument is of the type (with the aid of helper functions), can simplify the task for you.

PyObject *
counter_add(v, w)
  PyObject *v, *w;
  { long vi, wi;
    if (!objects2ints(v, w, &vi, &wi))
      return (PyObject *)NULL;
    return counter_NEW(vi + wi);
  }
PyObject *
counter_sub(v, w)
  PyObject *v, *w;
  { long vi, wi;
    if (!objects2ints(v, w, &vi, &wi))
      return (PyObject *)NULL;
    return counter_NEW(vi - wi);
  }
PyObject *
counter_mul(v, w)
  PyObject *v, *w;
  { long vi, wi;
    if (!objects2ints(v, w, &vi, &wi))
      return (PyObject *)NULL;
    return counter_NEW(vi * wi);
  }
PyObject *
counter_div(v, w)
  PyObject *v, *w;
  { long vi, wi;
    if (!objects2ints(v, w, &vi, &wi))
      return (PyObject *)NULL;
    if (wi == 0) {
      PyErr_SetString(PyExc_ZeroDivisionError,
        "integer division or modulo");
      return (PyObject *)NULL;
    }
    return counter_NEW(vi / wi);
  }
PyObject *
counter_mod(v, w)
  PyObject *v, *w;
  { long vi, wi;
    if (!objects2ints(v, w, &vi, &wi))
      return (PyObject *)NULL;
    if (wi == 0) {
      PyErr_SetString(PyExc_ZeroDivisionError,
        "integer division or modulo");
      return (PyObject *)NULL;
    }
    return counter_NEW(vi % wi);
  }
PyObject *
counter_divmod(v, w)
  PyObject *v, *w;
  { long vi, wi;

    if (!objects2ints(v, w, &vi, &wi))
      return (PyObject *)NULL;
    if (wi == 0) {
      PyErr_SetString(PyExc_ZeroDivisionError,
        "integer division or modulo");
      return (PyObject *)NULL;
    }
    return Py_BuildValue("(ii)", (vi / wi), (vi % wi));
  }
Some writers choose to make a divmod helper function which is called by the "nb_div", "nb_mod" and "nb_divmod" functions. If the division operation is complex, this can simplify the code (and make it easier to change later).

Advanced arithmetic functions

The "nb_nonzero" function is used for a truth value, it returns an int instead of a Python object.

It is sometimes unsure whether "__pos__" should return the object itself; a good rule is: when in doubt, create a new object.

PyObject *
counter_pow(v, w, z)
  PyObject *v, *w, *z;
  { long i, temp, iz, x, prev;

    iz = 0;
    if (!objects2ints(v, w, &i, &temp) ||
        (z != Py_None && !object2int(z &iz)))
      return (PyObject *)NULL;
    x = 1;
    while (i > 0) {
      prev = x;
      if (i & 1) {
        x *= temp;
        if (temp == 0)
          break;
        if (x / temp != prev) {
          PyErr_SetString(PyExc_OverflowError, "counter pow()");
          return (PyObject *)NULL;
        }
      }
      i >>= 1;
      if (i == 0)
        break;
      prev = temp;
      temp *= temp;
      if (prev != 0 && temp / prev != prev) {
        PyErr_SetString(PyExc_OverflowError, "counter pow()");
        return (PyObject *)NULL;
      }
      if (iz) {
        x %= iz;
        temp %= iz;
      }
    }
    if (iz) {
      PyObject *t1, *t2, *tr;
      long int div, mod;
      t1 = PyInt_FromLong(x);
      t2 = PyInt_FromLong(iz);
      if (t1 == NULL || t2 == NULL ||
          (tr = PyNumber_Divmod(t1, t2)) == NULL) {
        Py_XDECREF(t1);
        Py_XDECREF(t2);
        return (PyObject *)NULL;
      }
      Py_DECREF(t1);
      Py_DECREF(t2);
      if (!PyArg_Parse(tr, "(ii)", &div, &mod))
        return (PyObject *)NULL;
      x = mod;
    }
    return counter_NEW(x);
  }
PyObject *
counter_neg(self)
  PyObject *self;
  { return counter_NEW(-Counter_value(self));
  }
PyObject *
counter_pos(self)
  PyObject *self;
  { return counter_NEW(Counter_value(self));
  }
PyObject *
counter_abs(self)
  PyObject *self;
  { register long value = Counter_value(self);
    return counter_NEW(value < 0 ? -value : value);
  }
int
counter_nonzero(self)
  PyObject *self;
  { return (Counter_value(self) != 0;
  }
The pow() function above is taken from the Python 1.5.2 code as an example.

Bit-wise operations

PyObject *
counter_invert(self)
  PyObject *self;
  { return counter_NEW(~Counter_value(self));
  }
PyObject *
counter_lshift(v, w)
  PyObject *v, *w;
  { long vi, wi;

    if (!objects2ints(v, w, &vi, &wi))
      return (PyObject *)NULL;
    return counter_NEW(vi << wi);
  }
PyObject *
counter_rshift(v, w)
  PyObject *v, *w;
  { long vi, wi;

    if (!objects2ints(v, w, &vi, &wi))
      return (PyObject *)NULL;
    return counter_NEW(vi >> wi);
  }
PyObject *
counter_and(v, w)
  PyObject *v, *w;
  { long vi, wi;

    if (!objects2ints(v, w, &vi, &wi))
      return (PyObject *)NULL;
    return counter_NEW(vi & wi);
  }
PyObject *
counter_xor(v, w)
  PyObject *v, *w;
  { long vi, wi;
    if (!objects2ints(v, w, &vi, &wi))
      return (PyObject *)NULL;
    return counter_NEW(vi ^ wi);
  }
PyObject *
counter_or(v, w)
  PyObject *v, *w;
  { long vi, wi;
    if (!objects2ints(v, w, &vi, &wi))
      return (PyObject *)NULL;
    return counter_NEW(vi | wi);
  }

Conversion routines

The conversion routines are separated into three parts:

Numeric conversion
Return a Python number object, defined as a slot in PyNumberMethods; these are __float__, __int__ and __long__.
String conversion
Return a Python string object, defined as a slot in PyTypeObject; these are __repr__ and __str__.
Numeric string conversion
Return a Python string object, defined as a slot in PyNumberMethods; these are __hex__ and __oct__.

The __oct__ function should return a string that starts with "0" and the __hex__ function should return a string that starts with "0x".

PyObject *
counter_int(self)
  PyObject *self;
  { return PyInt_AsLong(Counter_value(self));
  }
PyObject *
counter_long(self)
  PyObject *self;
  { return PyLong_AsLong(Counter_value(self));
  }
PyObject *
counter_float(self)
  PyObject *self;
  { return PyFloat_AsDouble((double)Counter_value(self));
  }
PyObject *
counter_oct(self)
  PyObject *self;
  { char buf[20];
    sprintf(buf, "0%o", Counter_value(self));
    return Py_BuildValue("s", buf);
  }
PyObject *
counter_hex(self)
  PyObject *self;
  { char buf[20];
    sprintf(buf, "0x%x", Counter_value(self));
    return Py_BuildValue("s", buf);
  }

Coercion

In this example, everything but complex objects are converted to a counter object. If the other object is complex, then coerce the counter object to a complex number.

int
counter_coerce(v, w)
  PyObject **v, **w;
  { long i;

    /* the case where both are the same type should be handled already */
    if (PyComplex_Check(*w)) {
      *v = PyComplex_FromDoubles((double)Counter_value(*v), (double)0.0);
      Py_INCREF(*w);
      return 0;
    } else if (object2int(*w, &i))
      *w = counter_New(i);
      Py_INCREF(*v);
      return 0;
    }
    return 1;  /* couldn't do it */
  }
Copyright (C) 1999 Michael P. Reilly, All rights reserved.
Written by Michael P. Reilly.