/* -*- c -*- */

/* The purpose of this module is to add faster math for array scalars
   that does not go through the ufunc machinery

   but still supports error-modes.
*/

#define NPY_NO_DEPRECATED_API NPY_API_VERSION

#include "Python.h"
#include "numpy/arrayobject.h"
#include "numpy/ufuncobject.h"
#include "numpy/arrayscalars.h"

#include "npy_pycompat.h"

#include "numpy/halffloat.h"
#include "scalarmathmodule.h"

/* Basic operations:
 *
 *  BINARY:
 *
 * add, subtract, multiply, divide, remainder, divmod, power,
 * floor_divide, true_divide
 *
 * lshift, rshift, and, or, xor (integers only)
 *
 * UNARY:
 *
 * negative, positive, absolute, nonzero, invert, int, long, float, oct, hex
 *
 */

/**begin repeat
 *  #name = byte, short, int, long, longlong#
 *  #type = npy_byte, npy_short, npy_int, npy_long, npy_longlong#
 */
static void
@name@_ctype_add(@type@ a, @type@ b, @type@ *out) {
    *out = a + b;
    if ((*out^a) >= 0 || (*out^b) >= 0) {
        return;
    }
    npy_set_floatstatus_overflow();
    return;
}
static void
@name@_ctype_subtract(@type@ a, @type@ b, @type@ *out) {
    *out = a - b;
    if ((*out^a) >= 0 || (*out^~b) >= 0) {
        return;
    }
    npy_set_floatstatus_overflow();
    return;
}
/**end repeat**/

/**begin repeat
 *  #name = ubyte, ushort, uint, ulong, ulonglong#
 *  #type = npy_ubyte, npy_ushort, npy_uint, npy_ulong, npy_ulonglong#
 */
static void
@name@_ctype_add(@type@ a, @type@ b, @type@ *out) {
    *out = a + b;
    if (*out >= a && *out >= b) {
        return;
    }
    npy_set_floatstatus_overflow();
    return;
}
static void
@name@_ctype_subtract(@type@ a, @type@ b, @type@ *out) {
    *out = a - b;
    if (a >= b) {
        return;
    }
    npy_set_floatstatus_overflow();
    return;
}
/**end repeat**/

#ifndef NPY_SIZEOF_BYTE
#define NPY_SIZEOF_BYTE 1
#endif

/**begin repeat
 *
 * #name = byte, ubyte, short, ushort,
 *         int, uint, long, ulong#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort,
 *         npy_int, npy_uint, npy_long, npy_ulong#
 * #big =  npy_int, npy_uint, npy_int, npy_uint,
 *         npy_longlong, npy_ulonglong, npy_longlong, npy_ulonglong#
 * #NAME = BYTE, UBYTE, SHORT, USHORT,
 *         INT, UINT, LONG, ULONG#
 * #SIZENAME = BYTE*2, SHORT*2, INT*2, LONG*2#
 * #SIZE = INT*4,LONGLONG*4#
 * #neg = (1,0)*4#
 */
#if NPY_SIZEOF_@SIZE@ > NPY_SIZEOF_@SIZENAME@
static void
@name@_ctype_multiply(@type@ a, @type@ b, @type@ *out) {
    @big@ temp;
    temp = ((@big@) a) * ((@big@) b);
    *out = (@type@) temp;
#if @neg@
    if (temp > NPY_MAX_@NAME@ || temp < NPY_MIN_@NAME@)
#else
        if (temp > NPY_MAX_@NAME@)
#endif
            npy_set_floatstatus_overflow();
    return;
}
#endif
/**end repeat**/

/**begin repeat
 *
 * #name = int, uint, long, ulong,
 *         longlong, ulonglong#
 * #type = npy_int, npy_uint, npy_long, npy_ulong,
 *         npy_longlong, npy_ulonglong#
 * #SIZE = INT*2, LONG*2, LONGLONG*2#
 */
#if NPY_SIZEOF_LONGLONG == NPY_SIZEOF_@SIZE@
static void
@name@_ctype_multiply(@type@ a, @type@ b, @type@ *out) {
    if (npy_mul_with_overflow_@name@(out, a, b)) {
        npy_set_floatstatus_overflow();
    }
    return;
}
#endif
/**end repeat**/

/**begin repeat
 *
 * #name = byte, ubyte, short, ushort, int, uint,
 *         long, ulong, longlong, ulonglong#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong#
 * #neg = (1,0)*5#
 */
static void
@name@_ctype_divide(@type@ a, @type@ b, @type@ *out) {
    if (b == 0) {
        npy_set_floatstatus_divbyzero();
        *out = 0;
    }
#if @neg@
    else if (b == -1 && a < 0 && a == -a) {
        npy_set_floatstatus_overflow();
        *out = a / b;
    }
#endif
    else {
#if @neg@
        @type@ tmp;
        tmp = a / b;
        if (((a > 0) != (b > 0)) && (a % b != 0)) {
            tmp--;
        }
        *out = tmp;
#else
        *out = a / b;
#endif
    }
}

#define @name@_ctype_floor_divide @name@_ctype_divide
static void
@name@_ctype_remainder(@type@ a, @type@ b, @type@ *out) {
    if (a == 0 || b == 0) {
        if (b == 0) npy_set_floatstatus_divbyzero();
        *out = 0;
        return;
    }
#if @neg@
    else if ((a > 0) == (b > 0)) {
        *out = a % b;
    }
    else {
        /* handled like Python does */
        *out = a % b;
        if (*out) *out += b;
    }
#else
    *out = a % b;
#endif
}
/**end repeat**/

/**begin repeat
 *
 * #name = byte, ubyte, short, ushort, int, uint, long,
 *         ulong, longlong, ulonglong#
 * #otyp = npy_float*4, npy_double*6#
 */
#define @name@_ctype_true_divide(a, b, out)     \
    *(out) = ((@otyp@) (a)) / ((@otyp@) (b));
/**end repeat**/

/* b will always be positive in this call */
/**begin repeat
 *
 * #name = byte, ubyte, short, ushort, int, uint,
 *         long, ulong, longlong, ulonglong#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong#
 * #upc = BYTE, UBYTE, SHORT, USHORT, INT, UINT,
 *        LONG, ULONG, LONGLONG, ULONGLONG#
 */
static void
@name@_ctype_power(@type@ a, @type@ b, @type@ *out) {
    @type@ temp, ix, mult;
    /* code from Python's intobject.c, with overflow checking removed. */
    temp = a;
    ix = 1;
    while (b > 0) {
        if (b & 1) {
            @name@_ctype_multiply(ix, temp, &mult);
            ix = mult;
            if (temp == 0) {
                break;
            }
        }
        b >>= 1;        /* Shift exponent down by 1 bit */
        if (b==0) {
            break;
        }
        /* Square the value of temp */
        @name@_ctype_multiply(temp, temp, &mult);
        temp = mult;
    }
    *out = ix;
}
/**end repeat**/



/* QUESTION:  Should we check for overflow / underflow in (l,r)shift? */

/**begin repeat
 * #name = byte, ubyte, short, ushort, int, uint,
 *         long, ulong, longlong, ulonglong#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong#
 */

/**begin repeat1
 * #oper = and, xor, or, lshift, rshift#
 * #op = &, ^, |, <<, >>#
 */

#define @name@_ctype_@oper@(arg1, arg2, out) *(out) = (arg1) @op@ (arg2)

/**end repeat1**/

/**end repeat**/

/**begin repeat
 * #name = float, double, longdouble#
 * #type = npy_float, npy_double, npy_longdouble#
 */
static @type@ (*_basic_@name@_floor)(@type@);
static @type@ (*_basic_@name@_sqrt)(@type@);
static @type@ (*_basic_@name@_fmod)(@type@, @type@);
#define @name@_ctype_add(a, b, outp) *(outp) = a + b
#define @name@_ctype_subtract(a, b, outp) *(outp) = a - b
#define @name@_ctype_multiply(a, b, outp) *(outp) = a * b
#define @name@_ctype_divide(a, b, outp) *(outp) = a / b
#define @name@_ctype_true_divide @name@_ctype_divide
#define @name@_ctype_floor_divide(a, b, outp)   \
    *(outp) = _basic_@name@_floor((a) / (b))
