Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ddd59a9

Browse filesBrowse files
authored
Merge pull request #10741 from eric-wieser/as_integer_ratio
ENH: Implement `np.floating.as_integer_ratio`
2 parents 32acbd3 + 4bebf05 commit ddd59a9
Copy full SHA for ddd59a9

File tree

4 files changed

+239
-0
lines changed
Filter options

4 files changed

+239
-0
lines changed

‎doc/release/1.17.0-notes.rst

Copy file name to clipboardExpand all lines: doc/release/1.17.0-notes.rst
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ New mode "empty" for ``np.pad``
134134
This mode pads an array to a desired shape without initializing the new
135135
entries.
136136

137+
Floating point scalars implement ``as_integer_ratio`` to match the builtin float
138+
--------------------------------------------------------------------------------
139+
This returns a (numerator, denominator) pair, which can be used to construct a
140+
`fractions.Fraction`.
141+
142+
137143
Improvements
138144
============
139145

‎numpy/core/_add_newdocs.py

Copy file name to clipboardExpand all lines: numpy/core/_add_newdocs.py
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6799,3 +6799,21 @@ def add_newdoc_for_scalar_type(obj, fixed_aliases, doc):
67996799
"""
68006800
Any Python object.
68016801
""")
6802+
6803+
# TODO: work out how to put this on the base class, np.floating
6804+
for float_name in ('half', 'single', 'double', 'longdouble'):
6805+
add_newdoc('numpy.core.numerictypes', float_name, ('as_integer_ratio',
6806+
"""
6807+
{ftype}.as_integer_ratio() -> (int, int)
6808+
6809+
Return a pair of integers, whose ratio is exactly equal to the original
6810+
floating point number, and with a positive denominator.
6811+
Raise OverflowError on infinities and a ValueError on NaNs.
6812+
6813+
>>> np.{ftype}(10.0).as_integer_ratio()
6814+
(10, 1)
6815+
>>> np.{ftype}(0.0).as_integer_ratio()
6816+
(0, 1)
6817+
>>> np.{ftype}(-.25).as_integer_ratio()
6818+
(-1, 4)
6819+
""".format(ftype=float_name)))

‎numpy/core/src/multiarray/scalartypes.c.src

Copy file name to clipboardExpand all lines: numpy/core/src/multiarray/scalartypes.c.src
+106Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1993,6 +1993,92 @@ static PyObject *
19931993
}
19941994
/**end repeat**/
19951995

