Skip to content

Commit d95ad66

Browse files
committed
[GR-18821] Float.as_integer_ratio function is missing.
PullRequest: graalpython/688
2 parents ac1dcf7 + aaa61c1 commit d95ad66

File tree

3 files changed

+154
-16
lines changed

3 files changed

+154
-16
lines changed

graalpython/com.oracle.graal.python.test/src/tests/test_float.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939

4040
import unittest
4141
import os, sys
42+
import random
43+
import fractions
4244

4345
from math import isnan, copysign, ldexp
4446

@@ -132,6 +134,38 @@ def test_nan(self):
132134
self.assertNotEqual(float('nan'), float('nan'))
133135
self.assertTrue(NAN is NAN)
134136

137+
def test_floatasratio(self):
138+
for f, ratio in [
139+
(0.875, (7, 8)),
140+
(-0.875, (-7, 8)),
141+
(0.0, (0, 1)),
142+
(11.5, (23, 2)),
143+
]:
144+
self.assertEqual(f.as_integer_ratio(), ratio)
145+
146+
for i in range(10000):
147+
f = random.random()
148+
f *= 10 ** random.randint(-100, 100)
149+
n, d = f.as_integer_ratio()
150+
self.assertEqual(float(n).__truediv__(d), f)
151+
152+
R = fractions.Fraction
153+
self.assertEqual(R(0, 1),
154+
R(*float(0.0).as_integer_ratio()))
155+
self.assertEqual(R(5, 2),
156+
R(*float(2.5).as_integer_ratio()))
157+
self.assertEqual(R(1, 2),
158+
R(*float(0.5).as_integer_ratio()))
159+
self.assertEqual(R(4728779608739021, 2251799813685248),
160+
R(*float(2.1).as_integer_ratio()))
161+
self.assertEqual(R(-4728779608739021, 2251799813685248),
162+
R(*float(-2.1).as_integer_ratio()))
163+
self.assertEqual(R(-2100, 1),
164+
R(*float(-2100.0).as_integer_ratio()))
165+
166+
self.assertRaises(OverflowError, float('inf').as_integer_ratio)
167+
self.assertRaises(OverflowError, float('-inf').as_integer_ratio)
168+
self.assertRaises(ValueError, float('nan').as_integer_ratio)
135169

136170
fromHex = float.fromhex
137171

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,49 @@
1-
testAcos
2-
testAsin
3-
testAtan
4-
testAtan2
5-
testAtanh
6-
testCeil
7-
testConstants
8-
testCopysign
9-
testDegrees
10-
testExp
11-
testFabs
12-
testFactorial
13-
testFactorialHugeInputs
14-
testFloor
15-
testFmod
16-
testFrexp
1+
*MathTests.testAcos
2+
*MathTests.testAsin
3+
*MathTests.testAtan
4+
*MathTests.testAtan2
5+
*MathTests.testAtanh
6+
*MathTests.testCeil
7+
*MathTests.testConstants
8+
*MathTests.testCopysign
9+
*MathTests.testDegrees
10+
*MathTests.testExp
11+
*MathTests.testFabs
12+
*MathTests.testFactorial
13+
*MathTests.testFactorialHugeInputs
14+
*MathTests.testFloor
15+
*MathTests.testFmod
16+
*MathTests.testFrexp
17+
*MathTests.testFsum
18+
*MathTests.testGcd
19+
*MathTests.testHypot
20+
*MathTests.testIsfinite
21+
*MathTests.testIsinf
22+
*MathTests.testIsnan
23+
*MathTests.testLdexp
24+
*MathTests.testLog
25+
*MathTests.testLog10
26+
*MathTests.testLog1p
27+
*MathTests.testLog2
28+
*MathTests.testLog2Exact
29+
*MathTests.testModf
30+
*MathTests.testPow
31+
*MathTests.testRadians
32+
*MathTests.testSin
33+
*MathTests.testSqrt
34+
*MathTests.testTanhSign
35+
*MathTests.test_exceptions
36+
*MathTests.test_inf_constant
37+
*MathTests.test_nan_constant
38+
*MathTests.test_trunc
39+
*IsCloseTests.test_asymmetry
40+
*IsCloseTests.test_decimals
41+
*IsCloseTests.test_eight_decimal_places
42+
*IsCloseTests.test_fractions
43+
*IsCloseTests.test_identical
44+
*IsCloseTests.test_identical_infinite
45+
*IsCloseTests.test_inf_ninf_nan
46+
*IsCloseTests.test_integers
47+
*IsCloseTests.test_near_zero
48+
*IsCloseTests.test_negative_tolerances
49+
*IsCloseTests.test_zero_tolerance

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/floats/FloatBuiltins.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import com.oracle.graal.python.builtins.objects.cext.CExtNodes.FromNativeSubclassNode;
7676
import com.oracle.graal.python.builtins.objects.cext.PythonNativeObject;
7777
import com.oracle.graal.python.builtins.objects.ints.PInt;
78+
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
7879
import com.oracle.graal.python.builtins.objects.type.LazyPythonClass;
7980
import com.oracle.graal.python.nodes.call.special.LookupAndCallVarargsNode;
8081
import com.oracle.graal.python.nodes.classes.IsSubtypeNode;
@@ -1259,6 +1260,76 @@ abstract static class ImagNode extends PythonBuiltinNode {
12591260

12601261
}
12611262