/**end repeat**/

static npy_half (*_basic_half_floor)(npy_half);
static npy_half (*_basic_half_sqrt)(npy_half);
static npy_half (*_basic_half_fmod)(npy_half, npy_half);

#define half_ctype_add(a, b, outp) *(outp) = \
        npy_float_to_half(npy_half_to_float(a) + npy_half_to_float(b))
#define half_ctype_subtract(a, b, outp) *(outp) = \
        npy_float_to_half(npy_half_to_float(a) - npy_half_to_float(b))
#define half_ctype_multiply(a, b, outp) *(outp) = \
        npy_float_to_half(npy_half_to_float(a) * npy_half_to_float(b))
#define half_ctype_divide(a, b, outp) *(outp) = \
        npy_float_to_half(npy_half_to_float(a) / npy_half_to_float(b))
#define half_ctype_true_divide half_ctype_divide
#define half_ctype_floor_divide(a, b, outp)   \
        *(outp) = npy_float_to_half(_basic_float_floor(npy_half_to_float(a) / \
        npy_half_to_float(b)))

/**begin repeat
 * #name = cfloat, cdouble, clongdouble#
 * #rname = float, double, longdouble#
 * #rtype = npy_float, npy_double, npy_longdouble#
 * #c = f,,l#
 */
#define @name@_ctype_add(a, b, outp) do{        \
    (outp)->real = (a).real + (b).real;         \
    (outp)->imag = (a).imag + (b).imag;         \
    } while(0)
#define @name@_ctype_subtract(a, b, outp) do{   \
    (outp)->real = (a).real - (b).real;         \
    (outp)->imag = (a).imag - (b).imag;         \
    } while(0)
#define @name@_ctype_multiply(a, b, outp) do{                   \
    (outp)->real = (a).real * (b).real - (a).imag * (b).imag;   \
    (outp)->imag = (a).real * (b).imag + (a).imag * (b).real;   \
    } while(0)
/* Note: complex division by zero must yield some complex inf */
#define @name@_ctype_divide(a, b, outp) do{                         \
    @rtype@ d = (b).real*(b).real + (b).imag*(b).imag;              \
    if (d != 0) {                                                   \
        (outp)->real = ((a).real*(b).real + (a).imag*(b).imag)/d;   \
        (outp)->imag = ((a).imag*(b).real - (a).real*(b).imag)/d;   \
    }                                                               \
    else {                                                          \
        (outp)->real = (a).real/d;                                  \
        (outp)->imag = (a).imag/d;                                  \
    }                                                               \
    } while(0)
#define @name@_ctype_true_divide @name@_ctype_divide
#define @name@_ctype_floor_divide(a, b, outp) do {      \
    (outp)->real = _basic_@rname@_floor                 \
    (((a).real*(b).real + (a).imag*(b).imag) /          \
     ((b).real*(b).real + (b).imag*(b).imag));          \
    (outp)->imag = 0;                                   \
    } while(0)
/**end repeat**/

/**begin repeat
 * #name = float, double, longdouble#
 * #type = npy_float, npy_double, npy_longdouble#
 */
static void
@name@_ctype_remainder(@type@ a, @type@ b, @type@ *out) {
    @type@ mod;
    mod = _basic_@name@_fmod(a, b);
    if (mod && (((b < 0) != (mod < 0)))) {
        mod += b;
    }
    *out = mod;
}
/**end repeat**/

static void
half_ctype_remainder(npy_half a, npy_half b, npy_half *out) {
    float mod, fa = npy_half_to_float(a), fb = npy_half_to_float(b);
    mod = _basic_float_fmod(fa, fb);
    if (mod && (((fb < 0) != (mod < 0)))) {
        mod += fb;
    }
    *out = npy_float_to_half(mod);
}


/**begin repeat
 * #name = byte, ubyte, short, ushort, int, uint, long, ulong,
 *         longlong, ulonglong, half, float, double, longdouble,
 *         cfloat, cdouble, clongdouble#
 */
#define @name@_ctype_divmod(a, b, out, out2) {  \
    @name@_ctype_floor_divide(a, b, out);       \
    @name@_ctype_remainder(a, b, out2);         \
    }
/**end repeat**/

/**begin repeat
 * #name = float, double, longdouble#
 * #type = npy_float, npy_double, npy_longdouble#
 */
static npy_@name@ (*_basic_@name@_pow)(@type@ a, @type@ b);

static void
@name@_ctype_power(@type@ a, @type@ b, @type@ *out)
{
    *out = _basic_@name@_pow(a, b);
}
/**end repeat**/
static void
half_ctype_power(npy_half a, npy_half b, npy_half *out)
{
    const npy_float af = npy_half_to_float(a);
    const npy_float bf = npy_half_to_float(b);
    const npy_float outf = _basic_float_pow(af,bf);
    *out = npy_float_to_half(outf);
}

/**begin repeat
 * #name = byte, ubyte, short, ushort, int, uint,
 *         long, ulong, longlong, ulonglong,
 *         float, double, longdouble#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_float, npy_double, npy_longdouble#
 * #uns = (0,1)*5,0*3#
 */
static void
@name@_ctype_negative(@type@ a, @type@ *out)
{
#if @uns@
    npy_set_floatstatus_overflow();
#endif
    *out = -a;
}
/**end repeat**/

static void
half_ctype_negative(npy_half a, npy_half *out)
{
    *out = a^0x8000u;
}


/**begin repeat
 * #name = cfloat, cdouble, clongdouble#
 * #type = npy_cfloat, npy_cdouble, npy_clongdouble#
 */
static void
@name@_ctype_negative(@type@ a, @type@ *out)
{
    out->real = -a.real;
    out->imag = -a.imag;
}
/**end repeat**/

/**begin repeat
 * #name = byte, ubyte, short, ushort, int, uint,
 *         long, ulong, longlong, ulonglong,
 *         half, float, double, longdouble#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_double, npy_longdouble#
 */
static void
@name@_ctype_positive(@type@ a, @type@ *out)
{
    *out = a;
}
/**end repeat**/

/*
 * Get the nc_powf, nc_pow, and nc_powl functions from
 * the data area of the power ufunc in umathmodule.
 */

/**begin repeat
 * #name = cfloat, cdouble, clongdouble#
 * #type = npy_cfloat, npy_cdouble, npy_clongdouble#
 */
static void
@name@_ctype_positive(@type@ a, @type@ *out)
{
    out->real = a.real;
    out->imag = a.imag;
}

static void (*_basic_@name@_pow)(@type@ *, @type@ *, @type@ *);

static void
@name@_ctype_power(@type@ a, @type@ b, @type@ *out)
{
    _basic_@name@_pow(&a, &b, out);
}
/**end repeat**/


/**begin repeat
 * #name = ubyte, ushort, uint, ulong, ulonglong#
 */

#define @name@_ctype_absolute @name@_ctype_positive

/**end repeat**/


/**begin repeat
 * #name = byte, short, int, long, longlong,
 *         float, double, longdouble#
 * #type = npy_byte, npy_short, npy_int, npy_long, npy_longlong,
 *         npy_float, npy_double, npy_longdouble#
 */
static void
@name@_ctype_absolute(@type@ a, @type@ *out)
{
    *out = (a < 0 ? -a : a);
}
/**end repeat**/

static void
half_ctype_absolute(npy_half a, npy_half *out)
{
    *out = a&0x7fffu;
}

/**begin repeat
 * #name = cfloat, cdouble, clongdouble#
 * #type = npy_cfloat, npy_cdouble, npy_clongdouble#
 * #rname = float, double, longdouble#
 * #rtype = npy_float, npy_double, npy_longdouble#
 */
