diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_math_log.py b/graalpython/com.oracle.graal.python.test/src/tests/test_math_log.py new file mode 100644 index 0000000000..5375d0b393 --- /dev/null +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_math_log.py @@ -0,0 +1,96 @@ +# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +import math + + +def assert_raises(err, fn, *args, **kwargs): + raised = False + try: + fn(*args, **kwargs) + except err: + raised = True + assert raised + + +class MyInt(int): + pass + + +class PositiveFloat: + def __float__(self): + return 10.0 + + +class NegativeFloat: + def __float__(self): + return -1.0 + + +def test_log_base_none(): + assert_raises(TypeError, math.log, 10, None) + assert_raises(TypeError, math.log, 10.0, None) + assert_raises(TypeError, math.log, 2**70, None) + assert_raises(TypeError, math.log, PositiveFloat(), None) + assert_raises(ValueError, math.log, -1, None) + assert math.log(1) == 0.0 + assert math.log(1.0) == 0.0 + assert 48.52 < math.log(2**70) < 48.53 + + +def test_log_value_checked_before_base(): + assert_raises(ValueError, math.log, -1, object()) + assert_raises(ValueError, math.log, 0, object()) + assert_raises(ValueError, math.log, -1.0, object()) + assert_raises(ValueError, math.log, -2**70, object()) + assert_raises(ValueError, math.log, NegativeFloat(), object()) + assert_raises(TypeError, math.log, float('nan'), object()) + + +def test_log_zero_with_base(): + assert_raises(ValueError, math.log, 0, 2) + assert_raises(ValueError, math.log, 0.0, 2.0) + assert_raises(ValueError, math.log, -0.0, 2) + assert_raises(ValueError, math.log, False, 2) + assert_raises(ValueError, math.log, 0, 1) + assert_raises(ValueError, math.log, 0, MyInt(2)) + assert_raises(ValueError, math.log, 0.0, MyInt(2)) + assert_raises(ValueError, math.log, MyInt(0)) + assert_raises(ValueError, math.log, MyInt(0), 2.0) + assert_raises(ValueError, math.log, MyInt(0), 2) + assert_raises(ValueError, math.log, MyInt(0), MyInt(2)) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MathModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MathModuleBuiltins.java index 029f6ac3cf..0af8758e68 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MathModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MathModuleBuiltins.java @@ -1675,7 +1675,7 @@ private static double countBase(BigInteger base, Node inliningTarget, InlinedCon return logBase; } - @Specialization + @Specialization(guards = "isNoValue(novalue)") static double log(long value, PNone novalue, @Bind Node inliningTarget, @Shared @Cached InlinedConditionProfile doNotFit, @@ -1683,7 +1683,7 @@ static double log(long value, PNone novalue, return logDN(value, novalue, inliningTarget, doNotFit, raiseNode); } - @Specialization + @Specialization(guards = "isNoValue(novalue)") static double logDN(double value, @SuppressWarnings("unused") PNone novalue, @Bind Node inliningTarget, @Shared @Cached InlinedConditionProfile doNotFit, @@ -1692,14 +1692,14 @@ static double logDN(double value, @SuppressWarnings("unused") PNone novalue, return Math.log(value); } - @Specialization + @Specialization(guards = "isNoValue(novalue)") @TruffleBoundary static double logPIN(PInt value, @SuppressWarnings("unused") PNone novalue, @Bind Node inliningTarget, @Shared @Cached InlinedConditionProfile doNotFit, @Shared @Cached PRaiseNode raiseNode) { BigInteger bValue = value.getValue(); - raiseMathError(inliningTarget, doNotFit, bValue.compareTo(BigInteger.ZERO) < 0, raiseNode); + raiseMathError(inliningTarget, doNotFit, bValue.compareTo(BigInteger.ZERO) <= 0, raiseNode); return logBigInteger(bValue); } @@ -1736,7 +1736,7 @@ static double logDD(double value, double base, @Shared @Cached InlinedConditionProfile doNotFit, @Shared @Cached InlinedConditionProfile divByZero, @Shared @Cached PRaiseNode raiseNode) { - raiseMathError(inliningTarget, doNotFit, value < 0 || base <= 0, raiseNode); + raiseMathError(inliningTarget, doNotFit, value <= 0 || base <= 0, raiseNode); double logBase = countBase(base, inliningTarget, divByZero, raiseNode); return Math.log(value) / logBase; } @@ -1749,7 +1749,7 @@ static double logDPI(double value, PInt base, @Shared @Cached InlinedConditionProfile divByZero, @Shared @Cached PRaiseNode raiseNode) { BigInteger bBase = base.getValue(); - raiseMathError(inliningTarget, doNotFit, value < 0 || bBase.compareTo(BigInteger.ZERO) <= 0, raiseNode); + raiseMathError(inliningTarget, doNotFit, value <= 0 || bBase.compareTo(BigInteger.ZERO) <= 0, raiseNode); double logBase = countBase(bBase, inliningTarget, divByZero, raiseNode); return Math.log(value) / logBase; } @@ -1771,7 +1771,7 @@ static double logPID(PInt value, double base, @Shared @Cached InlinedConditionProfile divByZero, @Shared @Cached PRaiseNode raiseNode) { BigInteger bValue = value.getValue(); - raiseMathError(inliningTarget, doNotFit, bValue.compareTo(BigInteger.ZERO) < 0 || base <= 0, raiseNode); + raiseMathError(inliningTarget, doNotFit, bValue.compareTo(BigInteger.ZERO) <= 0 || base <= 0, raiseNode); double logBase = countBase(base, inliningTarget, divByZero, raiseNode); return logBigInteger(bValue) / logBase; } @@ -1784,7 +1784,7 @@ static double logLPI(long value, PInt base, @Shared @Cached InlinedConditionProfile divByZero, @Shared @Cached PRaiseNode raiseNode) { BigInteger bBase = base.getValue(); - raiseMathError(inliningTarget, doNotFit, value < 0 || bBase.compareTo(BigInteger.ZERO) <= 0, raiseNode); + raiseMathError(inliningTarget, doNotFit, value <= 0 || bBase.compareTo(BigInteger.ZERO) <= 0, raiseNode); double logBase = countBase(bBase, inliningTarget, divByZero, raiseNode); return Math.log(value) / logBase; } @@ -1798,12 +1798,12 @@ static double logPIPI(PInt value, PInt base, @Shared @Cached PRaiseNode raiseNode) { BigInteger bValue = value.getValue(); BigInteger bBase = base.getValue(); - raiseMathError(inliningTarget, doNotFit, bValue.compareTo(BigInteger.ZERO) < 0 || bBase.compareTo(BigInteger.ZERO) <= 0, raiseNode); + raiseMathError(inliningTarget, doNotFit, bValue.compareTo(BigInteger.ZERO) <= 0 || bBase.compareTo(BigInteger.ZERO) <= 0, raiseNode); double logBase = countBase(bBase, inliningTarget, divByZero, raiseNode); return logBigInteger(bValue) / logBase; } - @Specialization(guards = "!isNumber(value)") + @Specialization(guards = {"!isNumber(value)", "isNoValue(novalue)"}) double logO(VirtualFrame frame, Object value, PNone novalue, @Bind Node inliningTarget, @Shared @Cached PyFloatAsDoubleNode asDoubleNode) { @@ -1813,28 +1813,41 @@ static double logPIPI(PInt value, PInt base, @Specialization(guards = {"!isNumber(value)", "!isNoValue(base)"}) double logOO(VirtualFrame frame, Object value, Object base, @Bind Node inliningTarget, - @Shared @Cached PyFloatAsDoubleNode asDoubleNode) { - return executeRecursiveLogNode(frame, asDoubleNode.execute(frame, inliningTarget, value), asDoubleNode.execute(frame, inliningTarget, base)); + @Shared @Cached PyFloatAsDoubleNode asDoubleNode, + @Shared @Cached InlinedConditionProfile doNotFit, + @Shared @Cached PRaiseNode raiseNode) { + double x = asDoubleNode.execute(frame, inliningTarget, value); + raiseMathError(inliningTarget, doNotFit, x <= 0, raiseNode); + return executeRecursiveLogNode(frame, x, asDoubleNode.execute(frame, inliningTarget, base)); } - @Specialization(guards = {"!isNumber(base)"}) + @Specialization(guards = {"!isNumber(base)", "!isNoValue(base)"}) double logLO(VirtualFrame frame, long value, Object base, @Bind Node inliningTarget, - @Shared @Cached PyFloatAsDoubleNode asDoubleNode) { + @Shared @Cached PyFloatAsDoubleNode asDoubleNode, + @Shared @Cached InlinedConditionProfile doNotFit, + @Shared @Cached PRaiseNode raiseNode) { + raiseMathError(inliningTarget, doNotFit, value <= 0, raiseNode); return executeRecursiveLogNode(frame, value, asDoubleNode.execute(frame, inliningTarget, base)); } - @Specialization(guards = {"!isNumber(base)"}) + @Specialization(guards = {"!isNumber(base)", "!isNoValue(base)"}) double logDO(VirtualFrame frame, double value, Object base, @Bind Node inliningTarget, - @Shared @Cached PyFloatAsDoubleNode asDoubleNode) { + @Shared @Cached PyFloatAsDoubleNode asDoubleNode, + @Shared @Cached InlinedConditionProfile doNotFit, + @Shared @Cached PRaiseNode raiseNode) { + raiseMathError(inliningTarget, doNotFit, value <= 0, raiseNode); return executeRecursiveLogNode(frame, value, asDoubleNode.execute(frame, inliningTarget, base)); } - @Specialization(guards = {"!isNumber(base)"}) + @Specialization(guards = {"!isNumber(base)", "!isNoValue(base)"}) double logPIO(VirtualFrame frame, PInt value, Object base, @Bind Node inliningTarget, - @Shared @Cached PyFloatAsDoubleNode asDoubleNode) { + @Shared @Cached PyFloatAsDoubleNode asDoubleNode, + @Shared @Cached InlinedConditionProfile doNotFit, + @Shared @Cached PRaiseNode raiseNode) { + raiseMathError(inliningTarget, doNotFit, value.isZeroOrNegative(), raiseNode); return executeRecursiveLogNode(frame, value, asDoubleNode.execute(frame, inliningTarget, base)); }