Skip to content

Commit 1512458

Browse files
committed
[GR-10334] Math.acosh is missing.
1 parent 0c1a24b commit 1512458

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import math
66
import unittest
77
import sys
8+
import struct
89

910
eps = 1E-05
1011
INF = float('inf')
@@ -44,6 +45,40 @@ def py_factorial(n):
4445
outer *= inner
4546
return outer << (n - count_set_bits(n))
4647

48+
def to_ulps(x):
49+
"""Convert a non-NaN float x to an integer, in such a way that
50+
adjacent floats are converted to adjacent integers. Then
51+
abs(ulps(x) - ulps(y)) gives the difference in ulps between two
52+
floats.
53+
54+
The results from this function will only make sense on platforms
55+
where native doubles are represented in IEEE 754 binary64 format.
56+
57+
Note: 0.0 and -0.0 are converted to 0 and -1, respectively.
58+
"""
59+
n = struct.unpack('<q', struct.pack('<d', x))[0]
60+
if n < 0:
61+
n = ~(n+2**63)
62+
return n
63+
64+
def ulp_abs_check(expected, got, ulp_tol, abs_tol):
65+
"""Given finite floats `expected` and `got`, check that they're
66+
approximately equal to within the given number of ulps or the
67+
given absolute tolerance, whichever is bigger.
68+
69+
Returns None on success and an error message on failure.
70+
"""
71+
ulp_error = abs(to_ulps(expected) - to_ulps(got))
72+
abs_error = abs(expected - got)
73+
74+
# Succeed if either abs_error <= abs_tol or ulp_error <= ulp_tol.
75+
if abs_error <= abs_tol or ulp_error <= ulp_tol:
76+
return None
77+
else:
78+
fmt = ("error = {:.3g} ({:d} ulps); "
79+
"permitted error = {:.3g} or {:d} ulps")
80+
return fmt.format(abs_error, ulp_error, abs_tol, ulp_tol)
81+
4782
def result_check(expected, got, ulp_tol=5, abs_tol=0.0):
4883
# Common logic of MathTests.(ftest, test_testcases, test_mtestcases)
4984
"""Compare arguments expected and got, as floats, if either
@@ -140,6 +175,24 @@ def __float__(self):
140175
return 'ahoj'
141176
self.assertRaises(TypeError, math.acos, MyFloat3())
142177

178+
def testAcosh(self):
179+
self.assertRaises(TypeError, math.acosh)
180+
self.ftest('acosh(1)', math.acosh(1), 0)
181+
# TODO uncomment when GR-10346 will be fixed
182+
#self.ftest('acosh(2)', math.acosh(2), 1.3169578969248168)
183+
self.assertRaises(ValueError, math.acosh, 0)
184+
self.assertRaises(ValueError, math.acosh, -1)
185+
self.assertEqual(math.acosh(INF), INF)
186+
self.assertRaises(ValueError, math.acosh, NINF)
187+
self.assertTrue(math.isnan(math.acosh(NAN)))
188+
189+
class MyFF:
190+
def __float__(self):
191+
return 6
192+
# TODO uncomment when GR-10346 will be fixed
193+
#self.ftest('acos(MyFloat())', math.acosh(MyFF()), 0.9272952180016123)
194+
self.assertRaises(ValueError, math.acosh, MyFloat())
195+
143196
def testIsfinite(self):
144197
self.assertTrue(math.isfinite(0.0))
145198
self.assertTrue(math.isfinite(-0.0))

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MathModuleBuiltins.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import com.oracle.graal.python.nodes.PNode;
4444
import com.oracle.graal.python.nodes.call.special.LookupAndCallUnaryNode;
4545
import com.oracle.graal.python.nodes.function.PythonBuiltinNode;
46+
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
4647
import com.oracle.graal.python.nodes.truffle.PythonArithmeticTypes;
4748
import com.oracle.graal.python.runtime.exception.PythonErrorType;
4849
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
@@ -874,6 +875,45 @@ protected AcosNode create() {
874875
}
875876
}
876877

878+
@Builtin(name = "acosh", fixedNumOfArguments = 1, doc = "Return the inverse hyperbolic cosine of x.")
879+
@TypeSystemReference(PythonArithmeticTypes.class)
880+
@ImportStatic(MathGuards.class)
881+
@GenerateNodeFactory
882+
public abstract static class AcoshNode extends PythonUnaryBuiltinNode {
883+
884+
public abstract double executeObject(Object value);
885+
886+
@Specialization
887+
public double acoshInt(long value,
888+
@Cached("createBinaryProfile()") ConditionProfile doNotFit) {
889+
return acoshDouble(value, doNotFit);
890+
}
891+
892+
@Specialization
893+
public double acoshDouble(double value,
894+
@Cached("createBinaryProfile()") ConditionProfile doNotFit) {
895+
if (doNotFit.profile(value < 1 )) {
896+
throw raise(ValueError, "math domain error");
897+
}
898+
return Math.log(value + Math.sqrt(value*value - 1.0));
899+
}
900+
901+
@Specialization(guards = "!isNumber(value)")
902+
public double acosh(Object value,
903+
@Cached("create(__FLOAT__)") LookupAndCallUnaryNode dispatchFloat,
904+
@Cached("create()") AcoshNode acoshNode) {
905+
Object result = dispatchFloat.executeObject(value);
906+
if (result == PNone.NO_VALUE) {
907+
throw raise(TypeError, "must be real number, not %p", value);
908+
}
909+
return acoshNode.executeObject(result);
910+
}
911+
912+
protected AcoshNode create() {
913+
return MathModuleBuiltinsFactory.AcoshNodeFactory.create(new PNode[0]);
914+
}
915+
}
916+
877917
@Builtin(name = "cos", fixedNumOfArguments = 1)
878918
@GenerateNodeFactory
879919
public abstract static class CosNode extends PythonBuiltinNode {

0 commit comments

Comments
 (0)