1263+
@GenerateNodeFactory
1264+
@Builtin(name = "as_integer_ratio", minNumOfPositionalArgs = 1)
1265+
abstract static class AsIntegerRatio extends PythonBuiltinNode {
1266+
1267+
@Specialization
1268+
PTuple get(double self,
1269+
@Cached("createBinaryProfile()") ConditionProfile nanProfile,
1270+
@Cached("createBinaryProfile()") ConditionProfile infProfile) {
1271+
if (nanProfile.profile(Double.isNaN(self))) {
1272+
throw raise(PythonErrorType.ValueError, "cannot convert NaN to integer ratio");
1273+
}
1274+
if (infProfile.profile(Double.isInfinite(self))) {
1275+
throw raise(PythonErrorType.OverflowError, "cannot convert Infinity to integer ratio");
1276+
}
1277+
1278+
// At the first time find mantissa and exponent. This is functionanlity of Math.frexp
1279+
// node basically.
1280+
int exponent = 0;
1281+
double mantissa = 0.0;
1282+
1283+
if (!(self == 0.0 || self == -0.0)) {
1284+
boolean neg = false;
1285+
mantissa = self;
1286+
1287+
if (mantissa < 0) {
1288+
mantissa = -mantissa;
1289+
neg = true;
1290+
}
1291+
if (mantissa >= 1.0) {
1292+
while (mantissa >= 1) {
1293+
++exponent;
1294+
mantissa /= 2;
1295+
}
1296+
} else if (mantissa < 0.5) {
1297+
while (mantissa < 0.5) {
1298+
--exponent;
1299+
mantissa *= 2;
1300+
}
1301+
}
1302+
if (neg) {
1303+
mantissa = -mantissa;
1304+
}
1305+
}
1306+
1307+
// count the ratio
1308+
return factory().createTuple(countIt(mantissa, exponent));
1309+
}
1310+
1311+
@TruffleBoundary
1312+
private Object[] countIt(double manitssa, int exponent) {
1313+
for (int i = 0; i < 300 && Double.compare(manitssa, Math.floor(manitssa)) != 0; i++) {
1314+
manitssa *= 2.0;
1315+
exponent--;
1316+
}
1317+
1318+
BigInteger numerator = BigInteger.valueOf((new Double(manitssa)).longValue());
1319+
BigInteger denominator = BigInteger.ONE;
1320+
BigInteger py_exponent = denominator.shiftLeft(Math.abs(exponent));
1321+
if (exponent > 0) {
1322+
numerator = numerator.multiply(py_exponent);
1323+
} else {
1324+
denominator = py_exponent;
1325+
}
1326+
if (numerator.bitLength() < Long.SIZE && denominator.bitLength() < Long.SIZE) {
1327+
return new Object[]{numerator.longValue(), denominator.longValue()};
1328+
}
1329+
return new Object[]{factory().createInt(numerator), factory().createInt(denominator)};
1330+
}
1331+
}
1332+
12621333
@GenerateNodeFactory
12631334
@Builtin(name = "conjugate", minNumOfPositionalArgs = 1, doc = "Returns self, the complex conjugate of any float.")
12641335
abstract static class ConjugateNode extends RealNode {

0 commit comments

Comments
 (0)