How can I write a C function that takes either an int or a float?
Asked Answered
L

3

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?

Leftwich answered 4/10, 2016 at 4:19 Comment(2)
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.Bujumbura
You probably want to work with PyObject and then use the Number Protocol for working with the numbers. If you need to coerce to a float for example, you can call PyNumber_Float on your PyObject.Lykins
K
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
Kletter answered 4/10, 2016 at 6:23 Comment(9)
But only because the C compiler converted the int to a float before calling the function.Tuckerbag
Is that a problem?Kletter
I don't think it's a given that the Python runtime will do the same.Tuckerbag
The PyArg_ParseTuple function loads the arguments into C typed variables. After that, C's integer promotion algorithms will handle it.Kletter
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.Tuckerbag
No. Look at the example. I pass it an int, and a float, and it gives the right answer. Try it.Kletter
@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.Leftwich
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.Kletter
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?Nimrod
B
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");
    }
Butter answered 4/10, 2016 at 5:3 Comment(0)
C
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.

Crudden answered 4/10, 2016 at 7:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.