static void
@name@_ctype_absolute(@type@ a, @rtype@ *out)
{
    *out = _basic_@rname@_sqrt(a.real*a.real + a.imag*a.imag);
}
/**end repeat**/

/**begin repeat
 * #name = byte, ubyte, short, ushort, int, uint, long,
 *         ulong, longlong, ulonglong#
 */

#define @name@_ctype_invert(a, out) *(out) = ~a;

/**end repeat**/

/*** END OF BASIC CODE **/


/* The general strategy for commutative binary operators is to
 *
 * 1) Convert the types to the common type if both are scalars (0 return)
 * 2) If both are not scalars use ufunc machinery (-2 return)
 * 3) If both are scalars but cannot be cast to the right type
 * return NotImplmented (-1 return)
 *
 * 4) Perform the function on the C-type.
 * 5) If an error condition occurred, check to see
 * what the current error-handling is and handle the error.
 *
 * 6) Construct and return the output scalar.
 */

/**begin repeat
 * #name = byte, ubyte, short, ushort, int, uint,
 *         long, ulong, longlong, ulonglong,
 *         half, float, longdouble,
 *         cfloat, cdouble, clongdouble#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_longdouble,
 *         npy_cfloat, npy_cdouble, npy_clongdouble#
 * #Name = Byte, UByte, Short, UShort, Int, UInt,
 *         Long, ULong, LongLong, ULongLong,
 *         Half, Float, LongDouble,
 *         CFloat, CDouble, CLongDouble#
 * #TYPE = NPY_BYTE, NPY_UBYTE, NPY_SHORT, NPY_USHORT, NPY_INT, NPY_UINT,
 *         NPY_LONG, NPY_ULONG, NPY_LONGLONG, NPY_ULONGLONG,
 *         NPY_HALF, NPY_FLOAT, NPY_LONGDOUBLE,
 *         NPY_CFLOAT, NPY_CDOUBLE, NPY_CLONGDOUBLE#
 */

static int
_@name@_convert_to_ctype(PyObject *a, @type@ *arg1)
{
    PyObject *temp;

    if (PyArray_IsScalar(a, @Name@)) {
        *arg1 = PyArrayScalar_VAL(a, @Name@);
        return 0;
    }
    else if (PyArray_IsScalar(a, Generic)) {
        PyArray_Descr *descr1;

        if (!PyArray_IsScalar(a, Number)) {
            return -1;
        }
        descr1 = PyArray_DescrFromTypeObject((PyObject *)Py_TYPE(a));
        if (PyArray_CanCastSafely(descr1->type_num, @TYPE@)) {
            PyArray_CastScalarDirect(a, descr1, arg1, @TYPE@);
            Py_DECREF(descr1);
            return 0;
        }
        else {
            Py_DECREF(descr1);
            return -1;
        }
    }
    else if (PyArray_GetPriority(a, NPY_PRIORITY) > NPY_PRIORITY) {
        return -2;
    }
    else if ((temp = PyArray_ScalarFromObject(a)) != NULL) {
        int retval = _@name@_convert_to_ctype(temp, arg1);

        Py_DECREF(temp);
        return retval;
    }
    return -2;
}

/**end repeat**/


/* Same as above but added exact checks against known python types for speed */

/**begin repeat
 * #name = double#
 * #type = npy_double#
 * #Name = Double#
 * #TYPE = NPY_DOUBLE#
 * #PYCHECKEXACT = PyFloat_CheckExact#
 * #PYEXTRACTCTYPE = PyFloat_AS_DOUBLE#
 */

static int
_@name@_convert_to_ctype(PyObject *a, @type@ *arg1)
{
    PyObject *temp;

    if (@PYCHECKEXACT@(a)){
        *arg1 = @PYEXTRACTCTYPE@(a);
        return 0;
    }

    if (PyArray_IsScalar(a, @Name@)) {
        *arg1 = PyArrayScalar_VAL(a, @Name@);
        return 0;
    }
    else if (PyArray_IsScalar(a, Generic)) {
        PyArray_Descr *descr1;

        if (!PyArray_IsScalar(a, Number)) {
            return -1;
        }
        descr1 = PyArray_DescrFromTypeObject((PyObject *)Py_TYPE(a));
        if (PyArray_CanCastSafely(descr1->type_num, @TYPE@)) {
            PyArray_CastScalarDirect(a, descr1, arg1, @TYPE@);
            Py_DECREF(descr1);
            return 0;
        }
        else {
            Py_DECREF(descr1);
            return -1;
        }
    }
    else if (PyArray_GetPriority(a, NPY_PRIORITY) > NPY_PRIORITY) {
        return -2;
    }
    else if ((temp = PyArray_ScalarFromObject(a)) != NULL) {
        int retval = _@name@_convert_to_ctype(temp, arg1);

        Py_DECREF(temp);
        return retval;
    }
    return -2;
}

/**end repeat**/


/**begin repeat
 * #name = byte, ubyte, short, ushort, int, uint,
 *         long, ulong, longlong, ulonglong,
 *         half, float, double, cfloat, cdouble#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_double, npy_cfloat, npy_cdouble#
 */
static int
_@name@_convert2_to_ctypes(PyObject *a, @type@ *arg1,
                           PyObject *b, @type@ *arg2)
{
    int ret;
    ret = _@name@_convert_to_ctype(a, arg1);
    if (ret < 0) {
        return ret;
    }
    ret = _@name@_convert_to_ctype(b, arg2);
    if (ret < 0) {
        return ret;
    }
    return 0;
}
/**end repeat**/

/**begin repeat
 * #name = longdouble, clongdouble#
 * #type = npy_longdouble, npy_clongdouble#
 */

static int
_@name@_convert2_to_ctypes(PyObject *a, @type@ *arg1,
                           PyObject *b, @type@ *arg2)
{
    int ret;
    ret = _@name@_convert_to_ctype(a, arg1);
    if (ret < 0) {
        return ret;
    }
    ret = _@name@_convert_to_ctype(b, arg2);
    if (ret == -2) {
        ret = -3;
    }
    if (ret < 0) {
        return ret;
    }
    return 0;
}

/**end repeat**/


#if defined(NPY_PY3K)
#define CODEGEN_SKIP_divide_FLAG
#endif

/**begin repeat
 *
 * #name = (byte, ubyte, short, ushort, int, uint,
 *             long, ulong, longlong, ulonglong)*13,
 *         (half, float, double, longdouble,
 *             cfloat, cdouble, clongdouble)*6,
 *         (half, float, double, longdouble)*2#
 * #Name = (Byte, UByte, Short, UShort, Int, UInt,
 *             Long, ULong,LongLong,ULongLong)*13,
 *         (Half, Float, Double, LongDouble,
 *             CFloat, CDouble, CLongDouble)*6,
 *         (Half, Float, Double, LongDouble)*2#
 * #type = (npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *             npy_long, npy_ulong, npy_longlong, npy_ulonglong)*13,
 *         (npy_half, npy_float, npy_double, npy_longdouble,
 *             npy_cfloat, npy_cdouble, npy_clongdouble)*6,
 *         (npy_half, npy_float, npy_double, npy_longdouble)*2#
 *
 * #oper = add*10, subtract*10, multiply*10, divide*10, remainder*10,
 *         divmod*10, floor_divide*10, lshift*10, rshift*10, and*10,
 *         or*10, xor*10, true_divide*10,
 *         add*7, subtract*7, multiply*7, divide*7, floor_divide*7, true_divide*7,
 *         divmod*4, remainder*4#
 *
 * #fperr = 1*70,0*50,1*10,
 *          1*42,
 *          1*8#
 * #twoout = 0*50,1*10,0*70,
 *           0*42,
 *           1*4,0*4#
 * #otype = (npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *             npy_long, npy_ulong, npy_longlong, npy_ulonglong)*12,
 *         npy_float*4, npy_double*6,
 *         (npy_half, npy_float, npy_double, npy_longdouble,
 *             npy_cfloat, npy_cdouble, npy_clongdouble)*6,
 *         (npy_half, npy_float, npy_double, npy_longdouble)*2#
 * #OName = (Byte, UByte, Short, UShort, Int, UInt,
 *              Long, ULong, LongLong, ULongLong)*12,
 *          Float*4, Double*6,
 *          (Half, Float, Double, LongDouble,
 *              CFloat, CDouble, CLongDouble)*6,
 *          (Half, Float, Double, LongDouble)*2#
 */

