Commit 99b874cb authored by Stefan Behnel's avatar Stefan Behnel

Optimise calling float() on bytes/bytearray values by returning a C double directly.

parent 2d200b1c
......@@ -21,6 +21,8 @@ Features added
also when compiled by Cython.
Patch by Pedro Marques da Luz. (Github issue #2273)
* ``float()`` is optimised for ``bytes`` and ``bytearray`` arguments.
* Docstrings of ``cpdef`` enums are now copied to the enum class.
Patch by matham. (Github issue #3805)
......
......@@ -2189,7 +2189,7 @@ class OptimizeBuiltinCalls(Visitor.NodeRefCleanupMixin,
func_arg = arg.args[0]
if func_arg.type is Builtin.float_type:
return func_arg.as_none_safe_node("float() argument must be a string or a number, not 'NoneType'")
elif func_arg.type.is_pyobject:
elif func_arg.type.is_pyobject and arg.function.cname == "__Pyx__PyObject_AsDouble":
return ExprNodes.PythonCapiCallNode(
node.pos, '__Pyx_PyNumber_Float', self.PyNumber_Float_func_type,
args=[func_arg],
......@@ -2619,6 +2619,7 @@ class OptimizeBuiltinCalls(Visitor.NodeRefCleanupMixin,
elif len(pos_args) != 1:
self._error_wrong_arg_count('float', node, pos_args, '0 or 1')
return node
func_arg = pos_args[0]
if isinstance(func_arg, ExprNodes.CoerceToPyTypeNode):
func_arg = func_arg.arg
......@@ -2627,12 +2628,23 @@ class OptimizeBuiltinCalls(Visitor.NodeRefCleanupMixin,
elif node.type.assignable_from(func_arg.type) or func_arg.type.is_numeric:
return ExprNodes.TypecastNode(
node.pos, operand=func_arg, type=node.type)
if func_arg.type is Builtin.bytes_type:
cfunc_name = "__Pyx_PyBytes_AsDouble"
utility_code_name = 'pybytes_as_double'
elif func_arg.type is Builtin.bytearray_type:
cfunc_name = "__Pyx_PyByteArray_AsDouble"
utility_code_name = 'pybytes_as_double'
else:
cfunc_name = "__Pyx_PyObject_AsDouble"
utility_code_name = 'pyobject_as_double'
return ExprNodes.PythonCapiCallNode(
node.pos, "__Pyx_PyObject_AsDouble",
node.pos, cfunc_name,
self.PyObject_AsDouble_func_type,
args = pos_args,
is_temp = node.is_temp,
utility_code = load_c_utility('pyobject_as_double'),
utility_code = load_c_utility(utility_code_name),
py_name = "float")
PyNumber_Int_func_type = PyrexTypes.CFuncType(
......
......@@ -596,6 +596,7 @@ static double __Pyx__PyObject_AsDouble(PyObject* obj); /* proto */
#endif
/////////////// pyobject_as_double ///////////////
//@requires: pybytes_as_double
//@requires: ObjectHandling.c::PyObjectCallOneArg
static double __Pyx__PyObject_AsDouble(PyObject* obj) {
......@@ -604,9 +605,20 @@ static double __Pyx__PyObject_AsDouble(PyObject* obj) {
float_value = PyNumber_Float(obj); if ((0)) goto bad;
// avoid "unused" warnings
(void)__Pyx_PyObject_CallOneArg;
(void)__Pyx__PyBytes_AsDouble;
#else
PyNumberMethods *nb = Py_TYPE(obj)->tp_as_number;
if (likely(nb) && likely(nb->nb_float)) {
if (PyBytes_CheckExact(obj)) {
return __Pyx_PyBytes_AsDouble(obj);
} else if (PyByteArray_CheckExact(obj)) {
return __Pyx_PyByteArray_AsDouble(obj);
} else if (PyUnicode_CheckExact(obj)) {
#if PY_MAJOR_VERSION >= 3
float_value = PyFloat_FromString(obj);
#else
float_value = PyFloat_FromString(obj, 0);
#endif
} else if (likely(nb) && likely(nb->nb_float)) {
float_value = nb->nb_float(obj);
if (likely(float_value) && unlikely(!PyFloat_Check(float_value))) {
__Pyx_TypeName float_value_type_name = __Pyx_PyType_GetName(Py_TYPE(float_value));
......@@ -617,12 +629,6 @@ static double __Pyx__PyObject_AsDouble(PyObject* obj) {
Py_DECREF(float_value);
goto bad;
}
} else if (PyUnicode_CheckExact(obj) || PyBytes_CheckExact(obj)) {
#if PY_MAJOR_VERSION >= 3
float_value = PyFloat_FromString(obj);
#else
float_value = PyFloat_FromString(obj, 0);
#endif
} else {
float_value = __Pyx_PyObject_CallOneArg((PyObject*)&PyFloat_Type, obj);
}
......@@ -637,6 +643,50 @@ bad:
}
/////////////// pybytes_as_double.proto ///////////////
static double __Pyx__PyBytes_AsDouble(PyObject *obj, const char* start, Py_ssize_t length);/*proto*/
static CYTHON_INLINE double __Pyx_PyBytes_AsDouble(PyObject *obj) {
return __Pyx__PyBytes_AsDouble(obj, PyBytes_AS_STRING(obj), PyBytes_GET_SIZE(obj));
}
static CYTHON_INLINE double __Pyx_PyByteArray_AsDouble(PyObject *obj) {
return __Pyx__PyBytes_AsDouble(obj, PyByteArray_AS_STRING(obj), PyByteArray_GET_SIZE(obj));
}
/////////////// pybytes_as_double ///////////////
static double __Pyx__PyBytes_AsDouble(PyObject *bytes, const char* start, Py_ssize_t length) {
PyObject *float_value;
if (likely(!memchr(start, '_', length))) {
const char *end, *last = start + length;
double value;
while (Py_ISSPACE(*start))
start++;
while (start < last - 1 && Py_ISSPACE(last[-1]))
last--;
value = PyOS_string_to_double(start, (char**) &end, NULL);
if (likely(end == last) || (value == (double)-1 && PyErr_Occurred())) {
return value;
}
}
// slow fallback
#if PY_MAJOR_VERSION >= 3
float_value = PyFloat_FromString(bytes);
#else
float_value = PyFloat_FromString(bytes, 0);
#endif
if (likely(float_value)) {
double value = PyFloat_AS_DOUBLE(float_value);
Py_DECREF(float_value);
return value;
}
bad:
return (double)-1;
}
/////////////// PyNumberPow2.proto ///////////////
#define __Pyx_PyNumber_InPlacePowerOf2(a, b, c) __Pyx__PyNumber_PowerOf2(a, b, c, 1)
......
# mode: run
# tag: pure3.0
import cython
def empty_float():
"""
......@@ -26,3 +30,31 @@ def float_call_conjugate():
"""
x = float(1.5).conjugate()
return x
@cython.test_assert_path_exists(
"//CoerceToPyTypeNode",
"//CoerceToPyTypeNode//PythonCapiCallNode",
)
def from_bytes(s: bytes):
"""
>>> from_bytes(b"123")
123.0
>>> from_bytes(b"123.25")
123.25
>>> from_bytes(b"123E100")
1.23e+102
"""
return float(s)
@cython.test_assert_path_exists(
"//CoerceToPyTypeNode",
"//CoerceToPyTypeNode//PythonCapiCallNode",
)
def from_bytes_literals():
"""
>>> from_bytes_literals()
(123.0, 123.23, 1e+100)
"""
return float(b"123"), float(b"123.23"), float(b"1e100")
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment