11

I want to create a function in C that extends Python that can take inputs of either float or int type. So basically, I want f(5) and f(5.5) to be acceptable inputs.

I don't think I can use if (!PyArg_ParseTuple(args, "i", $value)) because it only takes only int or only float.

How can I make my function allow inputs that are either ints or floats?

I'm wondering if I should just take the input and put it into a PyObject and somehow take the type of the PyObject - is that the right approach?

user253751
  • 57,427
  • 7
  • 48
  • 90
ansg191
  • 419
  • 1
  • 7
  • 16
  • Note: I've edited your question to make it easier to read. If you don't like something I've changed then feel free to change it back, because it is your question not mine. – user253751 Oct 04 '16 at 04:30
  • 1
    You probably want to work with `PyObject` and then use the [Number Protocol](https://docs.python.org/2/c-api/number.html) for working with the numbers. If you _need_ to coerce to a `float` for example, you can call `PyNumber_Float` on your `PyObject`. – mgilson Oct 04 '16 at 04:32

3 Answers3

6

If you declare a C function to accept floats, the compiler won't complain if you hand it an int. For instance, this program produces the answer 2.000000:

#include <stdio.h>

float f(float x) {
  return x+1;
}

int main() {
  int i=1;
  printf ("%f", f(i));
}

A python module version, iorf.c:

#include <Python.h>

static PyObject *IorFError;

float f(float x) {
  return x+1;
}


static PyObject *
fwrap(PyObject *self, PyObject *args) {
  float in=0.0;
  if (!PyArg_ParseTuple(args, "f", &in))
    return NULL;
  return Py_BuildValue("f", f(in));
}

static PyMethodDef IorFMethods[] = {
    {"fup",  fwrap, METH_VARARGS,
     "Arg + 1"},
    {NULL, NULL, 0, NULL}        /* Sentinel */
};


PyMODINIT_FUNC
initiorf(void)
{
  PyObject *m;

  m = Py_InitModule("iorf", IorFMethods);
  if (m == NULL)
    return;

  IorFError = PyErr_NewException("iorf.error", NULL, NULL);
  Py_INCREF(IorFError);
  PyModule_AddObject(m, "error", IorFError);
}

The setup.py:

from distutils.core import setup, Extension

module1 = Extension('iorf',
                    sources = ['iorf.c'])

setup (name = 'iorf',
       version = '0.1',
       description = 'This is a test package',
       ext_modules = [module1])

An example:

03:21 $ python
Python 2.7.10 (default, Jul 30 2016, 18:31:42)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.34)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import iorf
>>> print iorf.fup(2)
3.0
>>> print iorf.fup(2.5)
3.5
Hack Saw
  • 2,741
  • 1
  • 18
  • 33
  • But only because the C compiler converted the `int` to a `float` *before* calling the function. –  Oct 04 '16 at 06:28
  • Is that a problem? – Hack Saw Oct 04 '16 at 06:29
  • I don't think it's a given that the Python runtime will do the same. –  Oct 04 '16 at 06:37
  • The PyArg_ParseTuple function loads the arguments into C typed variables. After that, C's integer promotion algorithms will handle it. – Hack Saw Oct 04 '16 at 06:46
  • 1
    Then the problem remains, right? OP would still need to call ParseTuple up to two times, once for the case that the argument was an `int` object, and once for `float`. The question doesn't seem to be about what you do with the value afterwards. –  Oct 04 '16 at 07:28
  • 1
    No. Look at the example. I pass it an int, and a float, and it gives the right answer. Try it. – Hack Saw Oct 04 '16 at 07:41
  • @HackSaw I see that it works (tried it myself), I just don't understand why. Does python convert their ints to floats like c? It sure seems like it. – ansg191 Oct 05 '16 at 05:56
  • From a mathematical standpoint, it's a kindness to try and treat integers as floats when you can. That said, how one handles things when you can't is implementation dependent, such is when you are dealing with integers larger than 10^37. – Hack Saw Oct 05 '16 at 20:27
  • As soon as `x` is 16777216 this won't work. `(float)(16777216) + 1` is still 16777216, not 16777217. A 32-bit float can only store integers with up to 24 significant bits. One has the same problem with a `double` and a `uint64_t`. This really isn't an answer, as it's effectively just "always use floating-point an never integers." If that worked, why would integer types still exist in basically every language? – TrentP Apr 09 '21 at 19:51
1

You can check type of input value like this:

    PyObject* check_type(PyObject*self, PyObject*args) {
    PyObject*any;

    if (!PyArg_ParseTuple(args, "O", &any)) {
        PyErr_SetString(PyExc_TypeError, "Nope.");
        return NULL;
    }

    if (PyFloat_Check(any)) {
        printf("indeed float");
    }
    else {
        printf("\nint\n");
    }

    Py_INCREF(Py_None);

    return Py_None;
}

You can extract float value from object using:

double result=PyFloat_AsDouble(any);

But in this particular situation probably no need to do this, no matter what you parsing int or float you can grab it as a float and check on roundness:

    float target;
    if (!PyArg_ParseTuple(args, "f", &target)) {
                PyErr_SetString(PyExc_TypeError, "Nope.");
                return NULL;
    }

    if (target - (int)target) {
        printf("\n input is float \n");
    }
    else {
        printf("\n input is int \n");
    }
Anatoly Strashkevich
  • 1,834
  • 4
  • 16
  • 32
1

Floats are (usually) passed in via registers while ints are (usually) passed in via the stack. This means that you literally cannot, inside the function, check whether the argument is a float or an int.

The only workaround is to use variadic arguments, with the first argument specifying the type as either int or double (not float).

func_int_or_double (uint8_t type, ...) {
va_list ap;
va_start (ap, type);
int intarg;
double doublearg;
if (type==1) {
   intarg = va_arg (ap, int);
}
if (type==2) {
   doublearg = va_arg (ap, double);
}
va_end (ap);
// Your code goes here
}

Although, I'm not really sure if python can handle calling variadic functions, so YMMV. As a last ditch-effort you can always sprintf the value into a buffer and let your function sscanf float/int from the buffer.

Lelanthran
  • 1,510
  • 12
  • 18