#if !defined(CODEGEN_SKIP_@oper@_FLAG)

static PyObject *
@name@_@oper@(PyObject *a, PyObject *b)
{
    PyObject *ret;
    @type@ arg1, arg2;
    /*
     * NOTE: In gcc >= 4.1, the compiler will reorder floating point
     *       operations and floating point error state checks. In
     *       particular, the arithmetic operations were being reordered
     *       so that the errors weren't caught.  Declaring this output
     *       variable volatile was the minimal fix for the issue.
     *       (Ticket #1671)
     */
    volatile @otype@ out;
#if @twoout@
    @otype@ out2;
    PyObject *obj;
#endif

#if @fperr@
    int retstatus;
    int first;
#endif

    switch(_@name@_convert2_to_ctypes(a, &arg1, b, &arg2)) {
        case 0:
            break;
        case -1:
            /* one of them can't be cast safely must be mixed-types*/
            return PyArray_Type.tp_as_number->nb_@oper@(a,b);
        case -2:
            /* use default handling */
            if (PyErr_Occurred()) {
                return NULL;
            }
            return PyGenericArrType_Type.tp_as_number->nb_@oper@(a,b);
        case -3:
            /*
             * special case for longdouble and clongdouble
             * because they have a recursive getitem in their dtype
             */
            Py_INCREF(Py_NotImplemented);
            return Py_NotImplemented;
    }

#if @fperr@
    PyUFunc_clearfperr();
#endif

    /*
     * here we do the actual calculation with arg1 and arg2
     * as a function call.
     */
#if @twoout@
    @name@_ctype_@oper@(arg1, arg2, (@otype@ *)&out, &out2);
#else
    @name@_ctype_@oper@(arg1, arg2, (@otype@ *)&out);
#endif

#if @fperr@
    /* Check status flag.  If it is set, then look up what to do */
    retstatus = PyUFunc_getfperr();
    if (retstatus) {
        int bufsize, errmask;
        PyObject *errobj;

        if (PyUFunc_GetPyValues("@name@_scalars", &bufsize, &errmask,
                                &errobj) < 0) {
            return NULL;
        }
        first = 1;
        if (PyUFunc_handlefperr(errmask, errobj, retstatus, &first)) {
            Py_XDECREF(errobj);
            return NULL;
        }
        Py_XDECREF(errobj);
    }
#endif


#if @twoout@
    ret = PyTuple_New(2);
    if (ret == NULL) {
        return NULL;
    }
    obj = PyArrayScalar_New(@OName@);
    if (obj == NULL) {
        Py_DECREF(ret);
        return NULL;
    }
    PyArrayScalar_ASSIGN(obj, @OName@, out);
    PyTuple_SET_ITEM(ret, 0, obj);
    obj = PyArrayScalar_New(@OName@);
    if (obj == NULL) {
        Py_DECREF(ret);
        return NULL;
    }
    PyArrayScalar_ASSIGN(obj, @OName@, out2);
    PyTuple_SET_ITEM(ret, 1, obj);
#else
    ret = PyArrayScalar_New(@OName@);
    if (ret == NULL) {
        return NULL;
    }
    PyArrayScalar_ASSIGN(ret, @OName@, out);
#endif
    return ret;
}
#endif

/**end repeat**/

#undef CODEGEN_SKIP_divide_FLAG

#define _IS_ZERO(x) (x == 0)

/**begin repeat
 *
 * #name = byte, ubyte, short, ushort, int, uint,
 *         long, ulong, longlong, ulonglong,
 *         half, float, double, longdouble,
 *         cfloat, cdouble, clongdouble#
 *
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_double, npy_longdouble,
 *         npy_cfloat, npy_cdouble, npy_clongdouble#
 *
 * #Name = Byte, UByte, Short, UShort, Int, UInt,
 *         Long, ULong, LongLong, ULongLong,
 *         Half, Float, Double, LongDouble,
 *         CFloat, CDouble, CLongDouble#
 *
 * #otype = npy_float*4, npy_double*6, npy_half, npy_float,
 *          npy_double, npy_longdouble,
 *          npy_cfloat, npy_cdouble, npy_clongdouble#
 *
 * #OName = Float*4, Double*6, Half, Float,
 *          Double, LongDouble,
 *          CFloat, CDouble, CLongDouble#
 *
 * #isint = (1,0)*5,0*7#
 * #cmplx = 0*14,1*3#
 * #iszero = _IS_ZERO*10, npy_half_iszero, _IS_ZERO*6#
 * #zero = 0*10, NPY_HALF_ZERO, 0*6#
 * #one = 1*10, NPY_HALF_ONE, 1*6#
 */

#if @cmplx@
static PyObject *
@name@_power(PyObject *a, PyObject *b, PyObject *NPY_UNUSED(c))
{
    PyObject *ret;
    @type@ arg1, arg2;
    int retstatus;
    int first;
    @type@ out = {@zero@, @zero@};

    switch(_@name@_convert2_to_ctypes(a, &arg1, b, &arg2)) {
        case 0:
            break;
        case -1:
            /* can't cast both safely mixed-types? */
            return PyArray_Type.tp_as_number->nb_power(a,b,NULL);
        case -2:
            /* use default handling */
            if (PyErr_Occurred()) {
                return NULL;
            }
            return PyGenericArrType_Type.tp_as_number->nb_power(a,b,NULL);
        case -3:
            /*
             * special case for longdouble and clongdouble
             * because they have a recursive getitem in their dtype
             */
            Py_INCREF(Py_NotImplemented);
            return Py_NotImplemented;
    }

    PyUFunc_clearfperr();

    /*
     * here we do the actual calculation with arg1 and arg2
     * as a function call.
     */
    if (@iszero@(arg2.real) && @iszero@(arg2.imag)) {
        out.real = @one@;
        out.imag = @zero@;
    }
    else {
        @name@_ctype_power(arg1, arg2, &out);
    }

    /* Check status flag.  If it is set, then look up what to do */
    retstatus = PyUFunc_getfperr();
    if (retstatus) {
        int bufsize, errmask;
        PyObject *errobj;

        if (PyUFunc_GetPyValues("@name@_scalars", &bufsize, &errmask,
                                &errobj) < 0) {
            return NULL;
        }
        first = 1;
        if (PyUFunc_handlefperr(errmask, errobj, retstatus, &first)) {
            Py_XDECREF(errobj);
            return NULL;
        }
        Py_XDECREF(errobj);
    }

    ret = PyArrayScalar_New(@Name@);
    if (ret == NULL) {
        return NULL;
    }
    PyArrayScalar_ASSIGN(ret, @Name@, out);

    return ret;
}

#elif @isint@