1996+
/**begin repeat
1997+
* #name = half, float, double, longdouble#
1998+
* #Name = Half, Float, Double, LongDouble#
1999+
* #is_half = 1,0,0,0#
2000+
* #c = f, f, , l#
2001+
* #convert = PyLong_FromDouble, PyLong_FromDouble, PyLong_FromDouble,
2002+
* npy_longdouble_to_PyLong#
2003+
* #
2004+
*/
2005+
/* Heavily copied from the builtin float.as_integer_ratio */
2006+
static PyObject *
2007+
@name@_as_integer_ratio(PyObject *self)
2008+
{
2009+
#if @is_half@
2010+
npy_double val = npy_half_to_double(PyArrayScalar_VAL(self, @Name@));
2011+
npy_double frac;
2012+
#else
2013+
npy_@name@ val = PyArrayScalar_VAL(self, @Name@);
2014+
npy_@name@ frac;
2015+
#endif
2016+
int exponent;
2017+
int i;
2018+
2019+
PyObject *py_exponent = NULL;
2020+
PyObject *numerator = NULL;
2021+
PyObject *denominator = NULL;
2022+
PyObject *result_pair = NULL;
2023+
PyNumberMethods *long_methods = PyLong_Type.tp_as_number;
2024+
2025+
if (npy_isnan(val)) {
2026+
PyErr_SetString(PyExc_ValueError,
2027+
"cannot convert NaN to integer ratio");
2028+
return NULL;
2029+
}
2030+
if (!npy_isfinite(val)) {
2031+
PyErr_SetString(PyExc_OverflowError,
2032+
"cannot convert Infinity to integer ratio");
2033+
return NULL;
2034+
}
2035+
2036+
frac = npy_frexp@c@(val, &exponent); /* val == frac * 2**exponent exactly */
2037+
2038+
/* This relies on the floating point type being base 2 to converge */
2039+
for (i = 0; frac != npy_floor@c@(frac); i++) {
2040+
frac *= 2.0;
2041+
exponent--;
2042+
}
2043+
2044+
/* self == frac * 2**exponent exactly and frac is integral. */
2045+
numerator = @convert@(frac);
2046+
if (numerator == NULL)
2047+
goto error;
2048+
denominator = PyLong_FromLong(1);
2049+
if (denominator == NULL)
2050+
goto error;
2051+
py_exponent = PyLong_FromLong(exponent < 0 ? -exponent : exponent);
2052+
if (py_exponent == NULL)
2053+
goto error;
2054+
2055+
/* fold in 2**exponent */
2056+
if (exponent > 0) {
2057+
PyObject *temp = long_methods->nb_lshift(numerator, py_exponent);
2058+
if (temp == NULL)
2059+
goto error;
2060+
Py_DECREF(numerator);
2061+
numerator = temp;
2062+
}
2063+
else {
2064+
PyObject *temp = long_methods->nb_lshift(denominator, py_exponent);
2065+
if (temp == NULL)
2066+
goto error;
2067+
Py_DECREF(denominator);
2068+
denominator = temp;
2069+
}
2070+
2071+
result_pair = PyTuple_Pack(2, numerator, denominator);
2072+
2073+
error:
2074+
Py_XDECREF(py_exponent);
2075+
Py_XDECREF(denominator);
2076+
Py_XDECREF(numerator);
2077+
return result_pair;
2078+
}
2079+
/**end repeat**/
2080+
2081+
19962082
/*
19972083
* need to fill in doc-strings for these methods on import -- copy from
19982084
* array docstrings
@@ -2256,6 +2342,17 @@ static PyMethodDef @name@type_methods[] = {
22562342
};
22572343
/**end repeat**/
22582344

2345+
/**begin repeat
2346+
* #name = half,float,double,longdouble#
2347+
*/
2348+
static PyMethodDef @name@type_methods[] = {
2349+
{"as_integer_ratio",
2350+
(PyCFunction)@name@_as_integer_ratio,
2351+
METH_NOARGS, NULL},
2352+
{NULL, NULL, 0, NULL}
2353+
};
2354+
/**end repeat**/
2355+
22592356
/************* As_mapping functions for void array scalar ************/
22602357

22612358
static Py_ssize_t
@@ -4311,6 +4408,15 @@ initialize_numeric_types(void)
43114408

43124409
/**end repeat**/
43134410