static PyObject *
@name@_power(PyObject *a, PyObject *b, PyObject *NPY_UNUSED(c))
{
    PyObject *ret;
    @type@ arg1, arg2;
    int retstatus;
    int first;
    @type@ out = @zero@;
    @otype@ out1 = @zero@;

    switch(_@name@_convert2_to_ctypes(a, &arg1, b, &arg2)) {
        case 0:
            break;
        case -1:
            /* can't cast both safely mixed-types? */
            return PyArray_Type.tp_as_number->nb_power(a,b,NULL);
        case -2:
            /* use default handling */
            if (PyErr_Occurred()) {
                return NULL;
            }
            return PyGenericArrType_Type.tp_as_number->nb_power(a,b,NULL);
        case -3:
            /*
             * special case for longdouble and clongdouble
             * because they have a recursive getitem in their dtype
             */
            Py_INCREF(Py_NotImplemented);
            return Py_NotImplemented;
    }

    PyUFunc_clearfperr();

    /*
     * here we do the actual calculation with arg1 and arg2
     * as a function call.
     */
    if (@iszero@(arg2)) {
        out1 = out = @one@;
    }
    else if (arg2 < 0) {
        @name@_ctype_power(arg1, -arg2, &out);
        out1 = (@otype@) (1.0 / out);
    }
    else {
        @name@_ctype_power(arg1, arg2, &out);
    }

    /* Check status flag.  If it is set, then look up what to do */
    retstatus = PyUFunc_getfperr();
    if (retstatus) {
        int bufsize, errmask;
        PyObject *errobj;

        if (PyUFunc_GetPyValues("@name@_scalars", &bufsize, &errmask,
                                &errobj) < 0) {
            return NULL;
        }
        first = 1;
        if (PyUFunc_handlefperr(errmask, errobj, retstatus, &first)) {
            Py_XDECREF(errobj);
            return NULL;
        }
        Py_XDECREF(errobj);
    }

    if (arg2 < 0) {
        ret = PyArrayScalar_New(@OName@);
        if (ret == NULL) {
            return NULL;
        }
        PyArrayScalar_ASSIGN(ret, @OName@, out1);
    }
    else {
        ret = PyArrayScalar_New(@Name@);
        if (ret == NULL) {
            return NULL;
        }
        PyArrayScalar_ASSIGN(ret, @Name@, out);
    }

    return ret;
}

#else

static PyObject *
@name@_power(PyObject *a, PyObject *b, PyObject *NPY_UNUSED(c))
{
    PyObject *ret;
    @type@ arg1, arg2;
    int retstatus;
    int first;

    @type@ out = @zero@;
    switch(_@name@_convert2_to_ctypes(a, &arg1, b, &arg2)) {
        case 0:
            break;
        case -1:
            /* can't cast both safely mixed-types? */
            return PyArray_Type.tp_as_number->nb_power(a,b,NULL);
        case -2:
            /* use default handling */
            if (PyErr_Occurred()) {
                return NULL;
            }
            return PyGenericArrType_Type.tp_as_number->nb_power(a,b,NULL);
        case -3:
            /*
             * special case for longdouble and clongdouble
             * because they have a recursive getitem in their dtype
             */
            Py_INCREF(Py_NotImplemented);
            return Py_NotImplemented;
    }

    PyUFunc_clearfperr();

    /*
     * here we do the actual calculation with arg1 and arg2
     * as a function call.
     */
    if (@iszero@(arg2)) {
        out = @one@;
    }
    else {
        @name@_ctype_power(arg1, arg2, &out);
    }

    /* Check status flag.  If it is set, then look up what to do */
    retstatus = PyUFunc_getfperr();
    if (retstatus) {
        int bufsize, errmask;
        PyObject *errobj;

        if (PyUFunc_GetPyValues("@name@_scalars", &bufsize, &errmask,
                                &errobj) < 0) {
            return NULL;
        }
        first = 1;
        if (PyUFunc_handlefperr(errmask, errobj, retstatus, &first)) {
            Py_XDECREF(errobj);
            return NULL;
        }
        Py_XDECREF(errobj);
    }

    ret = PyArrayScalar_New(@Name@);
    if (ret == NULL) {
        return NULL;
    }
    PyArrayScalar_ASSIGN(ret, @Name@, out);

    return ret;
}

#endif

/**end repeat**/
#undef _IS_ZERO


/**begin repeat
 *
 * #name = cfloat, cdouble, clongdouble#
 *
 */

/**begin repeat1
 *
 * #oper = divmod, remainder#
 *
 */

#define @name@_@oper@ NULL

/**end repeat1**/

/**end repeat**/

/**begin repeat
 *
 * #name = half, float, double, longdouble, cfloat, cdouble, clongdouble#
 *
 */

/**begin repeat1
 *
 * #oper = lshift, rshift, and, or, xor#
 *
 */

#define @name@_@oper@ NULL

/**end repeat1**/

/**end repeat**/

/**begin repeat
 * #name = (byte, ubyte, short, ushort, int, uint,
 *             long, ulong, longlong, ulonglong,
 *             half, float, double, longdouble,
 *             cfloat, cdouble, clongdouble)*3,
 *
 *         byte, ubyte, short, ushort, int, uint,
 *         long, ulong, longlong, ulonglong#
 *
 * #type = (npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *             npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *             npy_half, npy_float, npy_double, npy_longdouble,
 *             npy_cfloat, npy_cdouble, npy_clongdouble)*3,
 *
 *         npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong#
 *
 * #otype = (npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *             npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *             npy_half, npy_float, npy_double, npy_longdouble,
 *             npy_cfloat, npy_cdouble, npy_clongdouble)*2,
 *         npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_double, npy_longdouble,
 *         npy_float, npy_double, npy_longdouble,
 *
 *         npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int, npy_uint,
 *         npy_long, npy_ulong, npy_longlong, npy_ulonglong#
 *
 * #OName = (Byte, UByte, Short, UShort, Int, UInt,
 *              Long, ULong, LongLong, ULongLong,
 *              Half, Float, Double, LongDouble,
 *              CFloat, CDouble, CLongDouble)*2,
 *          Byte, UByte, Short, UShort, Int, UInt,
 *          Long, ULong, LongLong, ULongLong,
 *          Half, Float, Double, LongDouble,
 *          Float, Double, LongDouble,
 *
 *          Byte, UByte, Short, UShort, Int, UInt,
 *          Long, ULong, LongLong, ULongLong#
 *
 * #oper = negative*17, positive*17, absolute*17, invert*10#
 */
static PyObject *
@name@_@oper@(PyObject *a)
{
    @type@ arg1;
    @otype@ out;
    PyObject *ret;

    switch(_@name@_convert_to_ctype(a, &arg1)) {
    case 0:
        break;
    case -1:
        /* can't cast both safely use different add function */
        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    case -2:
        /* use default handling */
        if (PyErr_Occurred()) {
            return NULL;
        }
        return PyGenericArrType_Type.tp_as_number->nb_@oper@(a);
    }

    /*
     * here we do the actual calculation with arg1 and arg2
     * make it a function call.
     */

    @name@_ctype_@oper@(arg1, &out);

    ret = PyArrayScalar_New(@OName@);
    PyArrayScalar_ASSIGN(ret, @OName@, out);

    return ret;
}
/**end repeat**/

/**begin repeat
 *
 * #name = half, float, double, longdouble, cfloat, cdouble, clongdouble#
 */

#define @name@_invert NULL

/**end repeat**/

#if defined(NPY_PY3K)
#define NONZERO_NAME(prefix) prefix##bool
#else
#define NONZERO_NAME(prefix) prefix##nonzero
#endif

#define _IS_NONZERO(x) (x != 0)
/**begin repeat
 *
 * #name = byte, ubyte, short, ushort, int,
 *         uint, long, ulong, longlong, ulonglong,
 *         half, float, double, longdouble,
 *         cfloat, cdouble, clongdouble#
 * #type = npy_byte, npy_ubyte, npy_short, npy_ushort, npy_int,
 *         npy_uint, npy_long, npy_ulong, npy_longlong, npy_ulonglong,
 *         npy_half, npy_float, npy_double, npy_longdouble,
 *         npy_cfloat, npy_cdouble, npy_clongdouble#
 * #simp = 1*14, 0*3#
 * #nonzero = _IS_NONZERO*10, !npy_half_iszero, _IS_NONZERO*6#
 */
static int
NONZERO_NAME(@name@_)(PyObject *a)
{
    int ret;
    @type@ arg1;

    if (_@name@_convert_to_ctype(a, &arg1) < 0) {
        if (PyErr_Occurred()) {
            return -1;
        }
        return PyGenericArrType_Type.tp_as_number->NONZERO_NAME(nb_)(a);
    }

    /*
     * here we do the actual calculation with arg1 and arg2
     * make it a function call.
     */

#if @simp@
    ret = @nonzero@(arg1);
#else
    ret = (@nonzero@(arg1.real) || @nonzero@(arg1.imag));
#endif

    return ret;
}
/**end repeat**/
#undef _IS_NONZERO


static int
emit_complexwarning(void)
{
    static PyObject *cls = NULL;
    if (cls == NULL) {
        PyObject *mod;
        mod = PyImport_ImportModule("numpy.core");
        assert(mod != NULL);
        cls = PyObject_GetAttrString(mod, "ComplexWarning");
        assert(cls != NULL);
        Py_DECREF(mod);
    }
    return PyErr_WarnEx(cls,
            "Casting complex values to real discards the imaginary part", 1);
}

/**begin repeat
 *
 * #name = byte, ubyte, short, ushort, int,
 *         uint, long, ulong, longlong, ulonglong,
 *         half, float, double, longdouble,
 *         cfloat, cdouble, clongdouble#
 *
 * #Name = Byte, UByte, Short, UShort, Int,
 *         UInt, Long, ULong, LongLong, ULongLong,
 *         Half, Float, Double, LongDouble,
 *         CFloat, CDouble, CLongDouble#
 *
 * #cmplx = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1#
 * #sign = (signed, unsigned)*5, , , , , , , #
 * #unsigntyp = 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0*7#
 * #ctype = long*8, PY_LONG_LONG*2, double*7#
 * #to_ctype = , , , , , , , , , , npy_half_to_double, , , , , , #
 * #realtyp = 0*10, 1*7#
 * #func = (PyLong_FromLong, PyLong_FromUnsignedLong)*4,
 *         PyLong_FromLongLong, PyLong_FromUnsignedLongLong,
 *         PyLong_FromDouble*7#
 */
static PyObject *
@name@_int(PyObject *obj)
{
#if @cmplx@
    @sign@ @ctype@ x= @to_ctype@(PyArrayScalar_VAL(obj, @Name@).real);
    int ret;
#else
    @sign@ @ctype@ x= @to_ctype@(PyArrayScalar_VAL(obj, @Name@));
#endif

#if @realtyp@
    double ix;
    modf(x, &ix);
    x = ix;
#endif

#if @cmplx@
    ret = emit_complexwarning();
    if (ret < 0) {
        return NULL;
    }
#endif

#if @unsigntyp@
    if(x < LONG_MAX)
        return PyInt_FromLong(x);
#else
    if(LONG_MIN < x && x < LONG_MAX)
        return PyInt_FromLong(x);
#endif
    return @func@(x);
}
/**end repeat**/

/**begin repeat
 *
 * #name = (byte, ubyte, short, ushort, int, uint,
 *             long, ulong, longlong, ulonglong,
 *             half, float, double, longdouble,
 *             cfloat, cdouble, clongdouble)*2#
 * #Name = (Byte, UByte, Short, UShort, Int, UInt,
 *             Long, ULong, LongLong, ULongLong,
 *             Half, Float, Double, LongDouble,
 *             CFloat, CDouble, CLongDouble)*2#
 * #cmplx = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1)*2#
 * #to_ctype = (, , , , , , , , , , npy_half_to_double, , , , , , )*2#
 * #which = long*17, float*17#
 * #func = (PyLong_FromLongLong,  PyLong_FromUnsignedLongLong)*5,
 *         PyLong_FromDouble*7, PyFloat_FromDouble*17#
 */
static PyObject *
@name@_@which@(PyObject *obj)
{
#if @cmplx@
    int ret;
    ret = emit_complexwarning();
    if (ret < 0) {
        return NULL;
    }
    return @func@(@to_ctype@((PyArrayScalar_VAL(obj, @Name@)).real));
#else
    return @func@(@to_ctype@(PyArrayScalar_VAL(obj, @Name@)));
#endif
}
/**end repeat**/

#if !defined(NPY_PY3K)

/**begin repeat
 *
 * #name = (byte, ubyte, short, ushort, int, uint,
 *             long, ulong, longlong, ulonglong,
 *             half, float, double, longdouble,
 *             cfloat, cdouble, clongdouble)*2#
 * #oper = oct*17,  hex*17#
 * #kind = (int*5,  long*5,  int*2,  long*2,  int,  long*2)*2#
 * #cap = (Int*5,  Long*5,  Int*2,  Long*2,  Int,  Long*2)*2#
 */
static PyObject *
@name@_@oper@(PyObject *obj)
{
    PyObject *pyint;
    pyint = @name@_@kind@(obj);
    if (pyint == NULL) {
        return NULL;
    }
    return Py@cap@_Type.tp_as_number->nb_@oper@(pyint);
}
/**end repeat**/

#endif

/**begin repeat
 * #oper = le, ge, lt, gt, eq, ne#
 * #op = <=, >=, <, >, ==, !=#
 * #halfop = npy_half_le, npy_half_ge, npy_half_lt,
 *           npy_half_gt, npy_half_eq, npy_half_ne#
 */
#define def_cmp_@oper@(arg1, arg2) (arg1 @op@ arg2)
#define cmplx_cmp_@oper@(arg1, arg2) ((arg1.real == arg2.real) ?        \
                                      arg1.imag @op@ arg2.imag :        \
                                      arg1.real @op@ arg2.real)
#define def_half_cmp_@oper@(arg1, arg2) @halfop@(arg1, arg2)
/**end repeat**/

/**begin repeat
 * #name = byte, ubyte, short, ushort, int, uint,
 *         long, ulong, longlong, ulonglong,
 *         half, float, double, longdouble,
 *         cfloat, cdouble, clongdouble#
 * #simp = def*10, def_half, def*3, cmplx*3#
 */
static PyObject*
@name@_richcompare(PyObject *self, PyObject *other, int cmp_op)
{
    npy_@name@ arg1, arg2;
    int out=0;

    switch(_@name@_convert2_to_ctypes(self, &arg1, other, &arg2)) {
    case 0:
        break;
    case -1:
        /* can't cast both safely use different add function */
    case -2:
        /* use ufunc */
        if (PyErr_Occurred()) {
            return NULL;
        }
        return PyGenericArrType_Type.tp_richcompare(self, other, cmp_op);
    case -3:
        /*
         * special case for longdouble and clongdouble
         * because they have a recursive getitem in their dtype
         */
        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    }

    /* here we do the actual calculation with arg1 and arg2 */
    switch (cmp_op) {
    case Py_EQ:
        out = @simp@_cmp_eq(arg1, arg2);
        break;
    case Py_NE:
        out = @simp@_cmp_ne(arg1, arg2);
        break;
    case Py_LE:
        out = @simp@_cmp_le(arg1, arg2);
        break;
    case Py_GE:
        out = @simp@_cmp_ge(arg1, arg2);
        break;
    case Py_LT:
        out = @simp@_cmp_lt(arg1, arg2);
        break;
    case Py_GT:
        out = @simp@_cmp_gt(arg1, arg2);
        break;
    }

    if (out) {
        PyArrayScalar_RETURN_TRUE;
    }
    else {
        PyArrayScalar_RETURN_FALSE;
    }
}
/**end repeat**/