4411+
/**begin repeat
4412+
* #name = half, float, double, longdouble#
4413+
* #Name = Half, Float, Double, LongDouble#
4414+
*/
4415+
4416+
Py@Name@ArrType_Type.tp_methods = @name@type_methods;
4417+
4418+
/**end repeat**/
4419+
43144420
#if (NPY_SIZEOF_INT != NPY_SIZEOF_LONG) || defined(NPY_PY3K)
43154421
/* We won't be inheriting from Python Int type. */
43164422
PyIntArrType_Type.tp_hash = int_arrtype_hash;
+109Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""
2+
Test the scalar constructors, which also do type-coercion
3+
"""
4+
from __future__ import division, absolute_import, print_function
5+
6+
import os
7+
import fractions
8+
import platform
9+
10+
import pytest
11+
import numpy as np
12+
13+
from numpy.testing import (
14+
run_module_suite,
15+
assert_equal, assert_almost_equal, assert_raises, assert_warns,
16+
dec
17+
)
18+
19+
class TestAsIntegerRatio(object):
20+
# derived in part from the cpython test "test_floatasratio"
21+
22+
@pytest.mark.parametrize("ftype", [
23+
np.half, np.single, np.double, np.longdouble])
24+
@pytest.mark.parametrize("f, ratio", [
25+
(0.875, (7, 8)),
26+
(-0.875, (-7, 8)),
27+
(0.0, (0, 1)),
28+
(11.5, (23, 2)),
29+
])
30+
def test_small(self, ftype, f, ratio):
31+
assert_equal(ftype(f).as_integer_ratio(), ratio)
32+
33+
@pytest.mark.parametrize("ftype", [
34+
np.half, np.single, np.double, np.longdouble])
35+
def test_simple_fractions(self, ftype):
36+
R = fractions.Fraction
37+
assert_equal(R(0, 1),
38+
R(*ftype(0.0).as_integer_ratio()))
39+
assert_equal(R(5, 2),
40+
R(*ftype(2.5).as_integer_ratio()))
41+
assert_equal(R(1, 2),
42+
R(*ftype(0.5).as_integer_ratio()))
43+
assert_equal(R(-2100, 1),
44+
R(*ftype(-2100.0).as_integer_ratio()))
45+
46+
@pytest.mark.parametrize("ftype", [
47+
np.half, np.single, np.double, np.longdouble])
48+
def test_errors(self, ftype):
49+
assert_raises(OverflowError, ftype('inf').as_integer_ratio)
50+
assert_raises(OverflowError, ftype('-inf').as_integer_ratio)
51+
assert_raises(ValueError, ftype('nan').as_integer_ratio)
52+
53+
def test_against_known_values(self):
54+
R = fractions.Fraction
55+
assert_equal(R(1075, 512),
56+
R(*np.half(2.1).as_integer_ratio()))
57+
assert_equal(R(-1075, 512),
58+
R(*np.half(-2.1).as_integer_ratio()))
59+
assert_equal(R(4404019, 2097152),
60+
R(*np.single(2.1).as_integer_ratio()))
61+
assert_equal(R(-4404019, 2097152),
62+
R(*np.single(-2.1).as_integer_ratio()))
63+
assert_equal(R(4728779608739021, 2251799813685248),
64+
R(*np.double(2.1).as_integer_ratio()))
65+
assert_equal(R(-4728779608739021, 2251799813685248),
66+
R(*np.double(-2.1).as_integer_ratio()))
67+
# longdouble is platform depedent
68+
69+
@pytest.mark.parametrize("ftype, frac_vals, exp_vals", [
70+
# dtype test cases generated using hypothesis
71+
# first five generated cases per dtype
72+
(np.half, [0.0, 0.01154830649280303, 0.31082276347447274,
73+
0.527350517124794, 0.8308562335072596],
74+
[0, 1, 0, -8, 12]),
75+
(np.single, [0.0, 0.09248576989263226, 0.8160498218131407,
76+
0.17389442853722373, 0.7956044195067877],
77+
[0, 12, 10, 17, -26]),
78+
(np.double, [0.0, 0.031066908499895136, 0.5214135908877832,
79+
0.45780736035689296, 0.5906586745934036],
80+
[0, -801, 51, 194, -653]),
81+
pytest.param(
82+
np.longdouble,
83+
[0.0, 0.20492557202724854, 0.4277180662199366, 0.9888085019891495,
84+
0.9620175814461964],
85+
[0, -7400, 14266, -7822, -8721],
86+
marks=[
87+
pytest.mark.skipif(
88+
np.finfo(np.double) == np.finfo(np.longdouble),
89+
reason="long double is same as double"),
90+
pytest.mark.skipif(
91+
platform.machine().startswith("ppc"),
92+
reason="IBM double double"),
93+
]
94+
)
95+
])
96+
def test_roundtrip(self, ftype, frac_vals, exp_vals):
97+
for frac, exp in zip(frac_vals, exp_vals):
98+
f = np.ldexp(frac, exp, dtype=ftype)
99+
n, d = f.as_integer_ratio()
100+
101+
try:
102+
# workaround for gh-9968
103+
nf = np.longdouble(str(n))
104+
df = np.longdouble(str(d))
105+
except (OverflowError, RuntimeWarning):
106+
# the values may not fit in any float type
107+
pytest.skip("longdouble too small on this platform")
108+
109+
assert_equal(nf / df, f, "{}/{}".format(n, d))

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.