/**begin repeat
 *  #name = byte, ubyte, short, ushort, int, uint,
 *          long, ulong, longlong, ulonglong,
 *          half, float, double, longdouble,
 *          cfloat, cdouble, clongdouble#
**/
static PyNumberMethods @name@_as_number = {
    (binaryfunc)@name@_add,                     /*nb_add*/
    (binaryfunc)@name@_subtract,                /*nb_subtract*/
    (binaryfunc)@name@_multiply,                /*nb_multiply*/
#if defined(NPY_PY3K)
#else
    (binaryfunc)@name@_divide,                  /*nb_divide*/
#endif
    (binaryfunc)@name@_remainder,               /*nb_remainder*/
    (binaryfunc)@name@_divmod,                  /*nb_divmod*/
    (ternaryfunc)@name@_power,                  /*nb_power*/
    (unaryfunc)@name@_negative,
    (unaryfunc)@name@_positive,                 /*nb_pos*/
    (unaryfunc)@name@_absolute,                 /*nb_abs*/
#if defined(NPY_PY3K)
    (inquiry)@name@_bool,                       /*nb_bool*/
#else
    (inquiry)@name@_nonzero,                    /*nb_nonzero*/
#endif
    (unaryfunc)@name@_invert,                   /*nb_invert*/
    (binaryfunc)@name@_lshift,                  /*nb_lshift*/
    (binaryfunc)@name@_rshift,                  /*nb_rshift*/
    (binaryfunc)@name@_and,                     /*nb_and*/
    (binaryfunc)@name@_xor,                     /*nb_xor*/
    (binaryfunc)@name@_or,                      /*nb_or*/
#if defined(NPY_PY3K)
#else
    0,                                          /*nb_coerce*/
#endif
    (unaryfunc)@name@_int,                      /*nb_int*/
#if defined(NPY_PY3K)
    (unaryfunc)0,                               /*nb_reserved*/
#else
    (unaryfunc)@name@_long,                     /*nb_long*/
#endif
    (unaryfunc)@name@_float,                    /*nb_float*/
#if defined(NPY_PY3K)
#else
    (unaryfunc)@name@_oct,                      /*nb_oct*/
    (unaryfunc)@name@_hex,                      /*nb_hex*/
#endif
    0,                                          /*inplace_add*/
    0,                                          /*inplace_subtract*/
    0,                                          /*inplace_multiply*/
#if defined(NPY_PY3K)
#else
    0,                                          /*inplace_divide*/
#endif
    0,                                          /*inplace_remainder*/
    0,                                          /*inplace_power*/
    0,                                          /*inplace_lshift*/
    0,                                          /*inplace_rshift*/
    0,                                          /*inplace_and*/
    0,                                          /*inplace_xor*/
    0,                                          /*inplace_or*/
    (binaryfunc)@name@_floor_divide,            /*nb_floor_divide*/
    (binaryfunc)@name@_true_divide,             /*nb_true_divide*/
    0,                                          /*nb_inplace_floor_divide*/
    0,                                          /*nb_inplace_true_divide*/
    (unaryfunc)NULL,                            /*nb_index*/
};
/**end repeat**/

static void *saved_tables_arrtype[9];

static void
add_scalarmath(void)
{
    /**begin repeat
     *  #name = byte, ubyte, short, ushort, int, uint,
     *          long, ulong, longlong, ulonglong,
     *          half, float, double, longdouble,
     *          cfloat, cdouble, clongdouble#
     *  #NAME = Byte, UByte, Short, UShort, Int, UInt,
     *          Long, ULong, LongLong, ULongLong,
     *          Half, Float, Double, LongDouble,
     *          CFloat, CDouble, CLongDouble#
     **/
    @name@_as_number.nb_index = Py@NAME@ArrType_Type.tp_as_number->nb_index;
    Py@NAME@ArrType_Type.tp_as_number = &(@name@_as_number);
    Py@NAME@ArrType_Type.tp_richcompare = @name@_richcompare;
    /**end repeat**/

    saved_tables_arrtype[0] = PyLongArrType_Type.tp_as_number;
#if !defined(NPY_PY3K)
    saved_tables_arrtype[1] = PyLongArrType_Type.tp_compare;
#endif
    saved_tables_arrtype[2] = PyLongArrType_Type.tp_richcompare;
    saved_tables_arrtype[3] = PyDoubleArrType_Type.tp_as_number;
#if !defined(NPY_PY3K)
    saved_tables_arrtype[4] = PyDoubleArrType_Type.tp_compare;
#endif
    saved_tables_arrtype[5] = PyDoubleArrType_Type.tp_richcompare;
    saved_tables_arrtype[6] = PyCDoubleArrType_Type.tp_as_number;
#if !defined(NPY_PY3K)
    saved_tables_arrtype[7] = PyCDoubleArrType_Type.tp_compare;
#endif
    saved_tables_arrtype[8] = PyCDoubleArrType_Type.tp_richcompare;
}

static int
get_functions(void)
{
    PyObject *mm, *obj;
    void **funcdata;
    char *signatures;
    int i, j;
    int ret = -1;

    /* Get the nc_pow functions */
    /* Get the pow functions */
    mm = PyImport_ImportModule("numpy.core.umath");
    if (mm == NULL) {
        return -1;
    }

    obj = PyObject_GetAttrString(mm, "power");
    if (obj == NULL) {
        goto fail;
    }
    funcdata = ((PyUFuncObject *)obj)->data;
    signatures = ((PyUFuncObject *)obj)->types;

    i = 0;
    j = 0;
    while (signatures[i] != NPY_FLOAT) {
        i += 3;
        j++;
    }
    _basic_float_pow = funcdata[j];
    _basic_double_pow = funcdata[j + 1];
    _basic_longdouble_pow = funcdata[j + 2];
    _basic_cfloat_pow = funcdata[j + 3];
    _basic_cdouble_pow = funcdata[j + 4];
    _basic_clongdouble_pow = funcdata[j + 5];
    Py_DECREF(obj);

    /* Get the floor functions */
    obj = PyObject_GetAttrString(mm, "floor");
    if (obj == NULL) {
        goto fail;
    }
    funcdata = ((PyUFuncObject *)obj)->data;
    signatures = ((PyUFuncObject *)obj)->types;
    i = 0;
    j = 0;
    while(signatures[i] != NPY_FLOAT) {
        i += 2;
        j++;
    }
    _basic_half_floor = funcdata[j - 1];
    _basic_float_floor = funcdata[j];
    _basic_double_floor = funcdata[j + 1];
    _basic_longdouble_floor = funcdata[j + 2];
    Py_DECREF(obj);

    /* Get the sqrt functions */
    obj = PyObject_GetAttrString(mm, "sqrt");
    if (obj == NULL) {
        goto fail;
    }
    funcdata = ((PyUFuncObject *)obj)->data;
    signatures = ((PyUFuncObject *)obj)->types;
    /*
     * sqrt ufunc is specialized for double and float loops in
     * generate_umath.py, the first to go into FLOAT/DOUBLE_sqrt
     * they have the same signature as the scalar variants so we need to skip
     * over them
     */
    i = 4;
    j = 2;
    while (signatures[i] != NPY_FLOAT) {
        i += 2; j++;
    }
    _basic_half_sqrt = funcdata[j - 1];
    _basic_float_sqrt = funcdata[j];
    _basic_double_sqrt = funcdata[j + 1];
    _basic_longdouble_sqrt = funcdata[j + 2];
    Py_DECREF(obj);

    /* Get the fmod functions */
    obj = PyObject_GetAttrString(mm, "fmod");
    if (obj == NULL) {
        goto fail;
    }
    funcdata = ((PyUFuncObject *)obj)->data;
    signatures = ((PyUFuncObject *)obj)->types;
    i = 0;
    j = 0;
    while (signatures[i] != NPY_FLOAT) {
        i += 3;
        j++;
    }
    _basic_half_fmod = funcdata[j - 1];
    _basic_float_fmod = funcdata[j];
    _basic_double_fmod = funcdata[j + 1];
    _basic_longdouble_fmod = funcdata[j + 2];
    Py_DECREF(obj);
    return ret = 0;

 fail:
    Py_DECREF(mm);
    return ret;
}

static void *saved_tables[9];

char doc_alterpyscalars[] = "";

static PyObject *
alter_pyscalars(PyObject *NPY_UNUSED(dummy), PyObject *args)
{
    int n;
    PyObject *obj;
    n = PyTuple_GET_SIZE(args);
    while (n--) {
        obj = PyTuple_GET_ITEM(args, n);
#if !defined(NPY_PY3K)
        if (obj == (PyObject *)(&PyInt_Type)) {
            PyInt_Type.tp_as_number = PyLongArrType_Type.tp_as_number;
            PyInt_Type.tp_compare = PyLongArrType_Type.tp_compare;
            PyInt_Type.tp_richcompare = PyLongArrType_Type.tp_richcompare;
        }
        else
#endif
        if (obj == (PyObject *)(&PyFloat_Type)) {
            PyFloat_Type.tp_as_number = PyDoubleArrType_Type.tp_as_number;
#if !defined(NPY_PY3K)
            PyFloat_Type.tp_compare = PyDoubleArrType_Type.tp_compare;
#endif
            PyFloat_Type.tp_richcompare = PyDoubleArrType_Type.tp_richcompare;
        }
        else if (obj == (PyObject *)(&PyComplex_Type)) {
            PyComplex_Type.tp_as_number = PyCDoubleArrType_Type.tp_as_number;
#if !defined(NPY_PY3K)
            PyComplex_Type.tp_compare = PyCDoubleArrType_Type.tp_compare;
#endif
            PyComplex_Type.tp_richcompare =             \
                PyCDoubleArrType_Type.tp_richcompare;
        }
        else {
            PyErr_SetString(PyExc_ValueError,
                    "arguments must be int, float, or complex");
            return NULL;
        }
    }
    Py_INCREF(Py_None);
    return Py_None;
}

char doc_restorepyscalars[] = "";
static PyObject *
restore_pyscalars(PyObject *NPY_UNUSED(dummy), PyObject *args)
{
    int n;
    PyObject *obj;

    n = PyTuple_GET_SIZE(args);
    while (n--) {
        obj = PyTuple_GET_ITEM(args, n);
#if !defined(NPY_PY3K)
        if (obj == (PyObject *)(&PyInt_Type)) {
            PyInt_Type.tp_as_number = saved_tables[0];
            PyInt_Type.tp_compare = saved_tables[1];
            PyInt_Type.tp_richcompare = saved_tables[2];
        }
        else
#endif
        if (obj == (PyObject *)(&PyFloat_Type)) {
            PyFloat_Type.tp_as_number = saved_tables[3];
#if !defined(NPY_PY3K)
            PyFloat_Type.tp_compare = saved_tables[4];
#endif
            PyFloat_Type.tp_richcompare = saved_tables[5];
        }
        else if (obj == (PyObject *)(&PyComplex_Type)) {
            PyComplex_Type.tp_as_number = saved_tables[6];
#if !defined(NPY_PY3K)
            PyComplex_Type.tp_compare = saved_tables[7];
#endif
            PyComplex_Type.tp_richcompare = saved_tables[8];
        }
        else {
            PyErr_SetString(PyExc_ValueError,
                    "arguments must be int, float, or complex");
            return NULL;
        }
    }
    Py_INCREF(Py_None);
    return Py_None;
}

char doc_usepythonmath[] = "";
static PyObject *
use_pythonmath(PyObject *NPY_UNUSED(dummy), PyObject *args)
{
    int n;
    PyObject *obj;

    n = PyTuple_GET_SIZE(args);
    while (n--) {
        obj = PyTuple_GET_ITEM(args, n);
#if !defined(NPY_PY3K)
        if (obj == (PyObject *)(&PyInt_Type)) {
            PyLongArrType_Type.tp_as_number = saved_tables[0];
            PyLongArrType_Type.tp_compare = saved_tables[1];
            PyLongArrType_Type.tp_richcompare = saved_tables[2];
        }
        else
#endif
        if (obj == (PyObject *)(&PyFloat_Type)) {
            PyDoubleArrType_Type.tp_as_number = saved_tables[3];
#if !defined(NPY_PY3K)
            PyDoubleArrType_Type.tp_compare = saved_tables[4];
#endif
            PyDoubleArrType_Type.tp_richcompare = saved_tables[5];
        }
        else if (obj == (PyObject *)(&PyComplex_Type)) {
            PyCDoubleArrType_Type.tp_as_number = saved_tables[6];
#if !defined(NPY_PY3K)
            PyCDoubleArrType_Type.tp_compare = saved_tables[7];
#endif
            PyCDoubleArrType_Type.tp_richcompare = saved_tables[8];
        }
        else {
            PyErr_SetString(PyExc_ValueError,
                    "arguments must be int, float, or complex");
            return NULL;
        }
    }
    Py_INCREF(Py_None);
    return Py_None;
}

char doc_usescalarmath[] = "";
static PyObject *
use_scalarmath(PyObject *NPY_UNUSED(dummy), PyObject *args)
{
    int n;
    PyObject *obj;

    n = PyTuple_GET_SIZE(args);
    while (n--) {
        obj = PyTuple_GET_ITEM(args, n);
#if !defined(NPY_PY3K)
        if (obj == (PyObject *)(&PyInt_Type)) {
            PyLongArrType_Type.tp_as_number = saved_tables_arrtype[0];
            PyLongArrType_Type.tp_compare = saved_tables_arrtype[1];
            PyLongArrType_Type.tp_richcompare = saved_tables_arrtype[2];
        }
        else
#endif
        if (obj == (PyObject *)(&PyFloat_Type)) {
            PyDoubleArrType_Type.tp_as_number = saved_tables_arrtype[3];
#if !defined(NPY_PY3K)
            PyDoubleArrType_Type.tp_compare = saved_tables_arrtype[4];
#endif
            PyDoubleArrType_Type.tp_richcompare = saved_tables_arrtype[5];
        }
        else if (obj == (PyObject *)(&PyComplex_Type)) {
            PyCDoubleArrType_Type.tp_as_number = saved_tables_arrtype[6];
#if !defined(NPY_PY3K)
            PyCDoubleArrType_Type.tp_compare = saved_tables_arrtype[7];
#endif
            PyCDoubleArrType_Type.tp_richcompare = saved_tables_arrtype[8];
        }
        else {
            PyErr_SetString(PyExc_ValueError,
                    "arguments must be int, float, or complex");
            return NULL;
        }
    }
    Py_INCREF(Py_None);
    return Py_None;
}

static struct PyMethodDef methods[] = {
    {"alter_pythonmath",
        (PyCFunction) alter_pyscalars,
         METH_VARARGS, doc_alterpyscalars},
    {"restore_pythonmath",
        (PyCFunction) restore_pyscalars,
         METH_VARARGS, doc_restorepyscalars},
    {"use_pythonmath",
        (PyCFunction) use_pythonmath,
        METH_VARARGS, doc_usepythonmath},
    {"use_scalarmath",
        (PyCFunction) use_scalarmath,
        METH_VARARGS, doc_usescalarmath},
    {NULL, NULL, 0, NULL}
};

#if defined(NPY_PY3K)
static struct PyModuleDef moduledef = {
        PyModuleDef_HEAD_INIT,
        "scalarmath",
        NULL,
        -1,
        methods,
        NULL,
        NULL,
        NULL,
        NULL
};
#endif

#if defined(NPY_PY3K)
#define RETVAL m
PyMODINIT_FUNC PyInit_scalarmath(void)
#else
#define RETVAL
PyMODINIT_FUNC
initscalarmath(void)
#endif
{
#if defined(NPY_PY3K)
    PyObject *m = PyModule_Create(&moduledef);
    if (!m) {
        return NULL;
    }
#else
    Py_InitModule("scalarmath", methods);
#endif

    import_array();
    import_umath();

    if (get_functions() < 0) {
        return RETVAL;
    }

    add_scalarmath();

#if !defined(NPY_PY3K)
    saved_tables[0] = PyInt_Type.tp_as_number;
    saved_tables[1] = PyInt_Type.tp_compare;
    saved_tables[2] = PyInt_Type.tp_richcompare;
#endif
    saved_tables[3] = PyFloat_Type.tp_as_number;
#if !defined(NPY_PY3K)
    saved_tables[4] = PyFloat_Type.tp_compare;
#endif
    saved_tables[5] = PyFloat_Type.tp_richcompare;
    saved_tables[6] = PyComplex_Type.tp_as_number;
#if !defined(NPY_PY3K)
    saved_tables[7] = PyComplex_Type.tp_compare;
#endif
    saved_tables[8] = PyComplex_Type.tp_richcompare;

    return RETVAL;
}
