From 6d3964d124b285f7f7786fba13b8680ee88c2ff8 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Mon, 5 Oct 2026 10:58:49 +0200 Subject: [PATCH 01/31] Add skeleton of _decimal module --- .../graal/python/builtins/Python3Core.java | 12 + .../builtins/PythonBuiltinClassType.java | 10 + .../modules/DecimalModuleBuiltins.java | 166 +++ .../objects/decimal/DecimalBuiltins.java | 1105 +++++++++++++++++ .../decimal/DecimalContextBuiltins.java | 1068 ++++++++++++++++ .../DecimalContextManagerBuiltins.java | 111 ++ .../decimal/DecimalExceptionTypes.java | 74 ++ .../objects/decimal/DecimalModuleState.java | 69 + .../decimal/DecimalSignalDictBuiltins.java | 193 +++ .../builtins/objects/decimal/PDecimal.java | 52 + .../objects/decimal/PDecimalContext.java | 52 + .../decimal/PDecimalContextManager.java | 52 + .../objects/decimal/PDecimalSignalDict.java | 52 + 13 files changed, 3016 insertions(+) create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextManagerBuiltins.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalExceptionTypes.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalSignalDictBuiltins.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContext.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContextManager.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalSignalDict.java diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java index 703a6de553..f2db0422f9 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java @@ -78,6 +78,7 @@ import com.oracle.graal.python.builtins.modules.CodecsTruffleModuleBuiltins; import com.oracle.graal.python.builtins.modules.CollectionsModuleBuiltins; import com.oracle.graal.python.builtins.modules.ContextvarsModuleBuiltins; +import com.oracle.graal.python.builtins.modules.DecimalModuleBuiltins; import com.oracle.graal.python.builtins.modules.ErrnoModuleBuiltins; import com.oracle.graal.python.builtins.modules.FaulthandlerModuleBuiltins; import com.oracle.graal.python.builtins.modules.FcntlModuleBuiltins; @@ -238,6 +239,10 @@ import com.oracle.graal.python.builtins.objects.contextvars.ContextIteratorBuiltins; import com.oracle.graal.python.builtins.objects.contextvars.ContextVarBuiltins; import com.oracle.graal.python.builtins.objects.contextvars.TokenBuiltins; +import com.oracle.graal.python.builtins.objects.decimal.DecimalBuiltins; +import com.oracle.graal.python.builtins.objects.decimal.DecimalContextBuiltins; +import com.oracle.graal.python.builtins.objects.decimal.DecimalContextManagerBuiltins; +import com.oracle.graal.python.builtins.objects.decimal.DecimalSignalDictBuiltins; import com.oracle.graal.python.builtins.objects.deque.DequeBuiltins; import com.oracle.graal.python.builtins.objects.deque.DequeIterBuiltins; import com.oracle.graal.python.builtins.objects.deque.DequeIterCommonBuiltins; @@ -762,6 +767,13 @@ private static PythonBuiltins[] initializeBuiltins(TruffleLanguage.Env env) { new ResourceModuleBuiltins(), new ContextvarsModuleBuiltins(), + // _decimal + new DecimalModuleBuiltins(), + new DecimalBuiltins(), + new DecimalContextBuiltins(), + new DecimalContextManagerBuiltins(), + new DecimalSignalDictBuiltins(), + // pickle new PickleModuleBuiltins(), new PicklerBuiltins(), diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java index a96555806d..32414c2ad1 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java @@ -156,6 +156,10 @@ import com.oracle.graal.python.builtins.objects.contextvars.ContextIteratorBuiltins; import com.oracle.graal.python.builtins.objects.contextvars.ContextVarBuiltins; import com.oracle.graal.python.builtins.objects.contextvars.TokenBuiltins; +import com.oracle.graal.python.builtins.objects.decimal.DecimalBuiltins; +import com.oracle.graal.python.builtins.objects.decimal.DecimalContextBuiltins; +import com.oracle.graal.python.builtins.objects.decimal.DecimalContextManagerBuiltins; +import com.oracle.graal.python.builtins.objects.decimal.DecimalSignalDictBuiltins; import com.oracle.graal.python.builtins.objects.deque.DequeBuiltins; import com.oracle.graal.python.builtins.objects.deque.DequeIterBuiltins; import com.oracle.graal.python.builtins.objects.deque.DequeIterCommonBuiltins; @@ -1348,6 +1352,12 @@ def takewhile(predicate, iterable): MultibyteStreamReader("MultibyteStreamReader", PythonObject, newBuilder().publishInModule("_multibytecodec").basetype().addDict().slots(MultibyteStreamReaderBuiltins.SLOTS)), MultibyteStreamWriter("MultibyteStreamWriter", PythonObject, newBuilder().publishInModule("_multibytecodec").basetype().addDict().slots(MultibyteStreamWriterBuiltins.SLOTS)), + // decimal + PDecimal("Decimal", PythonObject, newBuilder().publishInModule("_decimal").moduleName("decimal").basetype().heaptype().haveGC().slots(DecimalBuiltins.SLOTS)), + PDecimalContext("Context", PythonObject, newBuilder().publishInModule("_decimal").moduleName("decimal").basetype().heaptype().haveGC().slots(DecimalContextBuiltins.SLOTS)), + PDecimalContextManager("ContextManager", PythonObject, newBuilder().moduleName("decimal").heaptype().haveGC().disallowInstantiation().slots(DecimalContextManagerBuiltins.SLOTS)), + PDecimalSignalDictMixin("SignalDictMixin", PythonObject, newBuilder().moduleName("decimal").basetype().heaptype().haveGC().slots(DecimalSignalDictBuiltins.SLOTS)), + // contextvars ContextVarsToken("Token", PythonObject, newBuilder().publishInModule(J__CONTEXTVARS).slots(TokenBuiltins.SLOTS)), ContextVarsContext("Context", PythonObject, newBuilder().publishInModule(J__CONTEXTVARS).slots(ContextBuiltins.SLOTS)), diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java new file mode 100644 index 0000000000..01814ba4bb --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java @@ -0,0 +1,166 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.modules; + +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; +import static com.oracle.graal.python.nodes.BuiltinNames.T_MODULE; +import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___MODULE__; +import static com.oracle.graal.python.util.PythonUtils.tsLiteral; + +import java.util.List; + +import com.oracle.graal.python.PythonLanguage; +import com.oracle.graal.python.annotations.Builtin; +import com.oracle.graal.python.builtins.CoreFunctions; +import com.oracle.graal.python.builtins.Python3Core; +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.builtins.objects.decimal.DecimalExceptionTypes; +import com.oracle.graal.python.builtins.objects.decimal.DecimalModuleState; +import com.oracle.graal.python.builtins.objects.function.PKeyword; +import com.oracle.graal.python.builtins.objects.module.PythonModule; +import com.oracle.graal.python.builtins.objects.type.PythonAbstractClass; +import com.oracle.graal.python.builtins.objects.type.PythonClass; +import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.call.CallNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; +import com.oracle.graal.python.nodes.statement.AbstractImportNode; +import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.GenerateNodeFactory; +import com.oracle.truffle.api.dsl.NodeFactory; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.strings.TruffleString; + +@CoreFunctions(defineModule = "_decimal") +public final class DecimalModuleBuiltins extends PythonBuiltins { + private static final TruffleString T__DECIMAL = tsLiteral("_decimal"); + private static final TruffleString T_DECIMAL = tsLiteral("decimal"); + private static final TruffleString T_DECIMAL_TUPLE = tsLiteral("DecimalTuple"); + private static final TruffleString T_DECIMAL_TUPLE_FIELDS = tsLiteral("sign digits exponent"); + private static final TruffleString T_COLLECTIONS = tsLiteral("collections"); + private static final TruffleString T_NAMEDTUPLE = tsLiteral("namedtuple"); + private static final TruffleString T_SIGNAL_DICT = tsLiteral("SignalDict"); + private static final TruffleString T_COLLECTIONS_ABC = tsLiteral("collections.abc"); + private static final TruffleString T_MUTABLE_MAPPING = tsLiteral("MutableMapping"); + + @Override + protected List> getNodeFactories() { + return DecimalModuleBuiltinsFactory.getFactories(); + } + + @Override + public void postInitialize(Python3Core core) { + PythonModule module = core.lookupBuiltinModule(T__DECIMAL); + PythonLanguage language = core.getLanguage(); + PythonClass[] exceptionTypes = new PythonClass[DecimalExceptionTypes.VALUES.length]; + for (DecimalExceptionTypes exception : DecimalExceptionTypes.VALUES) { + PythonAbstractClass[] bases = new PythonAbstractClass[exception.bases.length]; + for (int i = 0; i < bases.length; i++) { + Object base = exception.bases[i]; + if (base instanceof PythonBuiltinClassType builtinType) { + bases[i] = core.lookupType(builtinType); + } else { + bases[i] = exceptionTypes[((DecimalExceptionTypes) base).ordinal()]; + } + } + PythonClass type = PFactory.createPythonClassAndFixupSlots(null, language, exception.typeName, bases[0], bases); + type.setAttribute(T___MODULE__, T_DECIMAL); + module.setAttribute(exception.typeName, type); + exceptionTypes[exception.ordinal()] = type; + } + + PythonModule collections = AbstractImportNode.importModule(T_COLLECTIONS); + Object namedtuple = collections.getAttribute(T_NAMEDTUPLE); + PythonClass decimalTuple = (PythonClass) CallNode.executeUncached(namedtuple, new Object[]{T_DECIMAL_TUPLE, T_DECIMAL_TUPLE_FIELDS}, + new PKeyword[]{new PKeyword(T_MODULE, T_DECIMAL)}); + module.setAttribute(T_DECIMAL_TUPLE, decimalTuple); + + PythonModule abc = AbstractImportNode.importModule(T_COLLECTIONS_ABC); + PythonAbstractClass mixin = core.lookupType(PythonBuiltinClassType.PDecimalSignalDictMixin); + PythonAbstractClass mapping = (PythonAbstractClass) abc.getAttribute(T_MUTABLE_MAPPING); + PythonClass signalDict = PFactory.createPythonClassAndFixupSlots(null, language, T_SIGNAL_DICT, mixin, new PythonAbstractClass[]{mixin, mapping}); + signalDict.setAttribute(T___MODULE__, T_DECIMAL); + module.setModuleState(new DecimalModuleState(exceptionTypes, decimalTuple, signalDict)); + } + + @Builtin(name = "getcontext", minNumOfPositionalArgs = 0) + @GenerateNodeFactory + public abstract static class GetcontextNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement _decimal.getcontext. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "setcontext", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class SetcontextNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object context, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement _decimal.setcontext. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "localcontext", minNumOfPositionalArgs = 0, parameterNames = {"ctx", "prec", "rounding", "Emin", "Emax", "capitals", "clamp", "flags", "traps"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class LocalcontextNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object ctx, Object prec, Object rounding, Object Emin, Object Emax, Object capitals, Object clamp, Object flags, Object traps, + PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement _decimal.localcontext. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java new file mode 100644 index 0000000000..288ed82de1 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java @@ -0,0 +1,1105 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; + +import java.util.List; + +import com.oracle.graal.python.annotations.Builtin; +import com.oracle.graal.python.annotations.Slot.SlotKind; +import com.oracle.graal.python.annotations.Slot.SlotSignature; +import com.oracle.graal.python.annotations.Slot; +import com.oracle.graal.python.builtins.CoreFunctions; +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.builtins.objects.function.PKeyword; +import com.oracle.graal.python.builtins.objects.type.TpSlots; +import com.oracle.graal.python.builtins.objects.type.slots.TpSlotBinaryOp.BinaryOpBuiltinNode; +import com.oracle.graal.python.builtins.objects.type.slots.TpSlotGetAttr.GetAttrBuiltinNode; +import com.oracle.graal.python.builtins.objects.type.slots.TpSlotHashFun.HashBuiltinNode; +import com.oracle.graal.python.builtins.objects.type.slots.TpSlotInquiry.NbBoolBuiltinNode; +import com.oracle.graal.python.builtins.objects.type.slots.TpSlotRichCompare.RichCmpBuiltinNode; +import com.oracle.graal.python.lib.RichCmpOp; +import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.GenerateNodeFactory; +import com.oracle.truffle.api.dsl.NodeFactory; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.Node; + +@CoreFunctions(extendClasses = PythonBuiltinClassType.PDecimal) +public final class DecimalBuiltins extends PythonBuiltins { + public static final TpSlots SLOTS = DecimalBuiltinsSlotsGen.SLOTS; + + @Override + protected List> getNodeFactories() { + return DecimalBuiltinsFactory.getFactories(); + } + + @Builtin(name = "exp", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class ExpNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.exp. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "ln", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class LnNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.ln. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "log10", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class Log10Node extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.log10. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "next_minus", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class NextMinusNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.next_minus. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "next_plus", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class NextPlusNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.next_plus. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "normalize", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class NormalizeNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.normalize. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "to_integral", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "rounding", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class ToIntegralNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object rounding, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.to_integral. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "to_integral_exact", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "rounding", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class ToIntegralExactNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object rounding, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.to_integral_exact. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "to_integral_value", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "rounding", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class ToIntegralValueNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object rounding, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.to_integral_value. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "sqrt", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class SqrtNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.sqrt. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "compare", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class CompareNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.compare. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "compare_signal", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class CompareSignalNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.compare_signal. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "max", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class MaxNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.max. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "max_mag", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class MaxMagNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.max_mag. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "min", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class MinNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.min. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "min_mag", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class MinMagNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.min_mag. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "next_toward", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class NextTowardNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.next_toward. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "quantize", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "exp", "rounding", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class QuantizeNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object exp, Object rounding, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.quantize. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "remainder_near", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class RemainderNearNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.remainder_near. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "fma", minNumOfPositionalArgs = 3, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "third", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class FmaNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object third, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.fma. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_canonical", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class IsCanonicalNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.is_canonical. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_finite", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class IsFiniteNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.is_finite. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_infinite", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class IsInfiniteNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.is_infinite. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_nan", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class IsNanNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.is_nan. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_qnan", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class IsQnanNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.is_qnan. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_snan", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class IsSnanNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.is_snan. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_signed", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class IsSignedNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.is_signed. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_zero", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class IsZeroNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.is_zero. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_normal", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class IsNormalNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.is_normal. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_subnormal", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class IsSubnormalNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.is_subnormal. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "adjusted", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class AdjustedNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.adjusted. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "canonical", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class CanonicalNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.canonical. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "conjugate", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class ConjugateNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.conjugate. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "radix", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class RadixNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.radix. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "copy_abs", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class CopyAbsNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.copy_abs. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "copy_negate", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class CopyNegateNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.copy_negate. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "logb", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class LogbNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.logb. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "logical_invert", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class LogicalInvertNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.logical_invert. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "number_class", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class NumberClassNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.number_class. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "to_eng_string", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class ToEngStringNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.to_eng_string. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "compare_total", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class CompareTotalNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.compare_total. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "compare_total_mag", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class CompareTotalMagNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.compare_total_mag. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "copy_sign", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class CopySignNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.copy_sign. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "same_quantum", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class SameQuantumNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.same_quantum. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "logical_and", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class LogicalAndNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.logical_and. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "logical_or", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class LogicalOrNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.logical_or. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "logical_xor", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class LogicalXorNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.logical_xor. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "rotate", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class RotateNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.rotate. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "scaleb", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class ScalebNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.scaleb. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "shift", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class ShiftNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.shift. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "from_float", minNumOfPositionalArgs = 2, isClassmethod = true) + @GenerateNodeFactory + public abstract static class FromFloatNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.from_float. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "as_tuple", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class AsTupleNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.as_tuple. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "as_integer_ratio", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class AsIntegerRatioNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.as_integer_ratio. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "__copy__", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class CopyNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.__copy__. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "__deepcopy__", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class DeepcopyNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.__deepcopy__. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "__format__", minNumOfPositionalArgs = 2, maxNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class FormatNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.__format__. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "__reduce__", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class ReduceNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.__reduce__. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "__round__", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class RoundNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.__round__. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "__ceil__", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class CeilNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.__ceil__. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "__floor__", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class FloorNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.__floor__. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "__trunc__", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class TruncNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.__trunc__. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "__complex__", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class ComplexNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.__complex__. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "__sizeof__", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class SizeofNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.__sizeof__. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "real", minNumOfPositionalArgs = 1, isGetter = true) + @GenerateNodeFactory + public abstract static class RealNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.real. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "imag", minNumOfPositionalArgs = 1, isGetter = true) + @GenerateNodeFactory + public abstract static class ImagNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.imag. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_new, isComplex = true) + @SlotSignature(name = "Decimal", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"cls", "value", "context"}) + @GenerateNodeFactory + public abstract static class TpNewNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.tp_new. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_repr, isComplex = true) + @GenerateNodeFactory + public abstract static class TpReprNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.tp_repr. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_str, isComplex = true) + @GenerateNodeFactory + public abstract static class TpStrNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.tp_str. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.nb_negative, isComplex = true) + @GenerateNodeFactory + public abstract static class NbNegativeNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.nb_negative. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.nb_positive, isComplex = true) + @GenerateNodeFactory + public abstract static class NbPositiveNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.nb_positive. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.nb_absolute, isComplex = true) + @GenerateNodeFactory + public abstract static class NbAbsoluteNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.nb_absolute. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.nb_int, isComplex = true) + @GenerateNodeFactory + public abstract static class NbIntNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.nb_int. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.nb_float, isComplex = true) + @GenerateNodeFactory + public abstract static class NbFloatNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.nb_float. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_getattro, isComplex = true) + @GenerateNodeFactory + public abstract static class TpGetattroNode extends GetAttrBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.tp_getattro. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_hash, isComplex = true) + @GenerateNodeFactory + public abstract static class TpHashNode extends HashBuiltinNode { + @Specialization + static long doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.tp_hash. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_richcompare, isComplex = true) + @GenerateNodeFactory + public abstract static class TpRichcompareNode extends RichCmpBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, RichCmpOp op, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.tp_richcompare. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.nb_add, isComplex = true) + @GenerateNodeFactory + public abstract static class NbAddNode extends BinaryOpBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.nb_add. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.nb_subtract, isComplex = true) + @GenerateNodeFactory + public abstract static class NbSubtractNode extends BinaryOpBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.nb_subtract. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.nb_multiply, isComplex = true) + @GenerateNodeFactory + public abstract static class NbMultiplyNode extends BinaryOpBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.nb_multiply. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.nb_remainder, isComplex = true) + @GenerateNodeFactory + public abstract static class NbRemainderNode extends BinaryOpBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.nb_remainder. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.nb_divmod, isComplex = true) + @GenerateNodeFactory + public abstract static class NbDivmodNode extends BinaryOpBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.nb_divmod. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.nb_floor_divide, isComplex = true) + @GenerateNodeFactory + public abstract static class NbFloorDivideNode extends BinaryOpBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.nb_floor_divide. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.nb_true_divide, isComplex = true) + @GenerateNodeFactory + public abstract static class NbTrueDivideNode extends BinaryOpBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.nb_true_divide. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.nb_power, isComplex = true) + @GenerateNodeFactory + public abstract static class NbPowerNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.nb_power. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.nb_bool, isComplex = true) + @GenerateNodeFactory + public abstract static class NbBoolNode extends NbBoolBuiltinNode { + @Specialization + static boolean doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Decimal.nb_bool. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java new file mode 100644 index 0000000000..baa939529a --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java @@ -0,0 +1,1068 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; + +import java.util.List; + +import com.oracle.graal.python.annotations.Builtin; +import com.oracle.graal.python.annotations.Slot.SlotKind; +import com.oracle.graal.python.annotations.Slot.SlotSignature; +import com.oracle.graal.python.annotations.Slot; +import com.oracle.graal.python.builtins.CoreFunctions; +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.builtins.objects.function.PKeyword; +import com.oracle.graal.python.builtins.objects.type.TpSlots; +import com.oracle.graal.python.builtins.objects.type.slots.TpSlotGetAttr.GetAttrBuiltinNode; +import com.oracle.graal.python.builtins.objects.type.slots.TpSlotSetAttr.SetAttrBuiltinNode; +import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonQuaternaryBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonVarargsBuiltinNode; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.GenerateNodeFactory; +import com.oracle.truffle.api.dsl.NodeFactory; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.Node; + +@CoreFunctions(extendClasses = PythonBuiltinClassType.PDecimalContext) +public final class DecimalContextBuiltins extends PythonBuiltins { + public static final TpSlots SLOTS = DecimalContextBuiltinsSlotsGen.SLOTS; + + @Override + protected List> getNodeFactories() { + return DecimalContextBuiltinsFactory.getFactories(); + } + + @Builtin(name = "abs", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class AbsNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.abs. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "exp", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class ExpNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.exp. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "ln", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class LnNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.ln. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "log10", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class Log10Node extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.log10. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "minus", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class MinusNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.minus. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "next_minus", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class NextMinusNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.next_minus. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "next_plus", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class NextPlusNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.next_plus. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "normalize", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class NormalizeNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.normalize. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "plus", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class PlusNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.plus. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "to_integral", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class ToIntegralNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.to_integral. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "to_integral_exact", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class ToIntegralExactNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.to_integral_exact. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "to_integral_value", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class ToIntegralValueNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.to_integral_value. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "sqrt", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class SqrtNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.sqrt. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "add", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class AddNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.add. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "compare", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class CompareNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.compare. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "compare_signal", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class CompareSignalNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.compare_signal. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "divide", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class DivideNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.divide. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "divide_int", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class DivideIntNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.divide_int. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "divmod", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class DivmodNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.divmod. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "max", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class MaxNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.max. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "max_mag", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class MaxMagNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.max_mag. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "min", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class MinNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.min. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "min_mag", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class MinMagNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.min_mag. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "multiply", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class MultiplyNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.multiply. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "next_toward", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class NextTowardNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.next_toward. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "quantize", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class QuantizeNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.quantize. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "remainder", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class RemainderNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.remainder. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "remainder_near", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class RemainderNearNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.remainder_near. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "subtract", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class SubtractNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.subtract. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "power", minNumOfPositionalArgs = 3, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "a", "b", "modulo"}, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class PowerNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object a, Object b, Object modulo, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.power. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "fma", minNumOfPositionalArgs = 4) + @GenerateNodeFactory + public abstract static class FmaNode extends PythonQuaternaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, Object arg3, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.fma. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "Etiny", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class EtinyNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.Etiny. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "Etop", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class EtopNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.Etop. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "radix", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class RadixNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.radix. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_canonical", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class IsCanonicalNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.is_canonical. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_finite", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class IsFiniteNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.is_finite. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_infinite", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class IsInfiniteNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.is_infinite. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_nan", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class IsNanNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.is_nan. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_normal", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class IsNormalNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.is_normal. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_qnan", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class IsQnanNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.is_qnan. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_signed", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class IsSignedNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.is_signed. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_snan", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class IsSnanNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.is_snan. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_subnormal", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class IsSubnormalNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.is_subnormal. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "is_zero", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class IsZeroNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.is_zero. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "_apply", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class ApplyNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context._apply. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "canonical", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class CanonicalNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.canonical. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "copy_abs", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class CopyAbsNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.copy_abs. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "copy_decimal", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class CopyDecimalNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.copy_decimal. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "copy_negate", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class CopyNegateNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.copy_negate. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "logb", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class LogbNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.logb. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "logical_invert", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class LogicalInvertNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.logical_invert. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "number_class", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class NumberClassNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.number_class. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "to_sci_string", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class ToSciStringNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.to_sci_string. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "to_eng_string", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class ToEngStringNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.to_eng_string. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "compare_total", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class CompareTotalNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.compare_total. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "compare_total_mag", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class CompareTotalMagNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.compare_total_mag. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "copy_sign", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class CopySignNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.copy_sign. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "logical_and", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class LogicalAndNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.logical_and. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "logical_or", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class LogicalOrNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.logical_or. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "logical_xor", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class LogicalXorNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.logical_xor. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "rotate", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class RotateNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.rotate. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "same_quantum", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class SameQuantumNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.same_quantum. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "scaleb", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class ScalebNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.scaleb. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "shift", minNumOfPositionalArgs = 3) + @GenerateNodeFactory + public abstract static class ShiftNode extends PythonTernaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.shift. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "clear_flags", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class ClearFlagsNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.clear_flags. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "clear_traps", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class ClearTrapsNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.clear_traps. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "__copy__", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class CopySpecialNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.__copy__. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "__reduce__", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class ReduceNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.__reduce__. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "copy", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class CopyNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.copy. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "create_decimal", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class CreateDecimalNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.create_decimal. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "create_decimal_from_float", minNumOfPositionalArgs = 2) + @GenerateNodeFactory + public abstract static class CreateDecimalFromFloatNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.create_decimal_from_float. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "prec", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true) + @GenerateNodeFactory + public abstract static class PrecNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.prec (get/set). + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "Emax", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true) + @GenerateNodeFactory + public abstract static class EmaxNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.Emax (get/set). + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "Emin", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true) + @GenerateNodeFactory + public abstract static class EminNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.Emin (get/set). + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "rounding", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true) + @GenerateNodeFactory + public abstract static class RoundingNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.rounding (get/set). + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "capitals", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true) + @GenerateNodeFactory + public abstract static class CapitalsNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.capitals (get/set). + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "clamp", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true) + @GenerateNodeFactory + public abstract static class ClampNode extends PythonBinaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.clamp (get/set). + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_new, isComplex = true) + @SlotSignature(name = "Context", minNumOfPositionalArgs = 1, takesVarArgs = true, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class TpNewNode extends PythonVarargsBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object cls, Object[] args, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.tp_new. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_init, isComplex = true) + @SlotSignature(name = "Context", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "prec", "rounding", "Emin", "Emax", "capitals", "clamp", "flags", "traps"}) + @GenerateNodeFactory + public abstract static class TpInitNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.tp_init. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_repr, isComplex = true) + @GenerateNodeFactory + public abstract static class TpReprNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.tp_repr. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_getattro, isComplex = true) + @GenerateNodeFactory + public abstract static class TpGetattroNode extends GetAttrBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.tp_getattro. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_setattro, isComplex = true) + @GenerateNodeFactory + public abstract static class TpSetattroNode extends SetAttrBuiltinNode { + @Specialization + static void doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement Context.tp_setattro. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextManagerBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextManagerBuiltins.java new file mode 100644 index 0000000000..9fb7e834d4 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextManagerBuiltins.java @@ -0,0 +1,111 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; + +import java.util.List; + +import com.oracle.graal.python.annotations.Builtin; +import com.oracle.graal.python.annotations.Slot.SlotKind; +import com.oracle.graal.python.annotations.Slot; +import com.oracle.graal.python.builtins.CoreFunctions; +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.builtins.objects.type.TpSlots; +import com.oracle.graal.python.builtins.objects.type.slots.TpSlotGetAttr.GetAttrBuiltinNode; +import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.GenerateNodeFactory; +import com.oracle.truffle.api.dsl.NodeFactory; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.Node; + +@CoreFunctions(extendClasses = PythonBuiltinClassType.PDecimalContextManager) +public final class DecimalContextManagerBuiltins extends PythonBuiltins { + public static final TpSlots SLOTS = DecimalContextManagerBuiltinsSlotsGen.SLOTS; + + @Override + protected List> getNodeFactories() { + return DecimalContextManagerBuiltinsFactory.getFactories(); + } + + @Builtin(name = "__enter__", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class EnterNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement ContextManager.__enter__. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Builtin(name = "__exit__", minNumOfPositionalArgs = 1, takesVarArgs = true) + @GenerateNodeFactory + public abstract static class ExitNode extends PythonBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object[] args, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement ContextManager.__exit__. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_getattro, isComplex = true) + @GenerateNodeFactory + public abstract static class TpGetattroNode extends GetAttrBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement ContextManager.tp_getattro. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalExceptionTypes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalExceptionTypes.java new file mode 100644 index 0000000000..e6423fce64 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalExceptionTypes.java @@ -0,0 +1,74 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import static com.oracle.graal.python.util.PythonUtils.toInternedTruffleStringUncached; + +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.truffle.api.strings.TruffleString; + +public enum DecimalExceptionTypes { + DecimalException(PythonBuiltinClassType.ArithmeticError), + InvalidOperation(DecimalException), + FloatOperation(DecimalException, PythonBuiltinClassType.TypeError), + DivisionByZero(DecimalException, PythonBuiltinClassType.ZeroDivisionError), + Inexact(DecimalException), + Rounded(DecimalException), + Subnormal(DecimalException), + Overflow(Inexact, Rounded), + Underflow(Inexact, Rounded, Subnormal), + Clamped(DecimalException), + ConversionSyntax(InvalidOperation), + DivisionImpossible(InvalidOperation), + DivisionUndefined(InvalidOperation, PythonBuiltinClassType.ZeroDivisionError), + InvalidContext(InvalidOperation); + + public static final DecimalExceptionTypes[] VALUES = values(); + + public final TruffleString typeName; + // Bases are either builtin class types or earlier decimal exception enum constants. + public final Object[] bases; + + DecimalExceptionTypes(Object... bases) { + this.typeName = toInternedTruffleStringUncached(name()); + this.bases = bases; + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java new file mode 100644 index 0000000000..50a2976478 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java @@ -0,0 +1,69 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import com.oracle.graal.python.builtins.objects.type.PythonClass; + +public final class DecimalModuleState { + private final PythonClass[] exceptionTypes; + private final PythonClass decimalTuple; + private final PythonClass signalDict; + + public DecimalModuleState(PythonClass[] exceptionTypes, PythonClass decimalTuple, PythonClass signalDict) { + this.exceptionTypes = exceptionTypes; + this.decimalTuple = decimalTuple; + this.signalDict = signalDict; + } + + public PythonClass getExceptionType(DecimalExceptionTypes type) { + return exceptionTypes[type.ordinal()]; + } + + public PythonClass getDecimalTuple() { + return decimalTuple; + } + + public PythonClass getSignalDict() { + return signalDict; + } + + // TODO implement per-interpreter current context, context templates, and signal state. +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalSignalDictBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalSignalDictBuiltins.java new file mode 100644 index 0000000000..73d5d56092 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalSignalDictBuiltins.java @@ -0,0 +1,193 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; + +import java.util.List; + +import com.oracle.graal.python.annotations.Builtin; +import com.oracle.graal.python.annotations.HashNotImplemented; +import com.oracle.graal.python.annotations.Slot.SlotKind; +import com.oracle.graal.python.annotations.Slot.SlotSignature; +import com.oracle.graal.python.annotations.Slot; +import com.oracle.graal.python.builtins.CoreFunctions; +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.builtins.objects.function.PKeyword; +import com.oracle.graal.python.builtins.objects.type.TpSlots; +import com.oracle.graal.python.builtins.objects.type.slots.TpSlotBinaryFunc.MpSubscriptBuiltinNode; +import com.oracle.graal.python.builtins.objects.type.slots.TpSlotGetAttr.GetAttrBuiltinNode; +import com.oracle.graal.python.builtins.objects.type.slots.TpSlotLen.LenBuiltinNode; +import com.oracle.graal.python.builtins.objects.type.slots.TpSlotMpAssSubscript.MpAssSubscriptBuiltinNode; +import com.oracle.graal.python.builtins.objects.type.slots.TpSlotRichCompare.RichCmpBuiltinNode; +import com.oracle.graal.python.lib.RichCmpOp; +import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; +import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonVarargsBuiltinNode; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.GenerateNodeFactory; +import com.oracle.truffle.api.dsl.NodeFactory; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.Node; + +@CoreFunctions(extendClasses = PythonBuiltinClassType.PDecimalSignalDictMixin) +@HashNotImplemented +public final class DecimalSignalDictBuiltins extends PythonBuiltins { + public static final TpSlots SLOTS = DecimalSignalDictBuiltinsSlotsGen.SLOTS; + + @Override + protected List> getNodeFactories() { + return DecimalSignalDictBuiltinsFactory.getFactories(); + } + + @Builtin(name = "copy", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class CopyNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement SignalDictMixin.copy. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_init, isComplex = true) + @SlotSignature(name = "SignalDictMixin", minNumOfPositionalArgs = 1, takesVarArgs = true, takesVarKeywordArgs = true) + @GenerateNodeFactory + public abstract static class TpInitNode extends PythonVarargsBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object[] args, PKeyword[] kwargs, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement SignalDictMixin.tp_init. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_repr, isComplex = true) + @GenerateNodeFactory + public abstract static class TpReprNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement SignalDictMixin.tp_repr. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_getattro, isComplex = true) + @GenerateNodeFactory + public abstract static class TpGetattroNode extends GetAttrBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement SignalDictMixin.tp_getattro. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_richcompare, isComplex = true) + @GenerateNodeFactory + public abstract static class TpRichcompareNode extends RichCmpBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object other, RichCmpOp op, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement SignalDictMixin.tp_richcompare. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.tp_iter, isComplex = true) + @GenerateNodeFactory + public abstract static class TpIterNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement SignalDictMixin.tp_iter. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.mp_length, isComplex = true) + @GenerateNodeFactory + public abstract static class MpLengthNode extends LenBuiltinNode { + @Specialization + static int doMock(VirtualFrame frame, Object self, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement SignalDictMixin.mp_length. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.mp_subscript, isComplex = true) + @GenerateNodeFactory + public abstract static class MpSubscriptNode extends MpSubscriptBuiltinNode { + @Specialization + static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement SignalDictMixin.mp_subscript. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } + + @Slot(value = SlotKind.mp_ass_subscript, isComplex = true) + @GenerateNodeFactory + public abstract static class MpAssSubscriptNode extends MpAssSubscriptBuiltinNode { + @Specialization + static void doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + @Bind Node inliningTarget, + @Cached PRaiseNode raiseNode) { + // TODO implement SignalDictMixin.mp_ass_subscript. + throw raiseNode.raise(inliningTarget, NotImplementedError); + } + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java new file mode 100644 index 0000000000..db139fa387 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java @@ -0,0 +1,52 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import com.oracle.graal.python.builtins.objects.object.PythonBuiltinObject; +import com.oracle.truffle.api.object.Shape; + +public final class PDecimal extends PythonBuiltinObject { + // TODO implement the decimal sign, coefficient, exponent, and special values. + + public PDecimal(Object cls, Shape instanceShape) { + super(cls, instanceShape); + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContext.java new file mode 100644 index 0000000000..2a3ab93609 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContext.java @@ -0,0 +1,52 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import com.oracle.graal.python.builtins.objects.object.PythonBuiltinObject; +import com.oracle.truffle.api.object.Shape; + +public final class PDecimalContext extends PythonBuiltinObject { + // TODO implement precision, rounding, exponent limits, capitals, clamp, flags, and traps. + + public PDecimalContext(Object cls, Shape instanceShape) { + super(cls, instanceShape); + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContextManager.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContextManager.java new file mode 100644 index 0000000000..20ac8f67e6 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContextManager.java @@ -0,0 +1,52 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import com.oracle.graal.python.builtins.objects.object.PythonBuiltinObject; +import com.oracle.truffle.api.object.Shape; + +public final class PDecimalContextManager extends PythonBuiltinObject { + // TODO implement the outer and local contexts and context restoration state. + + public PDecimalContextManager(Object cls, Shape instanceShape) { + super(cls, instanceShape); + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalSignalDict.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalSignalDict.java new file mode 100644 index 0000000000..a19626e375 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalSignalDict.java @@ -0,0 +1,52 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import com.oracle.graal.python.builtins.objects.object.PythonBuiltinObject; +import com.oracle.truffle.api.object.Shape; + +public final class PDecimalSignalDict extends PythonBuiltinObject { + // TODO implement a view of context signals with flags/traps ownership. + + public PDecimalSignalDict(Object cls, Shape instanceShape) { + super(cls, instanceShape); + } +} From def4c0fc06522fc98c25674c2bb018e5d0907fe2 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Tue, 6 Oct 2026 11:32:27 +0200 Subject: [PATCH 02/31] Support native lists in GetListStorageNode --- .../graal/python/nodes/builtins/ListNodes.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/ListNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/ListNodes.java index ec4558b1e0..dde7f91b19 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/ListNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/ListNodes.java @@ -56,6 +56,7 @@ import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.ListGeneralizationNode; import com.oracle.graal.python.builtins.objects.ints.PInt; import com.oracle.graal.python.builtins.objects.list.PList; +import com.oracle.graal.python.lib.PyListCheckNode; import com.oracle.graal.python.lib.PyObjectGetIter; import com.oracle.graal.python.nodes.PNodeWithContext; import com.oracle.graal.python.nodes.PRaiseNode; @@ -108,6 +109,13 @@ static SequenceStorage doPList(PList list) { return list.getSequenceStorage(); } + @Specialization(guards = "listCheck.execute(inliningTarget, list)", limit = "1") + static SequenceStorage doNative(Node inliningTarget, PythonAbstractNativeObject list, + @Cached PyListCheckNode listCheck, + @Cached GetNativeListStorage getNativeStorage) { + return getNativeStorage.execute(inliningTarget, list); + } + @InliningCutoff @Specialization(guards = {"isForeignObjectNode.execute(inliningTarget, seq)", "interop.hasArrayElements(seq)"}, limit = "1") static SequenceStorage doForeign(Node inliningTarget, Object seq, @@ -351,12 +359,13 @@ public static void doPList(PList list, Object value, } } - @GenerateInline(false) + @GenerateInline(inlineByDefault = true) + @GenerateUncached public abstract static class GetNativeListStorage extends Node { - public abstract NativeSequenceStorage execute(PythonAbstractNativeObject list); + public abstract NativeSequenceStorage execute(Node inliningTarget, PythonAbstractNativeObject list); @Specialization - NativeSequenceStorage getNative(PythonAbstractNativeObject list) { + static NativeSequenceStorage getNative(PythonAbstractNativeObject list) { assert IsSubtypeNode.getUncached().execute(GetClassNode.executeUncached(list), PythonBuiltinClassType.PList); long listRawPtr = list.getPtr(); long array = readPtrField(listRawPtr, PyListObject__ob_item); From 339eff37995519c5504af42a2a34d54231a183c1 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Tue, 6 Oct 2026 11:39:46 +0200 Subject: [PATCH 03/31] Implement Decimal creation --- .../modules/DecimalModuleBuiltins.java | 45 ++- .../objects/decimal/DecimalBuiltins.java | 332 +++++++++++++++++- .../objects/decimal/DecimalUtils.java | 216 ++++++++++++ .../builtins/objects/decimal/PDecimal.java | 125 ++++++- .../graal/python/lib/PyListCheckNode.java | 8 +- .../graal/python/nodes/BuiltinNames.java | 10 + .../graal/python/nodes/ErrorMessages.java | 9 + .../python/nodes/builtins/ListNodes.java | 4 + .../python/nodes/builtins/TupleNodes.java | 4 + .../graal/python/runtime/object/PFactory.java | 17 + 10 files changed, 749 insertions(+), 21 deletions(-) create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java index 01814ba4bb..437263c192 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java @@ -41,7 +41,13 @@ package com.oracle.graal.python.builtins.modules; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; +import static com.oracle.graal.python.nodes.BuiltinNames.T_COLLECTIONS; +import static com.oracle.graal.python.nodes.BuiltinNames.T_COLLECTIONS_ABC; +import static com.oracle.graal.python.nodes.BuiltinNames.T_DECIMAL; import static com.oracle.graal.python.nodes.BuiltinNames.T_MODULE; +import static com.oracle.graal.python.nodes.BuiltinNames.T_MUTABLE_MAPPING; +import static com.oracle.graal.python.nodes.BuiltinNames.T_NAMEDTUPLE; +import static com.oracle.graal.python.nodes.BuiltinNames.T__DECIMAL; import static com.oracle.graal.python.nodes.SpecialAttributeNames.T___MODULE__; import static com.oracle.graal.python.util.PythonUtils.tsLiteral; @@ -55,6 +61,7 @@ import com.oracle.graal.python.builtins.PythonBuiltins; import com.oracle.graal.python.builtins.objects.decimal.DecimalExceptionTypes; import com.oracle.graal.python.builtins.objects.decimal.DecimalModuleState; +import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils; import com.oracle.graal.python.builtins.objects.function.PKeyword; import com.oracle.graal.python.builtins.objects.module.PythonModule; import com.oracle.graal.python.builtins.objects.type.PythonAbstractClass; @@ -77,15 +84,41 @@ @CoreFunctions(defineModule = "_decimal") public final class DecimalModuleBuiltins extends PythonBuiltins { - private static final TruffleString T__DECIMAL = tsLiteral("_decimal"); - private static final TruffleString T_DECIMAL = tsLiteral("decimal"); private static final TruffleString T_DECIMAL_TUPLE = tsLiteral("DecimalTuple"); private static final TruffleString T_DECIMAL_TUPLE_FIELDS = tsLiteral("sign digits exponent"); - private static final TruffleString T_COLLECTIONS = tsLiteral("collections"); - private static final TruffleString T_NAMEDTUPLE = tsLiteral("namedtuple"); private static final TruffleString T_SIGNAL_DICT = tsLiteral("SignalDict"); - private static final TruffleString T_COLLECTIONS_ABC = tsLiteral("collections.abc"); - private static final TruffleString T_MUTABLE_MAPPING = tsLiteral("MutableMapping"); + + public static final TruffleString T_ROUND_DOWN = tsLiteral("ROUND_DOWN"); + public static final TruffleString T_ROUND_UP = tsLiteral("ROUND_UP"); + public static final TruffleString T_ROUND_HALF_EVEN = tsLiteral("ROUND_HALF_EVEN"); + public static final TruffleString T_ROUND_HALF_DOWN = tsLiteral("ROUND_HALF_DOWN"); + public static final TruffleString T_ROUND_HALF_UP = tsLiteral("ROUND_HALF_UP"); + public static final TruffleString T_ROUND_CEILING = tsLiteral("ROUND_CEILING"); + public static final TruffleString T_ROUND_FLOOR = tsLiteral("ROUND_FLOOR"); + public static final TruffleString T_ROUND_05UP = tsLiteral("ROUND_05UP"); + + public DecimalModuleBuiltins() { + addBuiltinConstant("MAX_PREC", DecimalUtils.MAX_PREC); + addBuiltinConstant("MAX_EMAX", DecimalUtils.MAX_EMAX); + addBuiltinConstant("MIN_EMIN", DecimalUtils.MIN_EMIN); + addBuiltinConstant("MIN_ETINY", DecimalUtils.MIN_ETINY); + + addBuiltinConstant(T_ROUND_DOWN, T_ROUND_DOWN); + addBuiltinConstant(T_ROUND_UP, T_ROUND_UP); + addBuiltinConstant(T_ROUND_HALF_EVEN, T_ROUND_HALF_EVEN); + addBuiltinConstant(T_ROUND_HALF_DOWN, T_ROUND_HALF_DOWN); + addBuiltinConstant(T_ROUND_HALF_UP, T_ROUND_HALF_UP); + addBuiltinConstant(T_ROUND_CEILING, T_ROUND_CEILING); + addBuiltinConstant(T_ROUND_FLOOR, T_ROUND_FLOOR); + addBuiltinConstant(T_ROUND_05UP, T_ROUND_05UP); + + addBuiltinConstant("HAVE_THREADS", true); + addBuiltinConstant("HAVE_CONTEXTVAR", true); + + // Even _pydecimal exports these constants, despite not being based on libmpdec, so we should do that too + addBuiltinConstant("__version__", tsLiteral("1.70")); + addBuiltinConstant("__libmpdec_version__", tsLiteral("2.5.1")); + } @Override protected List> getNodeFactories() { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java index 288ed82de1..a7ed659b7e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java @@ -41,37 +41,85 @@ package com.oracle.graal.python.builtins.objects.decimal; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; - +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; +import static com.oracle.graal.python.nodes.BuiltinNames.T__DECIMAL; +import static com.oracle.graal.python.nodes.PGuards.isNoValue; +import static com.oracle.graal.python.nodes.PGuards.isNone; +import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; + +import java.math.BigInteger; import java.util.List; +import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.annotations.Builtin; +import com.oracle.graal.python.annotations.Slot; import com.oracle.graal.python.annotations.Slot.SlotKind; import com.oracle.graal.python.annotations.Slot.SlotSignature; -import com.oracle.graal.python.annotations.Slot; import com.oracle.graal.python.builtins.CoreFunctions; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; +import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.ConversionException; +import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.Value; +import com.oracle.graal.python.builtins.objects.decimal.PDecimal.Kind; +import com.oracle.graal.python.builtins.objects.floats.PFloat; import com.oracle.graal.python.builtins.objects.function.PKeyword; +import com.oracle.graal.python.builtins.objects.ints.PInt; +import com.oracle.graal.python.builtins.objects.list.PList; import com.oracle.graal.python.builtins.objects.type.TpSlots; +import com.oracle.graal.python.builtins.objects.type.TypeNodes; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotBinaryOp.BinaryOpBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotGetAttr.GetAttrBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotHashFun.HashBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotInquiry.NbBoolBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotRichCompare.RichCmpBuiltinNode; +import com.oracle.graal.python.lib.PyFloatAsDoubleNode; +import com.oracle.graal.python.lib.PyFloatCheckNode; +import com.oracle.graal.python.lib.PyListCheckNode; +import com.oracle.graal.python.lib.PyLongAsLongNode; +import com.oracle.graal.python.lib.PyLongCheckNode; +import com.oracle.graal.python.lib.PyTupleCheckNode; +import com.oracle.graal.python.lib.PyUnicodeCheckNode; import com.oracle.graal.python.lib.RichCmpOp; +import com.oracle.graal.python.nodes.ErrorMessages; +import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.builtins.ListNodes.GetListStorageNode; +import com.oracle.graal.python.nodes.builtins.TupleNodes.GetTupleStorage; +import com.oracle.graal.python.nodes.call.CallNode; import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; import com.oracle.graal.python.nodes.function.PythonBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; +import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinClassExactProfile; +import com.oracle.graal.python.nodes.object.GetClassNode; +import com.oracle.graal.python.nodes.util.CastToJavaBigIntegerNode; +import com.oracle.graal.python.nodes.util.CastToJavaStringNode; +import com.oracle.graal.python.runtime.ExecutionContext.BoundaryCallContext; +import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; +import com.oracle.graal.python.runtime.PythonContext; +import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Cached.Shared; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.GenerateCached; +import com.oracle.truffle.api.dsl.GenerateInline; import com.oracle.truffle.api.dsl.GenerateNodeFactory; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.NodeFactory; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.profiles.BranchProfile; +import com.oracle.truffle.api.profiles.InlinedBranchProfile; +import com.oracle.truffle.api.strings.TruffleString; @CoreFunctions(extendClasses = PythonBuiltinClassType.PDecimal) public final class DecimalBuiltins extends PythonBuiltins { @@ -867,11 +915,267 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class TpNewNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.tp_new. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static PDecimal construct(VirtualFrame frame, Object cls, Object value, Object context, + @Bind Node inliningTarget, + @Bind PythonLanguage language, + @Cached(inline = true) CreateDecimalNode createDecimal, + @Cached CallNode callNode, + @Cached PRaiseNode raiseNode, + @Cached BranchProfile errorProfile) { + if (!isNone(context) && !isNoValue(context) && !(context instanceof PDecimalContext)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.OPTIONAL_DECIMAL_CONTEXT); + } + try { + return createDecimal.execute(frame, inliningTarget, cls, value); + } catch (ConversionException e) { + errorProfile.enter(); + // Until contexts have flags/traps, use the default InvalidOperation trap. + DecimalModuleState state = PythonContext.get(inliningTarget).lookupBuiltinModule(T__DECIMAL).getModuleState(DecimalModuleState.class); + PList conditions = PFactory.createList(language, new Object[]{state.getExceptionType(e.condition)}); + Object exception = callNode.execute(frame, state.getExceptionType(DecimalExceptionTypes.InvalidOperation), conditions); + throw PRaiseNode.raiseExceptionObjectStatic(inliningTarget, exception); + } + } + + @GenerateInline + @GenerateCached(false) + @GenerateUncached + @ImportStatic(PGuards.class) + abstract static class CreateDecimalNode extends Node { + abstract PDecimal execute(VirtualFrame frame, Node inliningTarget, Object cls, Object value) throws ConversionException; + + @Specialization(guards = "isNoValue(value)") + static PDecimal doDefault(@SuppressWarnings("unused") Node inliningTarget, Object cls, @SuppressWarnings("unused") PNone value, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return PFactory.createDecimal(cls, getShape.execute(cls), 0L); + } + + @Specialization + static PDecimal doDecimal(Node inliningTarget, Object cls, PDecimal value, + @Shared @Cached GetClassNode getClass, + @Shared @Cached IsBuiltinClassExactProfile clsProfile, + @Shared @Cached IsBuiltinClassExactProfile valueProfile, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + if (clsProfile.profileClass(inliningTarget, cls, PythonBuiltinClassType.PDecimal) && + valueProfile.profileClass(inliningTarget, getClass.execute(inliningTarget, value), PythonBuiltinClassType.PDecimal)) { + return value; + } + return PFactory.createDecimal(cls, getShape.execute(cls), value); + } + + @Specialization(guards = "unicodeCheck.execute(inliningTarget, value)") + static PDecimal doString(Node inliningTarget, Object cls, Object value, + @SuppressWarnings("unused") @Shared @Cached PyUnicodeCheckNode unicodeCheck, + @Cached CastToJavaStringNode asString, + @Shared @Cached TypeNodes.GetInstanceShape getShape) throws ConversionException { + return create(cls, DecimalUtils.parse(asString.execute(value)), getShape); + } + + @Specialization + static PDecimal doInt(@SuppressWarnings("unused") Node inliningTarget, Object cls, int value, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return PFactory.createDecimal(cls, getShape.execute(cls), value); + } + + @Specialization + static PDecimal doLong(@SuppressWarnings("unused") Node inliningTarget, Object cls, long value, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return PFactory.createDecimal(cls, getShape.execute(cls), value); + } + + @Specialization + static PDecimal doPInt(@SuppressWarnings("unused") Node inliningTarget, Object cls, PInt value, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return create(cls, DecimalUtils.fromInteger(value.getValue()), getShape); + } + + @Specialization(guards = "longCheck.execute(inliningTarget, value)") + static PDecimal doIntGeneric(Node inliningTarget, Object cls, Object value, + @SuppressWarnings("unused") @Shared @Cached PyLongCheckNode longCheck, + @Shared @Cached CastToJavaBigIntegerNode asBigInteger, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return create(cls, DecimalUtils.fromInteger(asBigInteger.execute(inliningTarget, value)), getShape); + } + + @Specialization + static PDecimal doDouble(Node inliningTarget, Object cls, double value, + @Shared @Cached InlinedBranchProfile nonCompactProfile, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + // TODO signal FloatOperation when decimal contexts and signals are implemented. + return fromDouble(inliningTarget, cls, value, getShape, nonCompactProfile); + } + + @Specialization + static PDecimal doPFloat(Node inliningTarget, Object cls, PFloat value, + @Shared @Cached InlinedBranchProfile nonCompactProfile, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return fromDouble(inliningTarget, cls, value.getValue(), getShape, nonCompactProfile); + } + + @Specialization(guards = "floatCheck.execute(inliningTarget, value)") + static PDecimal doFloatGeneric(VirtualFrame frame, Node inliningTarget, Object cls, Object value, + @SuppressWarnings("unused") @Shared @Cached PyFloatCheckNode floatCheck, + @Shared @Cached PyFloatAsDoubleNode asDouble, + @Shared @Cached InlinedBranchProfile nonCompactProfile, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return fromDouble(inliningTarget, cls, asDouble.execute(frame, inliningTarget, value), getShape, nonCompactProfile); + } + + @Specialization(guards = "tupleCheck.execute(inliningTarget, value)") + static PDecimal doTuple(VirtualFrame frame, Node inliningTarget, Object cls, Object value, + @SuppressWarnings("unused") @Shared @Cached PyTupleCheckNode tupleCheck, + @Shared @Cached GetTupleStorage getTupleStorage, + @Shared @Cached("createFor($node)") BoundaryCallData boundaryCallData, + @Shared @Cached TypeNodes.GetInstanceShape getShape) throws ConversionException { + SequenceStorage storage = getTupleStorage.execute(inliningTarget, value); + Object state = BoundaryCallContext.enter(frame, boundaryCallData); + try { + return create(cls, fromSequence(storage), getShape); + } finally { + BoundaryCallContext.exit(frame, boundaryCallData, state); + } + } + + @Specialization(guards = "listCheck.execute(inliningTarget, value)") + static PDecimal doList(VirtualFrame frame, Node inliningTarget, Object cls, Object value, + @SuppressWarnings("unused") @Shared @Cached PyListCheckNode listCheck, + @Shared @Cached GetListStorageNode getListStorage, + @Shared @Cached("createFor($node)") BoundaryCallData boundaryCallData, + @Shared @Cached TypeNodes.GetInstanceShape getShape) throws ConversionException { + SequenceStorage storage = getListStorage.execute(inliningTarget, value); + Object state = BoundaryCallContext.enter(frame, boundaryCallData); + try { + return create(cls, fromSequence(storage), getShape); + } finally { + BoundaryCallContext.exit(frame, boundaryCallData, state); + } + } + + @Fallback + static PDecimal doUnsupported(Node inliningTarget, @SuppressWarnings("unused") Object cls, Object value, + @Shared @Cached GetClassNode getClass, + @Shared @Cached TypeNodes.GetNameNode getName, + @Shared @Cached PRaiseNode raiseNode) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.CONVERSION_FROM_S_TO_DECIMAL_NOT_SUPPORTED, + getName.execute(inliningTarget, getClass.execute(inliningTarget, value))); + } + + private static PDecimal create(Object cls, Value value, TypeNodes.GetInstanceShape getShape) { + return PFactory.createDecimal(cls, getShape.execute(cls), value.negative(), value.coefficient(), value.exponent(), value.kind()); + } + + private static PDecimal fromDouble(Node inliningTarget, Object cls, double value, TypeNodes.GetInstanceShape getShape, InlinedBranchProfile nonCompactProfile) { + // IEEE 754 binary64 stores the sign in bit 63, an 11-bit biased exponent + // in bits 62..52, and a 52-bit fraction in bits 51..0. The masks select + long bits = Double.doubleToRawLongBits(value); + boolean negative = bits < 0; + int exponentBits = (int) ((bits >>> 52) & 0x7ff); + long coefficient = bits & 0x000fffffffffffffL; + // An all-ones exponent encodes infinity if the fraction is zero, else NaN. + if (exponentBits == 0x7ff) { + Kind kind = coefficient == 0 ? Kind.INFINITY : Kind.NAN; + return PFactory.createDecimal(cls, getShape.execute(cls), kind == Kind.INFINITY && negative, 0L, 0, kind); + } + if (exponentBits == 0 && coefficient == 0) { + return PFactory.createDecimal(cls, getShape.execute(cls), negative, 0L, 0, Kind.FINITE); + } + int binaryExponent; + if (exponentBits == 0) { + // Subnormals have no implicit leading 1. Their integer fraction is + // scaled by 2^(1 - 1023 - 52) = 2^-1074 (bias 1023, 52 fraction bits). + binaryExponent = -1074; + } else { + // Normal values have an implicit leading 1 at bit 52. Subtract the + // exponent bias (1023) and fraction width (52) to scale this integer. + coefficient |= 1L << 52; + binaryExponent = exponentBits - 1023 - 52; + } + // Reduce coefficient * 2^binaryExponent to an odd coefficient. + int trailingZeros = Long.numberOfTrailingZeros(coefficient); + coefficient >>>= trailingZeros; + binaryExponent += trailingZeros; + if (binaryExponent >= 0) { + // A compact positive coefficient has 63 usable bits. Check the shift + // count and remaining capacity before multiplying by 2^binaryExponent. + if (binaryExponent < Long.SIZE - 1 && coefficient <= (Long.MAX_VALUE >>> binaryExponent)) { + return PFactory.createDecimal(cls, getShape.execute(cls), negative, coefficient << binaryExponent, 0, Kind.FINITE); + } + } else { + // n / 2^k = (n * 5^k) * 10^-k because 2 * 5 = 10, with no decimal + // rounding. Long.MAX_VALUE / 5 is the largest coefficient that can + // survive the next multiplication by 5 without signed-long overflow. + for (int i = 0; i < -binaryExponent; i++) { + if (coefficient > Long.MAX_VALUE / 5) { + nonCompactProfile.enter(inliningTarget); + return create(cls, DecimalUtils.fromFloat(value), getShape); + } + coefficient *= 5; + } + return PFactory.createDecimal(cls, getShape.execute(cls), negative, coefficient, binaryExponent, Kind.FINITE); + } + nonCompactProfile.enter(inliningTarget); + return create(cls, DecimalUtils.fromFloat(value), getShape); + } + + @TruffleBoundary + private static Value fromSequence(SequenceStorage triple) throws ConversionException { + PRaiseNode raiseNode = PRaiseNode.getUncached(); + // Decimal triples are (sign, digits, exponent), at indices 0, 1, and 2. + if (triple.length() != 3) { + throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_SEQUENCE_LENGTH); + } + Object sign = SequenceStorageNodes.GetItemScalarNode.executeUncached(triple, 0); + if (!PyLongCheckNode.executeUncached(sign)) { + throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_SIGN); + } + long signValue = PyLongAsLongNode.executeUncached(sign); + // The tuple format uses 0 for positive and 1 for negative, including zero. + if (signValue != 0 && signValue != 1) { + throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_SIGN); + } + Object exp = SequenceStorageNodes.GetItemScalarNode.executeUncached(triple, 2); + Kind kind = Kind.FINITE; + long exponent = 0; + if (PyUnicodeCheckNode.executeUncached(exp)) { + kind = switch (CastToJavaStringNode.getUncached().execute(exp)) { + case "F" -> Kind.INFINITY; + case "n" -> Kind.NAN; + case "N" -> Kind.SNAN; + default -> throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_SPECIAL_EXPONENT); + }; + } else { + if (!PyLongCheckNode.executeUncached(exp)) { + throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_EXPONENT); + } + exponent = PyLongAsLongNode.executeUncached(exp); + } + Object digits = SequenceStorageNodes.GetItemScalarNode.executeUncached(triple, 1); + SequenceStorage storage; + if (PyTupleCheckNode.executeUncached(digits)) { + storage = GetTupleStorage.executeUncached(digits); + } else if (PyListCheckNode.executeUncached(digits)) { + storage = GetListStorageNode.executeUncached(digits); + } else { + throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_COEFFICIENT); + } + StringBuilder coefficient = new StringBuilder(storage.length()); + for (int i = 0; i < storage.length(); i++) { + Object digit = SequenceStorageNodes.GetItemScalarNode.executeUncached(storage, i); + if (!PyLongCheckNode.executeUncached(digit)) { + throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_COEFFICIENT); + } + long n = PyLongAsLongNode.executeUncached(digit); + // Each coefficient element is one base-10 digit, in the range 0..9. + if (n < 0 || n > 9) { + throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_COEFFICIENT); + } + if (kind != Kind.INFINITY) { + coefficient.append((char) ('0' + n)); + } + } + BigInteger integer = coefficient.isEmpty() ? BigInteger.ZERO : new BigInteger(coefficient.toString()); + return kind == Kind.FINITE ? DecimalUtils.finite(signValue != 0, integer, exponent) : new Value(signValue != 0, integer, 0, kind); + } } } @@ -879,11 +1183,15 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class TpReprNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.tp_repr. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static TruffleString repr(PDecimal self, + @Cached TruffleString.FromJavaStringNode fromJavaString) { + return fromJavaString.execute(reprString(self), TS_ENCODING); + } + + @TruffleBoundary + private static String reprString(PDecimal self) { + // TODO read capitals from the current context once contexts are implemented. + return "Decimal('" + self.toScientificString(true) + "')"; } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java new file mode 100644 index 0000000000..615f3cb616 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java @@ -0,0 +1,216 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import org.graalvm.shadowed.com.ibm.icu.lang.UCharacter; + +import com.oracle.graal.python.builtins.objects.decimal.PDecimal.Kind; +import com.oracle.graal.python.builtins.objects.str.StringUtils; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; + +/** Exact conversions, independent of the arithmetic context's precision and rounding. */ +public final class DecimalUtils { + // Match MPD_MAX_PREC in CPython's Modules/_decimal/libmpdec/mpdecimal.h (CONFIG_64): + // the maximum context precision is 10^18 - 1 decimal digits. + public static final long MAX_PREC = 999999999999999999L; + // MPD_MAX_EMAX is also 10^18 - 1, one below libmpdec's exponent limit MPD_ELIMIT. + public static final long MAX_EMAX = MAX_PREC; + // The minimum context exponent is the negative of the maximum context exponent. + public static final long MIN_EMIN = -MAX_EMAX; + // Etiny = Emin - precision + 1: the exponent of the smallest subnormal at maximum + // precision. The precision - 1 offset accounts for the remaining coefficient digits. + public static final long MIN_ETINY = MIN_EMIN - (MAX_PREC - 1); + + private DecimalUtils() { + } + + // Special values have an unused exponent of zero. A zero NaN coefficient means no + // diagnostic payload; infinity always has a zero coefficient. + record Value(boolean negative, BigInteger coefficient, long exponent, Kind kind) { + } + + static final class ConversionException extends Exception { + private static final long serialVersionUID = 1L; + final DecimalExceptionTypes condition; + + ConversionException(DecimalExceptionTypes condition) { + this.condition = condition; + } + } + + @TruffleBoundary + static Value fromInteger(BigInteger integer) { + return new Value(integer.signum() < 0, integer.abs(), 0, Kind.FINITE); + } + + @TruffleBoundary + static Value fromFloat(double value) { + if (Double.isNaN(value)) { + return new Value(false, BigInteger.ZERO, 0, Kind.NAN); + } + boolean negative = Double.doubleToRawLongBits(value) < 0; + if (Double.isInfinite(value)) { + return new Value(negative, BigInteger.ZERO, 0, Kind.INFINITY); + } + // BigDecimal(double) converts the binary value exactly, unlike valueOf(double). + // BigDecimal's scale counts fractional decimal places, hence exponent = -scale. + BigDecimal decimal = new BigDecimal(value); + return new Value(negative, decimal.unscaledValue().abs(), -((long) decimal.scale()), Kind.FINITE); + } + + @TruffleBoundary + static Value parse(String input) throws ConversionException { + int start = 0; + int end = input.length(); + while (start < end && StringUtils.isUnicodeWhitespace(input.codePointAt(start))) { + start += Character.charCount(input.codePointAt(start)); + } + while (end > start && StringUtils.isUnicodeWhitespace(input.codePointBefore(end))) { + end -= Character.charCount(input.codePointBefore(end)); + } + StringBuilder ascii = new StringBuilder(end - start); + for (int i = start; i < end;) { + int ch = input.codePointAt(i); + i += Character.charCount(ch); + if (ch == '_') { + continue; + } + // ASCII ends at U+007F (127). Beyond it, only Unicode base-10 digits are + // accepted and translated to the contiguous ASCII range '0' through '9'. + if (ch > 127) { + int digit = UCharacter.digit(ch, 10); + if (digit < 0) { + throw new ConversionException(DecimalExceptionTypes.ConversionSyntax); + } + ch = '0' + digit; + } + ascii.append((char) ch); + } + String text = ascii.toString(); + int pos = 0; + boolean negative = false; + if (!text.isEmpty() && (text.charAt(0) == '-' || text.charAt(0) == '+')) { + negative = text.charAt(0) == '-'; + pos++; + } + String unsigned = text.substring(pos); + if (unsigned.equalsIgnoreCase("inf") || unsigned.equalsIgnoreCase("infinity")) { + return new Value(negative, BigInteger.ZERO, 0, Kind.INFINITY); + } + Kind kind = Kind.FINITE; + // Skip the four-character "sNaN" or three-character "NaN" prefix; any remaining + // digits form the diagnostic payload. + if (unsigned.regionMatches(true, 0, "sNaN", 0, 4)) { + kind = Kind.SNAN; + pos += 4; + } else if (unsigned.regionMatches(true, 0, "NaN", 0, 3)) { + kind = Kind.NAN; + pos += 3; + } + StringBuilder coefficient = new StringBuilder(); + int fractionalDigits = 0; + boolean point = false; + while (pos < text.length()) { + char ch = text.charAt(pos); + if (ch >= '0' && ch <= '9') { + coefficient.append(ch); + if (point) { + fractionalDigits++; + } + } else if (ch == '.' && !point && kind == Kind.FINITE) { + point = true; + } else { + break; + } + pos++; + } + if (kind != Kind.FINITE) { + if (pos != text.length()) { + throw new ConversionException(DecimalExceptionTypes.ConversionSyntax); + } + return new Value(negative, coefficient.isEmpty() ? BigInteger.ZERO : new BigInteger(coefficient.toString()), 0, kind); + } + if (coefficient.isEmpty()) { + throw new ConversionException(DecimalExceptionTypes.ConversionSyntax); + } + long exponent = 0; + if (pos < text.length() && (text.charAt(pos) == 'e' || text.charAt(pos) == 'E')) { + int exponentStart = ++pos; + if (pos < text.length() && (text.charAt(pos) == '+' || text.charAt(pos) == '-')) { + pos++; + } + int digitsStart = pos; + while (pos < text.length() && text.charAt(pos) >= '0' && text.charAt(pos) <= '9') { + pos++; + } + if (pos == digitsStart || pos != text.length()) { + throw new ConversionException(DecimalExceptionTypes.ConversionSyntax); + } + try { + exponent = new BigInteger(text.substring(exponentStart)).longValueExact(); + } catch (ArithmeticException e) { + throw new ConversionException(DecimalExceptionTypes.InvalidOperation); + } + } + if (pos != text.length()) { + throw new ConversionException(DecimalExceptionTypes.ConversionSyntax); + } + try { + exponent = Math.subtractExact(exponent, fractionalDigits); + } catch (ArithmeticException e) { + throw new ConversionException(DecimalExceptionTypes.InvalidOperation); + } + return finite(negative, new BigInteger(coefficient.toString()), exponent); + } + + @TruffleBoundary + static Value finite(boolean negative, BigInteger coefficient, long exponent) throws ConversionException { + // The adjusted exponent locates the most significant digit: exponent + digits - 1. + // Both the stored exponent and the adjusted exponent must fit libmpdec's limits. + if (exponent < MIN_ETINY || exponent > MAX_EMAX || exponent + coefficient.toString().length() - 1 > MAX_EMAX) { + throw new ConversionException(DecimalExceptionTypes.InvalidOperation); + } + return new Value(negative, coefficient, exponent, Kind.FINITE); + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java index db139fa387..7034f6d3f9 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java @@ -40,13 +40,134 @@ */ package com.oracle.graal.python.builtins.objects.decimal; +import java.math.BigInteger; + import com.oracle.graal.python.builtins.objects.object.PythonBuiltinObject; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.object.Shape; public final class PDecimal extends PythonBuiltinObject { - // TODO implement the decimal sign, coefficient, exponent, and special values. + // abs(Long.MIN_VALUE) is 2^63, the only Java long magnitude that does not fit a + // signed long. Share its coefficient so this conversion needs no BigInteger allocation. + private static final BigInteger MIN_LONG_COEFFICIENT = BigInteger.ONE.shiftLeft(Long.SIZE - 1); + + public enum Kind { + FINITE, + INFINITY, + NAN, + SNAN + } + + // Finite values are (-1)^negative * coefficient * 10^exponent. The exponent + // preserves the quantum, including trailing zeros and the exponent of zero. + private final boolean negative; + private final long exponent; + private final Kind kind; + private final long compactCoefficient; + private final BigInteger largeCoefficient; + + public PDecimal(Object cls, Shape instanceShape, long value) { + super(cls, instanceShape); + negative = value < 0; + exponent = 0; + kind = Kind.FINITE; + if (value == Long.MIN_VALUE) { + compactCoefficient = 0; + largeCoefficient = MIN_LONG_COEFFICIENT; + } else { + compactCoefficient = Math.abs(value); + largeCoefficient = null; + } + } - public PDecimal(Object cls, Shape instanceShape) { + public PDecimal(Object cls, Shape instanceShape, boolean negative, long coefficient, long exponent, Kind kind) { super(cls, instanceShape); + assert coefficient >= 0; + this.negative = negative; + this.exponent = exponent; + this.kind = kind; + compactCoefficient = coefficient; + largeCoefficient = null; + } + + public PDecimal(Object cls, Shape instanceShape, boolean negative, BigInteger coefficient, long exponent, Kind kind) { + super(cls, instanceShape); + assert coefficient.signum() >= 0; + this.negative = negative; + this.exponent = exponent; + this.kind = kind; + // A nonnegative signed long has at most 63 bits; the 64th bit is its sign bit. + if (coefficient.bitLength() < Long.SIZE) { + compactCoefficient = coefficient.longValue(); + largeCoefficient = null; + } else { + compactCoefficient = 0; + largeCoefficient = coefficient; + } + } + + public PDecimal(Object cls, Shape instanceShape, PDecimal value) { + super(cls, instanceShape); + negative = value.negative; + exponent = value.exponent; + kind = value.kind; + compactCoefficient = value.compactCoefficient; + largeCoefficient = value.largeCoefficient; + } + + public boolean isNegative() { + return negative; + } + + public long getExponent() { + return exponent; + } + + public Kind getKind() { + return kind; + } + + public boolean isCompact() { + return largeCoefficient == null; + } + + public long getCompactCoefficient() { + assert isCompact(); + return compactCoefficient; + } + + @TruffleBoundary + public BigInteger getCoefficient() { + return isCompact() ? BigInteger.valueOf(compactCoefficient) : largeCoefficient; + } + + @TruffleBoundary + public String toScientificString(boolean capitals) { + String sign = negative ? "-" : ""; + if (kind == Kind.INFINITY) { + return sign + "Infinity"; + } + String digits = isCompact() ? Long.toString(compactCoefficient) : largeCoefficient.toString(); + if (kind != Kind.FINITE) { + return sign + (kind == Kind.NAN ? "NaN" : "sNaN") + (digits.equals("0") ? "" : digits); + } + // Moving the decimal point after the first digit adds digits.length() - 1 + // to the stored exponent, giving the exponent of the most significant digit. + long adjustedExponent = exponent + digits.length() - 1; + // General Decimal Arithmetic's to-scientific-string rule (libmpdec's io.c): + // use fixed notation for nonpositive stored exponents and adjusted exponents + // of at least -6. Thus 0.000001 stays fixed, while 0.0000001 becomes 1E-7. + if (exponent <= 0 && adjustedExponent >= -6) { + int point = (int) (digits.length() + exponent); + if (point <= 0) { + return sign + "0." + "0".repeat(-point) + digits; + } else if (point < digits.length()) { + return sign + digits.substring(0, point) + "." + digits.substring(point); + } + return sign + digits; + } + // Scientific notation puts exactly one coefficient digit before the point. + String significand = digits.length() == 1 ? digits : digits.charAt(0) + "." + digits.substring(1); + return sign + significand + (capitals ? "E" : "e") + (adjustedExponent >= 0 ? "+" : "") + adjustedExponent; } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyListCheckNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyListCheckNode.java index ab2546dbac..34861a1066 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyListCheckNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyListCheckNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -47,14 +47,20 @@ import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.GenerateCached; import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.nodes.Node; @GenerateInline @GenerateCached(false) +@GenerateUncached public abstract class PyListCheckNode extends Node { public abstract boolean execute(Node inliningTarget, Object object); + public static boolean executeUncached(Object object) { + return PyListCheckNodeGen.getUncached().execute(null, object); + } + @Specialization static boolean managed(@SuppressWarnings("unused") PList object) { return true; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/BuiltinNames.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/BuiltinNames.java index b13dd5db00..ea3a5cc7ed 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/BuiltinNames.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/BuiltinNames.java @@ -138,6 +138,16 @@ private static TruffleString tsLiteral(String s) { public static final TruffleString T_MAX = tsLiteral("max"); public static final TruffleString T_RESOLUTION = tsLiteral("resolution"); + // decimal + public static final TruffleString T_DECIMAL = tsLiteral("decimal"); + public static final TruffleString T__DECIMAL = tsLiteral("_decimal"); + + // collections + public static final TruffleString T_COLLECTIONS = tsLiteral("collections"); + public static final TruffleString T_COLLECTIONS_ABC = tsLiteral("collections.abc"); + public static final TruffleString T_NAMEDTUPLE = tsLiteral("namedtuple"); + public static final TruffleString T_MUTABLE_MAPPING = tsLiteral("MutableMapping"); + // built-in functions public static final String J_ABS = "abs"; public static final TruffleString T_ABS = tsLiteral(J_ABS); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java index c3b1c1ec55..b74b70035c 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java @@ -1604,6 +1604,15 @@ public abstract class ErrorMessages { public static final TruffleString NOT_A_DIGIT = tsLiteral("not a digit"); public static final TruffleString NOT_A_DECIMAL = tsLiteral("not a decimal"); + // decimal + public static final TruffleString OPTIONAL_DECIMAL_CONTEXT = tsLiteral("optional argument must be a context"); + public static final TruffleString CONVERSION_FROM_S_TO_DECIMAL_NOT_SUPPORTED = tsLiteral("conversion from %s to Decimal is not supported"); + public static final TruffleString DECIMAL_SEQUENCE_LENGTH = tsLiteral("argument must be a sequence of length 3"); + public static final TruffleString DECIMAL_SIGN = tsLiteral("sign must be an integer with the value 0 or 1"); + public static final TruffleString DECIMAL_EXPONENT = tsLiteral("exponent must be an integer"); + public static final TruffleString DECIMAL_SPECIAL_EXPONENT = tsLiteral("string argument in the third position must be 'F', 'n' or 'N'"); + public static final TruffleString DECIMAL_COEFFICIENT = tsLiteral("coefficient must be a tuple of digits"); + // re module errors public static final TruffleString BAD_ESCAPE_S = tsLiteral("bad escape %s"); public static final TruffleString BAD_ESCAPE_END_OF_STRING = tsLiteral("bad escape (end of string)"); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/ListNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/ListNodes.java index dde7f91b19..cce466d595 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/ListNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/ListNodes.java @@ -104,6 +104,10 @@ public abstract static class GetListStorageNode extends PNodeWithContext { public abstract SequenceStorage execute(Node inliningTarget, Object seq); + public static SequenceStorage executeUncached(Object seq) { + return ListNodesFactory.GetListStorageNodeGen.getUncached().execute(null, seq); + } + @Specialization static SequenceStorage doPList(PList list) { return list.getSequenceStorage(); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/TupleNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/TupleNodes.java index 89716be70a..6c150a4695 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/TupleNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/builtins/TupleNodes.java @@ -158,6 +158,10 @@ public static ConstructTupleNode getUncached() { public abstract static class GetTupleStorage extends Node { public abstract SequenceStorage execute(Node inliningTarget, Object tuple); + public static SequenceStorage executeUncached(Object tuple) { + return TupleNodesFactory.GetTupleStorageNodeGen.getUncached().execute(null, tuple); + } + @Specialization static SequenceStorage doManaged(PTuple tuple) { return tuple.getSequenceStorage(); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java index 989b5a5c52..9beaf3112d 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java @@ -108,6 +108,7 @@ import com.oracle.graal.python.builtins.objects.contextvars.PContextVar; import com.oracle.graal.python.builtins.objects.contextvars.PContextVarsContext; import com.oracle.graal.python.builtins.objects.contextvars.PContextVarsToken; +import com.oracle.graal.python.builtins.objects.decimal.PDecimal; import com.oracle.graal.python.builtins.objects.deque.PDeque; import com.oracle.graal.python.builtins.objects.deque.PDequeIter; import com.oracle.graal.python.builtins.objects.dict.PDefaultDict; @@ -390,6 +391,22 @@ public static PTupleGetter createTupleGetter(Object cls, Shape shape, int index, return new PTupleGetter(cls, shape, index, doc); } + public static PDecimal createDecimal(Object cls, Shape shape, long value) { + return new PDecimal(cls, shape, value); + } + + public static PDecimal createDecimal(Object cls, Shape shape, boolean negative, long coefficient, long exponent, PDecimal.Kind kind) { + return new PDecimal(cls, shape, negative, coefficient, exponent, kind); + } + + public static PDecimal createDecimal(Object cls, Shape shape, boolean negative, BigInteger coefficient, long exponent, PDecimal.Kind kind) { + return new PDecimal(cls, shape, negative, coefficient, exponent, kind); + } + + public static PDecimal createDecimal(Object cls, Shape shape, PDecimal value) { + return new PDecimal(cls, shape, value); + } + public static PComplex createComplex(Object cls, Shape shape, double real, double imag) { return new PComplex(cls, shape, real, imag); } From 5a1f93b93bc71e924353d8b6253591e8712ddfe9 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Tue, 6 Oct 2026 16:24:24 +0200 Subject: [PATCH 04/31] Implement Decimal context --- .../builtins/PythonBuiltinClassType.java | 3 +- .../modules/DecimalModuleBuiltins.java | 55 +++- .../objects/decimal/DecimalBuiltins.java | 43 ++- .../decimal/DecimalContextBuiltins.java | 266 ++++++++++------ .../DecimalContextManagerBuiltins.java | 42 +-- .../objects/decimal/DecimalContextUtils.java | 296 ++++++++++++++++++ .../objects/decimal/DecimalModuleState.java | 104 +++++- .../objects/decimal/DecimalNodes.java | 124 ++++++++ .../decimal/DecimalSignalDictBuiltins.java | 145 ++++++--- .../objects/decimal/PDecimalContext.java | 102 +++++- .../decimal/PDecimalContextManager.java | 15 +- .../objects/decimal/PDecimalSignalDict.java | 43 ++- .../graal/python/nodes/ErrorMessages.java | 14 + .../graal/python/runtime/object/PFactory.java | 15 + 14 files changed, 1037 insertions(+), 230 deletions(-) create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextUtils.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java index 32414c2ad1..6585f85507 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java @@ -158,7 +158,6 @@ import com.oracle.graal.python.builtins.objects.contextvars.TokenBuiltins; import com.oracle.graal.python.builtins.objects.decimal.DecimalBuiltins; import com.oracle.graal.python.builtins.objects.decimal.DecimalContextBuiltins; -import com.oracle.graal.python.builtins.objects.decimal.DecimalContextManagerBuiltins; import com.oracle.graal.python.builtins.objects.decimal.DecimalSignalDictBuiltins; import com.oracle.graal.python.builtins.objects.deque.DequeBuiltins; import com.oracle.graal.python.builtins.objects.deque.DequeIterBuiltins; @@ -1355,7 +1354,7 @@ def takewhile(predicate, iterable): // decimal PDecimal("Decimal", PythonObject, newBuilder().publishInModule("_decimal").moduleName("decimal").basetype().heaptype().haveGC().slots(DecimalBuiltins.SLOTS)), PDecimalContext("Context", PythonObject, newBuilder().publishInModule("_decimal").moduleName("decimal").basetype().heaptype().haveGC().slots(DecimalContextBuiltins.SLOTS)), - PDecimalContextManager("ContextManager", PythonObject, newBuilder().moduleName("decimal").heaptype().haveGC().disallowInstantiation().slots(DecimalContextManagerBuiltins.SLOTS)), + PDecimalContextManager("ContextManager", PythonObject, newBuilder().moduleName("decimal").heaptype().haveGC().disallowInstantiation()), PDecimalSignalDictMixin("SignalDictMixin", PythonObject, newBuilder().moduleName("decimal").basetype().heaptype().haveGC().slots(DecimalSignalDictBuiltins.SLOTS)), // contextvars diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java index 437263c192..f0489b49ab 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java @@ -40,7 +40,7 @@ */ package com.oracle.graal.python.builtins.modules; -import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import static com.oracle.graal.python.nodes.BuiltinNames.T_COLLECTIONS; import static com.oracle.graal.python.nodes.BuiltinNames.T_COLLECTIONS_ABC; import static com.oracle.graal.python.nodes.BuiltinNames.T_DECIMAL; @@ -59,13 +59,18 @@ import com.oracle.graal.python.builtins.Python3Core; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.decimal.DecimalContextUtils; import com.oracle.graal.python.builtins.objects.decimal.DecimalExceptionTypes; import com.oracle.graal.python.builtins.objects.decimal.DecimalModuleState; import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils; +import com.oracle.graal.python.builtins.objects.decimal.PDecimalContext; +import com.oracle.graal.python.builtins.objects.decimal.PDecimalContextManager; import com.oracle.graal.python.builtins.objects.function.PKeyword; import com.oracle.graal.python.builtins.objects.module.PythonModule; import com.oracle.graal.python.builtins.objects.type.PythonAbstractClass; import com.oracle.graal.python.builtins.objects.type.PythonClass; +import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.call.CallNode; import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; @@ -78,7 +83,6 @@ import com.oracle.truffle.api.dsl.GenerateNodeFactory; import com.oracle.truffle.api.dsl.NodeFactory; import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.strings.TruffleString; @@ -87,6 +91,9 @@ public final class DecimalModuleBuiltins extends PythonBuiltins { private static final TruffleString T_DECIMAL_TUPLE = tsLiteral("DecimalTuple"); private static final TruffleString T_DECIMAL_TUPLE_FIELDS = tsLiteral("sign digits exponent"); private static final TruffleString T_SIGNAL_DICT = tsLiteral("SignalDict"); + public static final TruffleString T_DEFAULT_CONTEXT = tsLiteral("DefaultContext"); + public static final TruffleString T_BASIC_CONTEXT = tsLiteral("BasicContext"); + public static final TruffleString T_EXTENDED_CONTEXT = tsLiteral("ExtendedContext"); public static final TruffleString T_ROUND_DOWN = tsLiteral("ROUND_DOWN"); public static final TruffleString T_ROUND_UP = tsLiteral("ROUND_UP"); @@ -157,18 +164,21 @@ public void postInitialize(Python3Core core) { PythonAbstractClass mapping = (PythonAbstractClass) abc.getAttribute(T_MUTABLE_MAPPING); PythonClass signalDict = PFactory.createPythonClassAndFixupSlots(null, language, T_SIGNAL_DICT, mixin, new PythonAbstractClass[]{mixin, mapping}); signalDict.setAttribute(T___MODULE__, T_DECIMAL); - module.setModuleState(new DecimalModuleState(exceptionTypes, decimalTuple, signalDict)); + + DecimalModuleState state = new DecimalModuleState(exceptionTypes, decimalTuple, signalDict); + module.setModuleState(state); + state.initializeContexts(language); + module.setAttribute(T_DEFAULT_CONTEXT, state.getDefaultContext()); + module.setAttribute(T_BASIC_CONTEXT, state.getBasicContext()); + module.setAttribute(T_EXTENDED_CONTEXT, state.getExtendedContext()); } @Builtin(name = "getcontext", minNumOfPositionalArgs = 0) @GenerateNodeFactory public abstract static class GetcontextNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement _decimal.getcontext. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static PDecimalContext get(@Bind Node inliningTarget) { + return DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); } } @@ -176,11 +186,14 @@ static Object doMock(VirtualFrame frame, @GenerateNodeFactory public abstract static class SetcontextNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object context, + static Object set(Object context, @Bind Node inliningTarget, @Cached PRaiseNode raiseNode) { - // TODO implement _decimal.setcontext. - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (!(context instanceof PDecimalContext decimalContext)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_CONTEXT_TYPE); + } + DecimalContextUtils.getState(inliningTarget).setCurrentContext(inliningTarget, decimalContext); + return PNone.NONE; } } @@ -188,12 +201,26 @@ static Object doMock(VirtualFrame frame, Object context, @GenerateNodeFactory public abstract static class LocalcontextNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object ctx, Object prec, Object rounding, Object Emin, Object Emax, Object capitals, Object clamp, Object flags, Object traps, + static PDecimalContextManager local(Object ctx, Object prec, Object rounding, Object emin, Object emax, Object capitals, Object clamp, Object flags, Object traps, PKeyword[] kwargs, @Bind Node inliningTarget, + @Bind PythonLanguage language, @Cached PRaiseNode raiseNode) { - // TODO implement _decimal.localcontext. - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (kwargs.length != 0) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_INVALID_LOCALCONTEXT_KEYWORD, kwargs[0].getName()); + } + DecimalModuleState state = DecimalContextUtils.getState(inliningTarget); + PDecimalContext outer = state.getCurrentContext(inliningTarget); + PDecimalContext source = outer; + if (!(ctx instanceof PNone)) { + if (!(ctx instanceof PDecimalContext decimalContext)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.OPTIONAL_DECIMAL_CONTEXT); + } + source = decimalContext; + } + PDecimalContext local = state.copyContext(language, source); + DecimalContextUtils.setAttributes(inliningTarget, local, prec, rounding, emin, emax, capitals, clamp, flags, traps); + return PFactory.createDecimalContextManager(language, outer, local); } } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java index a7ed659b7e..09726ee710 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java @@ -43,15 +43,11 @@ import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; -import static com.oracle.graal.python.nodes.BuiltinNames.T__DECIMAL; -import static com.oracle.graal.python.nodes.PGuards.isNoValue; -import static com.oracle.graal.python.nodes.PGuards.isNone; import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; import java.math.BigInteger; import java.util.List; -import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.annotations.Builtin; import com.oracle.graal.python.annotations.Slot; import com.oracle.graal.python.annotations.Slot.SlotKind; @@ -61,13 +57,14 @@ import com.oracle.graal.python.builtins.PythonBuiltins; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.GetContextNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.RaiseSignalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.ConversionException; import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.Value; import com.oracle.graal.python.builtins.objects.decimal.PDecimal.Kind; import com.oracle.graal.python.builtins.objects.floats.PFloat; import com.oracle.graal.python.builtins.objects.function.PKeyword; import com.oracle.graal.python.builtins.objects.ints.PInt; -import com.oracle.graal.python.builtins.objects.list.PList; import com.oracle.graal.python.builtins.objects.type.TpSlots; import com.oracle.graal.python.builtins.objects.type.TypeNodes; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotBinaryOp.BinaryOpBuiltinNode; @@ -88,7 +85,6 @@ import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.builtins.ListNodes.GetListStorageNode; import com.oracle.graal.python.nodes.builtins.TupleNodes.GetTupleStorage; -import com.oracle.graal.python.nodes.call.CallNode; import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; import com.oracle.graal.python.nodes.function.PythonBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode; @@ -100,7 +96,6 @@ import com.oracle.graal.python.nodes.util.CastToJavaStringNode; import com.oracle.graal.python.runtime.ExecutionContext.BoundaryCallContext; import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; -import com.oracle.graal.python.runtime.PythonContext; import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; @@ -117,7 +112,6 @@ import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.api.profiles.InlinedBranchProfile; import com.oracle.truffle.api.strings.TruffleString; @@ -917,23 +911,22 @@ public abstract static class TpNewNode extends PythonTernaryBuiltinNode { @Specialization static PDecimal construct(VirtualFrame frame, Object cls, Object value, Object context, @Bind Node inliningTarget, - @Bind PythonLanguage language, @Cached(inline = true) CreateDecimalNode createDecimal, - @Cached CallNode callNode, - @Cached PRaiseNode raiseNode, - @Cached BranchProfile errorProfile) { - if (!isNone(context) && !isNoValue(context) && !(context instanceof PDecimalContext)) { - throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.OPTIONAL_DECIMAL_CONTEXT); + @Cached RaiseSignalNode raiseSignal, + @Cached GetContextNode getContext, + @Cached InlinedBranchProfile errorProfile, + @Cached PyFloatCheckNode floatCheck, + @Cached TypeNodes.GetInstanceShape getShape) { + PDecimalContext decimalContext = getContext.execute(inliningTarget, context); + if (floatCheck.execute(inliningTarget, value)) { + DecimalContextUtils.addStatus(frame, decimalContext, DecimalContextUtils.conditionMask(DecimalExceptionTypes.FloatOperation), raiseSignal); } try { return createDecimal.execute(frame, inliningTarget, cls, value); } catch (ConversionException e) { - errorProfile.enter(); - // Until contexts have flags/traps, use the default InvalidOperation trap. - DecimalModuleState state = PythonContext.get(inliningTarget).lookupBuiltinModule(T__DECIMAL).getModuleState(DecimalModuleState.class); - PList conditions = PFactory.createList(language, new Object[]{state.getExceptionType(e.condition)}); - Object exception = callNode.execute(frame, state.getExceptionType(DecimalExceptionTypes.InvalidOperation), conditions); - throw PRaiseNode.raiseExceptionObjectStatic(inliningTarget, exception); + errorProfile.enter(inliningTarget); + DecimalContextUtils.addStatus(frame, decimalContext, DecimalContextUtils.conditionMask(e.condition), raiseSignal); + return PFactory.createDecimal(cls, getShape.execute(cls), false, 0L, 0L, Kind.NAN); } } @@ -1001,7 +994,6 @@ static PDecimal doIntGeneric(Node inliningTarget, Object cls, Object value, static PDecimal doDouble(Node inliningTarget, Object cls, double value, @Shared @Cached InlinedBranchProfile nonCompactProfile, @Shared @Cached TypeNodes.GetInstanceShape getShape) { - // TODO signal FloatOperation when decimal contexts and signals are implemented. return fromDouble(inliningTarget, cls, value, getShape, nonCompactProfile); } @@ -1184,14 +1176,15 @@ private static Value fromSequence(SequenceStorage triple) throws ConversionExcep public abstract static class TpReprNode extends PythonUnaryBuiltinNode { @Specialization static TruffleString repr(PDecimal self, + @Bind Node inliningTarget, @Cached TruffleString.FromJavaStringNode fromJavaString) { - return fromJavaString.execute(reprString(self), TS_ENCODING); + boolean capitals = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget).getCapitals() != 0; + return fromJavaString.execute(reprString(self, capitals), TS_ENCODING); } @TruffleBoundary - private static String reprString(PDecimal self) { - // TODO read capitals from the current context once contexts are implemented. - return "Decimal('" + self.toScientificString(true) + "')"; + private static String reprString(PDecimal self, boolean capitals) { + return "Decimal('" + self.toScientificString(capitals) + "')"; } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java index baa939529a..ef6fcec0d8 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java @@ -40,21 +40,35 @@ */ package com.oracle.graal.python.builtins.objects.decimal; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.AttributeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; +import static com.oracle.graal.python.builtins.objects.decimal.DecimalContextUtils.T_FLAGS; +import static com.oracle.graal.python.builtins.objects.decimal.DecimalContextUtils.T_TRAPS; +import static com.oracle.graal.python.nodes.ErrorMessages.ATTR_NAME_MUST_BE_STRING; +import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; import java.util.List; +import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.annotations.Builtin; +import com.oracle.graal.python.annotations.Slot; import com.oracle.graal.python.annotations.Slot.SlotKind; import com.oracle.graal.python.annotations.Slot.SlotSignature; -import com.oracle.graal.python.annotations.Slot; import com.oracle.graal.python.builtins.CoreFunctions; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.function.PKeyword; +import com.oracle.graal.python.builtins.objects.object.ObjectBuiltins; +import com.oracle.graal.python.builtins.objects.str.StringNodes.CastToTruffleStringChecked1Node; import com.oracle.graal.python.builtins.objects.type.TpSlots; +import com.oracle.graal.python.builtins.objects.type.TypeNodes; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotGetAttr.GetAttrBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotSetAttr.SetAttrBuiltinNode; +import com.oracle.graal.python.lib.PyLongAsLongNode; +import com.oracle.graal.python.lib.PyLongCheckNode; +import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; import com.oracle.graal.python.nodes.function.PythonBuiltinNode; @@ -63,6 +77,8 @@ import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonVarargsBuiltinNode; +import com.oracle.graal.python.nodes.object.GetClassNode; +import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.GenerateNodeFactory; @@ -70,6 +86,7 @@ import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.strings.TruffleString; @CoreFunctions(extendClasses = PythonBuiltinClassType.PDecimalContext) public final class DecimalContextBuiltins extends PythonBuiltins { @@ -456,11 +473,8 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class EtinyNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.Etiny. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static long get(PDecimalContext self) { + return self.getEmin() - self.getPrecision() + 1; } } @@ -468,11 +482,8 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class EtopNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.Etop. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static long get(PDecimalContext self) { + return self.getEmax() - self.getPrecision() + 1; } } @@ -480,11 +491,8 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class RadixNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.radix. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static PDecimal radix(@SuppressWarnings("unused") PDecimalContext self, @Bind PythonLanguage language) { + return PFactory.createDecimal(PythonBuiltinClassType.PDecimal, PythonBuiltinClassType.PDecimal.getInstanceShape(language), 10L); } } @@ -852,11 +860,9 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class ClearFlagsNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.clear_flags. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static Object clear(PDecimalContext self) { + self.setFlags(0); + return PNone.NONE; } } @@ -864,11 +870,9 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class ClearTrapsNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.clear_traps. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static Object clear(PDecimalContext self) { + self.setTraps(0); + return PNone.NONE; } } @@ -876,11 +880,10 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class CopySpecialNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static PDecimalContext copy(PDecimalContext self, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.__copy__. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Bind PythonLanguage language) { + return DecimalContextUtils.getState(inliningTarget).copyContext(language, self); } } @@ -888,11 +891,15 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class ReduceNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static Object reduce(PDecimalContext self, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.__reduce__. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Bind PythonLanguage language, + @Cached GetClassNode getClass) { + Object args = PFactory.createTuple(language, new Object[]{ + self.getPrecision(), self.getRounding(), self.getEmin(), self.getEmax(), self.getCapitals(), self.getClamp(), + DecimalContextUtils.signalsAsList(inliningTarget, self.getFlags()), DecimalContextUtils.signalsAsList(inliningTarget, self.getTraps()) + }); + return PFactory.createTuple(language, new Object[]{getClass.execute(inliningTarget, self), args}); } } @@ -900,11 +907,10 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class CopyNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static PDecimalContext copy(PDecimalContext self, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.copy. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Bind PythonLanguage language) { + return DecimalContextUtils.getState(inliningTarget).copyContext(language, self); } } @@ -932,75 +938,134 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, } } - @Builtin(name = "prec", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true) + @Builtin(name = "prec", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true) @GenerateNodeFactory public abstract static class PrecNode extends PythonBinaryBuiltinNode { - @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Specialization(guards = "isNoValue(value)") + static long get(PDecimalContext self, @SuppressWarnings("unused") PNone value) { + return self.getPrecision(); + } + + @Specialization(guards = "!isNoValue(value)") + static Object set(VirtualFrame frame, PDecimalContext self, Object value, @Bind Node inliningTarget, + @Cached PyLongAsLongNode asLong, + @Cached PyLongCheckNode longCheck, @Cached PRaiseNode raiseNode) { - // TODO implement Context.prec (get/set). - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (!longCheck.execute(inliningTarget, value)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.INTEGER_REQUIRED); + } + long number = asLong.execute(frame, inliningTarget, value); + self.setPrecision(DecimalContextUtils.validateLong(inliningTarget, raiseNode, number, 1, DecimalUtils.MAX_PREC, ErrorMessages.DECIMAL_PREC_RANGE)); + return PNone.NONE; } } - @Builtin(name = "Emax", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true) + @Builtin(name = "Emax", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true) @GenerateNodeFactory public abstract static class EmaxNode extends PythonBinaryBuiltinNode { - @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Specialization(guards = "isNoValue(value)") + static long get(PDecimalContext self, @SuppressWarnings("unused") PNone value) { + return self.getEmax(); + } + + @Specialization(guards = "!isNoValue(value)") + static Object set(VirtualFrame frame, PDecimalContext self, Object value, @Bind Node inliningTarget, + @Cached PyLongAsLongNode asLong, + @Cached PyLongCheckNode longCheck, @Cached PRaiseNode raiseNode) { - // TODO implement Context.Emax (get/set). - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (!longCheck.execute(inliningTarget, value)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.INTEGER_REQUIRED); + } + long number = asLong.execute(frame, inliningTarget, value); + self.setEmax(DecimalContextUtils.validateLong(inliningTarget, raiseNode, number, 0, DecimalUtils.MAX_EMAX, ErrorMessages.DECIMAL_EMAX_RANGE)); + return PNone.NONE; } } - @Builtin(name = "Emin", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true) + @Builtin(name = "Emin", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true) @GenerateNodeFactory public abstract static class EminNode extends PythonBinaryBuiltinNode { - @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Specialization(guards = "isNoValue(value)") + static long get(PDecimalContext self, @SuppressWarnings("unused") PNone value) { + return self.getEmin(); + } + + @Specialization(guards = "!isNoValue(value)") + static Object set(VirtualFrame frame, PDecimalContext self, Object value, @Bind Node inliningTarget, + @Cached PyLongAsLongNode asLong, + @Cached PyLongCheckNode longCheck, @Cached PRaiseNode raiseNode) { - // TODO implement Context.Emin (get/set). - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (!longCheck.execute(inliningTarget, value)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.INTEGER_REQUIRED); + } + long number = asLong.execute(frame, inliningTarget, value); + self.setEmin(DecimalContextUtils.validateLong(inliningTarget, raiseNode, number, DecimalUtils.MIN_EMIN, 0, ErrorMessages.DECIMAL_EMIN_RANGE)); + return PNone.NONE; } } - @Builtin(name = "rounding", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true) + @Builtin(name = "rounding", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true) @GenerateNodeFactory public abstract static class RoundingNode extends PythonBinaryBuiltinNode { - @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.rounding (get/set). - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Specialization(guards = "isNoValue(value)") + static TruffleString get(PDecimalContext self, @SuppressWarnings("unused") PNone value) { + return self.getRounding(); + } + + @Specialization(guards = "!isNoValue(value)") + static Object set(PDecimalContext self, Object value, + @Bind Node inliningTarget) { + self.setRounding(DecimalContextUtils.validateRounding(inliningTarget, value)); + return PNone.NONE; } } - @Builtin(name = "capitals", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true) + @Builtin(name = "capitals", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true) @GenerateNodeFactory public abstract static class CapitalsNode extends PythonBinaryBuiltinNode { - @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Specialization(guards = "isNoValue(value)") + static int get(PDecimalContext self, @SuppressWarnings("unused") PNone value) { + return self.getCapitals(); + } + + @Specialization(guards = "!isNoValue(value)") + static Object set(VirtualFrame frame, PDecimalContext self, Object value, @Bind Node inliningTarget, + @Cached PyLongAsLongNode asLong, + @Cached PyLongCheckNode longCheck, @Cached PRaiseNode raiseNode) { - // TODO implement Context.capitals (get/set). - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (!longCheck.execute(inliningTarget, value)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.INTEGER_REQUIRED); + } + long number = asLong.execute(frame, inliningTarget, value); + self.setCapitals((int) DecimalContextUtils.validateLong(inliningTarget, raiseNode, number, 0, 1, ErrorMessages.DECIMAL_CAPITALS_RANGE)); + return PNone.NONE; } } - @Builtin(name = "clamp", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true, allowsDelete = true) + @Builtin(name = "clamp", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true) @GenerateNodeFactory public abstract static class ClampNode extends PythonBinaryBuiltinNode { - @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + @Specialization(guards = "isNoValue(value)") + static int get(PDecimalContext self, @SuppressWarnings("unused") PNone value) { + return self.getClamp(); + } + + @Specialization(guards = "!isNoValue(value)") + static Object set(VirtualFrame frame, PDecimalContext self, Object value, @Bind Node inliningTarget, + @Cached PyLongAsLongNode asLong, + @Cached PyLongCheckNode longCheck, @Cached PRaiseNode raiseNode) { - // TODO implement Context.clamp (get/set). - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (!longCheck.execute(inliningTarget, value)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.INTEGER_REQUIRED); + } + long number = asLong.execute(frame, inliningTarget, value); + self.setClamp(DecimalContextUtils.validateClamp(inliningTarget, raiseNode, number)); + return PNone.NONE; } } @@ -1009,11 +1074,10 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class TpNewNode extends PythonVarargsBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object cls, Object[] args, PKeyword[] kwargs, + static PDecimalContext construct(Object cls, @SuppressWarnings("unused") Object[] args, @SuppressWarnings("unused") PKeyword[] kwargs, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.tp_new. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached TypeNodes.GetInstanceShape getShape) { + return DecimalContextUtils.getState(inliningTarget).newContext(cls, getShape.execute(cls)); } } @@ -1022,11 +1086,10 @@ static Object doMock(VirtualFrame frame, Object cls, Object[] args, PKeyword[] k @GenerateNodeFactory public abstract static class TpInitNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.tp_init. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static Object initialize(PDecimalContext self, Object prec, Object rounding, Object emin, Object emax, Object capitals, Object clamp, Object flags, Object traps, + @Bind Node inliningTarget) { + DecimalContextUtils.setAttributes(inliningTarget, self, prec, rounding, emin, emax, capitals, clamp, flags, traps); + return PNone.NONE; } } @@ -1034,11 +1097,9 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class TpReprNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.tp_repr. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static TruffleString repr(PDecimalContext self, + @Cached TruffleString.FromJavaStringNode fromJavaString) { + return fromJavaString.execute(DecimalContextUtils.contextRepr(self), TS_ENCODING); } } @@ -1046,11 +1107,19 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class TpGetattroNode extends GetAttrBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static Object get(VirtualFrame frame, PDecimalContext self, Object name, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.tp_getattro. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached CastToTruffleStringChecked1Node castName, + @Cached TruffleString.EqualNode equal, + @Cached ObjectBuiltins.GetAttributeNode genericGet) { + TruffleString key = castName.cast(inliningTarget, name, ATTR_NAME_MUST_BE_STRING, name); + if (equal.execute(key, T_FLAGS, TS_ENCODING)) { + return self.getFlagsDict(); + } + if (equal.execute(key, T_TRAPS, TS_ENCODING)) { + return self.getTrapsDict(); + } + return genericGet.execute(frame, self, key); } } @@ -1058,11 +1127,22 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class TpSetattroNode extends SetAttrBuiltinNode { @Specialization - static void doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.tp_setattro. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static void set(VirtualFrame frame, PDecimalContext self, Object name, Object value, + @Bind Node inliningTarget, + @Cached CastToTruffleStringChecked1Node castName, + @Cached TruffleString.EqualNode equal, + @Cached PRaiseNode raiseNode, + @Cached ObjectBuiltins.SetattrNode genericSet) { + TruffleString key = castName.cast(inliningTarget, name, ATTR_NAME_MUST_BE_STRING, name); + if (value == PNone.NO_VALUE) { + throw raiseNode.raise(inliningTarget, AttributeError, ErrorMessages.CANT_DELETE_ATTRIBUTE); + } else if (equal.execute(key, T_FLAGS, TS_ENCODING)) { + self.setFlags(DecimalContextUtils.signalsFromObject(inliningTarget, value, false)); + } else if (equal.execute(key, T_TRAPS, TS_ENCODING)) { + self.setTraps(DecimalContextUtils.signalsFromObject(inliningTarget, value, false)); + } else { + genericSet.execute(frame, self, key, value); + } } } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextManagerBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextManagerBuiltins.java index 9fb7e834d4..a9d23a7a50 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextManagerBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextManagerBuiltins.java @@ -40,34 +40,24 @@ */ package com.oracle.graal.python.builtins.objects.decimal; -import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; - import java.util.List; import com.oracle.graal.python.annotations.Builtin; -import com.oracle.graal.python.annotations.Slot.SlotKind; -import com.oracle.graal.python.annotations.Slot; import com.oracle.graal.python.builtins.CoreFunctions; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; -import com.oracle.graal.python.builtins.objects.type.TpSlots; -import com.oracle.graal.python.builtins.objects.type.slots.TpSlotGetAttr.GetAttrBuiltinNode; -import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; import com.oracle.graal.python.nodes.function.PythonBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; import com.oracle.truffle.api.dsl.Bind; -import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.GenerateNodeFactory; import com.oracle.truffle.api.dsl.NodeFactory; import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; @CoreFunctions(extendClasses = PythonBuiltinClassType.PDecimalContextManager) public final class DecimalContextManagerBuiltins extends PythonBuiltins { - public static final TpSlots SLOTS = DecimalContextManagerBuiltinsSlotsGen.SLOTS; - @Override protected List> getNodeFactories() { return DecimalContextManagerBuiltinsFactory.getFactories(); @@ -77,11 +67,10 @@ protected List> getNodeFa @GenerateNodeFactory public abstract static class EnterNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement ContextManager.__enter__. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static PDecimalContext enter(PDecimalContextManager self, + @Bind Node inliningTarget) { + DecimalContextUtils.getState(inliningTarget).setCurrentContext(inliningTarget, self.getLocal()); + return self.getLocal(); } } @@ -89,23 +78,10 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class ExitNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object[] args, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement ContextManager.__exit__. - throw raiseNode.raise(inliningTarget, NotImplementedError); - } - } - - @Slot(value = SlotKind.tp_getattro, isComplex = true) - @GenerateNodeFactory - public abstract static class TpGetattroNode extends GetAttrBuiltinNode { - @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement ContextManager.tp_getattro. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static Object exit(PDecimalContextManager self, @SuppressWarnings("unused") Object[] args, + @Bind Node inliningTarget) { + DecimalContextUtils.getState(inliningTarget).setCurrentContext(inliningTarget, self.getOuter()); + return PNone.NONE; } } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextUtils.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextUtils.java new file mode 100644 index 0000000000..796c4a0e4c --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextUtils.java @@ -0,0 +1,296 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.KeyError; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; +import static com.oracle.graal.python.nodes.BuiltinNames.T__DECIMAL; +import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; +import static com.oracle.graal.python.util.PythonUtils.tsLiteral; + +import java.util.ArrayList; +import java.util.List; + +import com.oracle.graal.python.PythonLanguage; +import com.oracle.graal.python.builtins.modules.DecimalModuleBuiltins; +import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageLen; +import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.GetItemScalarNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.RaiseSignalNode; +import com.oracle.graal.python.builtins.objects.dict.PDict; +import com.oracle.graal.python.builtins.objects.list.PList; +import com.oracle.graal.python.lib.PyListCheckNode; +import com.oracle.graal.python.lib.PyLongAsLongNode; +import com.oracle.graal.python.lib.PyLongCheckNode; +import com.oracle.graal.python.lib.PyObjectIsTrueNode; +import com.oracle.graal.python.nodes.ErrorMessages; +import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.builtins.ListNodes; +import com.oracle.graal.python.nodes.util.CannotCastException; +import com.oracle.graal.python.nodes.util.CastToTruffleStringNode; +import com.oracle.graal.python.runtime.PythonContext; +import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.strings.TruffleString; + +/** Context configuration and the status protocol shared by decimal operations. */ +public final class DecimalContextUtils { + public static final DecimalExceptionTypes[] SIGNALS = { + DecimalExceptionTypes.InvalidOperation, DecimalExceptionTypes.FloatOperation, DecimalExceptionTypes.DivisionByZero, + DecimalExceptionTypes.Overflow, DecimalExceptionTypes.Underflow, DecimalExceptionTypes.Subnormal, + DecimalExceptionTypes.Inexact, DecimalExceptionTypes.Rounded, DecimalExceptionTypes.Clamped + }; + // Context repr follows libmpdec's status-bit ordering, rather than exception priority. + private static final DecimalExceptionTypes[] REPR_SIGNALS = { + DecimalExceptionTypes.Clamped, DecimalExceptionTypes.InvalidOperation, DecimalExceptionTypes.DivisionByZero, + DecimalExceptionTypes.Inexact, DecimalExceptionTypes.FloatOperation, DecimalExceptionTypes.Overflow, + DecimalExceptionTypes.Rounded, DecimalExceptionTypes.Subnormal, DecimalExceptionTypes.Underflow + }; + static final DecimalExceptionTypes[] INVALID_CONDITIONS = { + DecimalExceptionTypes.InvalidOperation, DecimalExceptionTypes.ConversionSyntax, + DecimalExceptionTypes.DivisionImpossible, DecimalExceptionTypes.DivisionUndefined, DecimalExceptionTypes.InvalidContext + }; + private static final TruffleString[] ROUNDING_MODES = { + DecimalModuleBuiltins.T_ROUND_CEILING, DecimalModuleBuiltins.T_ROUND_FLOOR, + DecimalModuleBuiltins.T_ROUND_UP, DecimalModuleBuiltins.T_ROUND_DOWN, + DecimalModuleBuiltins.T_ROUND_HALF_UP, DecimalModuleBuiltins.T_ROUND_HALF_DOWN, + DecimalModuleBuiltins.T_ROUND_HALF_EVEN, DecimalModuleBuiltins.T_ROUND_05UP + }; + public static final int DEFAULT_TRAPS = mask(DecimalExceptionTypes.InvalidOperation) | + mask(DecimalExceptionTypes.DivisionByZero) | mask(DecimalExceptionTypes.Overflow); + public static final TruffleString T_FLAGS = tsLiteral("flags"); + public static final TruffleString T_TRAPS = tsLiteral("traps"); + + private DecimalContextUtils() { + } + + public static int conditionMask(DecimalExceptionTypes condition) { + return 1 << condition.ordinal(); + } + + public static int mask(DecimalExceptionTypes signal) { + if (signal == DecimalExceptionTypes.InvalidOperation) { + int result = 0; + for (DecimalExceptionTypes condition : INVALID_CONDITIONS) { + result |= conditionMask(condition); + } + return result; + } + return conditionMask(signal); + } + + public static DecimalModuleState getState(Node node) { + return PythonContext.get(node).lookupBuiltinModule(T__DECIMAL).getModuleState(DecimalModuleState.class); + } + + public static int signalMask(Node node, PRaiseNode raiseNode, Object key) { + DecimalModuleState state = getState(node); + for (DecimalExceptionTypes signal : SIGNALS) { + if (key == state.getExceptionType(signal)) { + return mask(signal); + } + } + throw raiseNode.raise(node, KeyError, ErrorMessages.DECIMAL_INVALID_SIGNALS); + } + + /** Set all sticky flags before selecting a trap; trap precedence is CPython's signal order. */ + public static void addStatus(VirtualFrame frame, PDecimalContext context, int status, RaiseSignalNode raiseSignal) { + context.setFlags(context.getFlags() | status); + int trapped = status & context.getTraps(); + if (trapped != 0) { + raiseSignal.execute(frame, trapped); + } + } + + @TruffleBoundary + public static int signalsFromObject(Node node, Object value, boolean allowList) { + if (allowList && PyListCheckNode.executeUncached(value)) { + int result = 0; + SequenceStorage storage = ListNodes.GetListStorageNode.executeUncached(value); + for (int i = 0; i < storage.length(); i++) { + result |= signalMask(node, PRaiseNode.getUncached(), GetItemScalarNode.executeUncached(storage, i)); + } + return result; + } + if (value instanceof PDecimalSignalDict valueDict) { + valueDict.checkValid(node); + return valueDict.getFlags(); + } + if (!(value instanceof PDict dict)) { + throw PRaiseNode.raiseStatic(node, TypeError, ErrorMessages.DECIMAL_EXPECTED_SIGNAL_DICT); + } + if (HashingStorageLen.executeUncached(dict.getDictStorage()) != SIGNALS.length) { + throw PRaiseNode.raiseStatic(node, KeyError, ErrorMessages.DECIMAL_INVALID_SIGNAL_DICT); + } + DecimalModuleState state = getState(node); + int result = 0; + for (DecimalExceptionTypes signal : SIGNALS) { + Object item = dict.getItem(state.getExceptionType(signal)); + if (item == null) { + throw PRaiseNode.raiseStatic(node, KeyError, ErrorMessages.DECIMAL_INVALID_SIGNAL_DICT); + } + if (PyObjectIsTrueNode.executeUncached(item)) { + result |= mask(signal); + } + } + return result; + } + + @TruffleBoundary + public static PDict signalsAsDict(Node node, int flags) { + PDict result = PFactory.createDict(PythonLanguage.get(node)); + DecimalModuleState state = getState(node); + for (DecimalExceptionTypes signal : SIGNALS) { + result.setItem(state.getExceptionType(signal), (flags & mask(signal)) != 0); + } + return result; + } + + @TruffleBoundary + public static PList signalsAsList(Node node, int flags) { + List result = new ArrayList<>(); + DecimalModuleState state = getState(node); + for (DecimalExceptionTypes signal : SIGNALS) { + if ((flags & mask(signal)) != 0) { + result.add(state.getExceptionType(signal)); + } + } + return PFactory.createList(PythonLanguage.get(node), result.toArray()); + } + + public static long validateLong(Node node, PRaiseNode raiseNode, long value, long min, long max, TruffleString error) { + if (value < min || value > max) { + throw raiseNode.raise(node, ValueError, error); + } + return value; + } + + public static int validateClamp(Node node, PRaiseNode raiseNode, long value) { + return (int) validateLong(node, raiseNode, value, 0, 1, ErrorMessages.DECIMAL_CLAMP_RANGE); + } + + @TruffleBoundary + public static TruffleString validateRounding(Node node, Object value) { + try { + TruffleString string = CastToTruffleStringNode.executeUncached(value); + for (TruffleString rounding : ROUNDING_MODES) { + if (rounding.equalsUncached(string, TS_ENCODING)) { + return rounding; + } + } + } catch (CannotCastException e) { + // Report the supported rounding modes for both wrong types and unknown strings. + } + throw PRaiseNode.raiseStatic(node, TypeError, ErrorMessages.DECIMAL_ROUNDING_VALUES); + } + + /** None and omitted arguments leave existing attributes unchanged, including in __init__. */ + @TruffleBoundary + public static void setAttributes(Node node, PDecimalContext self, Object prec, Object rounding, + Object emin, Object emax, Object capitals, Object clamp, Object flags, Object traps) { + if (isProvided(prec)) { + self.setPrecision(validateLong(node, PRaiseNode.getUncached(), asContextLong(node, prec), 1, DecimalUtils.MAX_PREC, ErrorMessages.DECIMAL_PREC_RANGE)); + } + if (isProvided(rounding)) { + self.setRounding(validateRounding(node, rounding)); + } + if (isProvided(emin)) { + self.setEmin(validateLong(node, PRaiseNode.getUncached(), asContextLong(node, emin), DecimalUtils.MIN_EMIN, 0, ErrorMessages.DECIMAL_EMIN_RANGE)); + } + if (isProvided(emax)) { + self.setEmax(validateLong(node, PRaiseNode.getUncached(), asContextLong(node, emax), 0, DecimalUtils.MAX_EMAX, ErrorMessages.DECIMAL_EMAX_RANGE)); + } + if (isProvided(capitals)) { + self.setCapitals((int) validateLong(node, PRaiseNode.getUncached(), asContextLong(node, capitals), 0, 1, ErrorMessages.DECIMAL_CAPITALS_RANGE)); + } + if (isProvided(clamp)) { + self.setClamp(validateClamp(node, PRaiseNode.getUncached(), asContextLong(node, clamp))); + } + // Match CPython's validation and mutation order. + if (isProvided(traps)) { + self.setTraps(signalsFromObject(node, traps, true)); + } + if (isProvided(flags)) { + self.setFlags(signalsFromObject(node, flags, true)); + } + } + + private static long asContextLong(Node node, Object value) { + if (!PyLongCheckNode.executeUncached(value)) { + throw PRaiseNode.raiseStatic(node, TypeError, ErrorMessages.INTEGER_REQUIRED); + } + return PyLongAsLongNode.executeUncached(value); + } + + private static boolean isProvided(Object value) { + return !(value instanceof PNone); + } + + @TruffleBoundary + public static String contextRepr(PDecimalContext self) { + return "Context(prec=" + self.getPrecision() + ", rounding=" + self.getRounding().toJavaStringUncached() + + ", Emin=" + self.getEmin() + ", Emax=" + self.getEmax() + ", capitals=" + self.getCapitals() + + ", clamp=" + self.getClamp() + ", flags=" + signalNames(self.getFlags()) + ", traps=" + signalNames(self.getTraps()) + ")"; + } + + private static String signalNames(int flags) { + List names = new ArrayList<>(); + for (DecimalExceptionTypes signal : REPR_SIGNALS) { + if ((flags & mask(signal)) != 0) { + names.add(signal.name()); + } + } + return "[" + String.join(", ", names) + "]"; + } + + @TruffleBoundary + public static String signalDictRepr(int flags) { + List items = new ArrayList<>(); + for (DecimalExceptionTypes signal : SIGNALS) { + items.add(":" + ((flags & mask(signal)) != 0 ? "True" : "False")); + } + return "{" + String.join(", ", items) + "}"; + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java index 50a2976478..e87c0b1e00 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java @@ -40,30 +40,114 @@ */ package com.oracle.graal.python.builtins.objects.decimal; +import static com.oracle.graal.python.builtins.modules.DecimalModuleBuiltins.T_ROUND_HALF_UP; +import static com.oracle.graal.python.util.PythonUtils.tsLiteral; + +import com.oracle.graal.python.PythonLanguage; +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.objects.contextvars.PContextVar; import com.oracle.graal.python.builtins.objects.type.PythonClass; +import com.oracle.graal.python.runtime.PythonContext; +import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.object.Shape; +import com.oracle.truffle.api.strings.TruffleString; public final class DecimalModuleState { + public static final TruffleString T_DECIMAL_CONTEXT = tsLiteral("decimal_context"); + private final PythonClass[] exceptionTypes; - private final PythonClass decimalTuple; - private final PythonClass signalDict; + private final PythonClass decimalTupleType; + private final PythonClass signalDictType; + + private PDecimalContext defaultContext; + private PDecimalContext basicContext; + private PDecimalContext extendedContext; + private PContextVar currentContext; - public DecimalModuleState(PythonClass[] exceptionTypes, PythonClass decimalTuple, PythonClass signalDict) { + public DecimalModuleState(PythonClass[] exceptionTypes, PythonClass decimalTupleType, PythonClass signalDictType) { this.exceptionTypes = exceptionTypes; - this.decimalTuple = decimalTuple; - this.signalDict = signalDict; + this.decimalTupleType = decimalTupleType; + this.signalDictType = signalDictType; } public PythonClass getExceptionType(DecimalExceptionTypes type) { return exceptionTypes[type.ordinal()]; } - public PythonClass getDecimalTuple() { - return decimalTuple; + public PythonClass getDecimalTupleType() { + return decimalTupleType; + } + + public PythonClass getSignalDictType() { + return signalDictType; + } + + public void initializeContexts(PythonLanguage language) { + currentContext = PFactory.createContextVar(language, T_DECIMAL_CONTEXT, PContextVar.NO_DEFAULT); + Object cls = PythonBuiltinClassType.PDecimalContext; + Shape shape = PythonBuiltinClassType.PDecimalContext.getInstanceShape(language); + defaultContext = newContext(cls, shape); + basicContext = newContext(cls, shape); + basicContext.setPrecision(9); + basicContext.setRounding(T_ROUND_HALF_UP); + basicContext.setTraps(DecimalContextUtils.DEFAULT_TRAPS | DecimalContextUtils.mask(DecimalExceptionTypes.Underflow) | DecimalContextUtils.mask(DecimalExceptionTypes.Clamped)); + extendedContext = newContext(cls, shape); + extendedContext.setPrecision(9); + extendedContext.setTraps(0); + } + + public PDecimalContext getDefaultContext() { + return defaultContext; } - public PythonClass getSignalDict() { - return signalDict; + public PDecimalContext getBasicContext() { + return basicContext; } - // TODO implement per-interpreter current context, context templates, and signal state. + public PDecimalContext getExtendedContext() { + return extendedContext; + } + + public PDecimalContext newContext(Object cls, Shape shape) { + PDecimalContext result = PFactory.createDecimalContext(cls, shape); + if (defaultContext != null) { + result.copyFrom(defaultContext); + // Context.__new__ inherits arithmetic settings and signals, but not capitals. + result.setCapitals(1); + } + result.setSignalDicts(PFactory.createDecimalSignalDict(signalDictType, signalDictType.getInstanceShape(), result, false), + PFactory.createDecimalSignalDict(signalDictType, signalDictType.getInstanceShape(), result, true)); + return result; + } + + public PDecimalContext copyContext(PythonLanguage language, PDecimalContext original) { + PDecimalContext copy = newContext(PythonBuiltinClassType.PDecimalContext, PythonBuiltinClassType.PDecimalContext.getInstanceShape(language)); + copy.copyFrom(original); + return copy; + } + + public PDecimalContext getCurrentContext(Node node) { + PythonContext context = PythonContext.get(node); + PythonLanguage language = context.getLanguage(node); + PythonContext.PythonThreadState threadState = context.getThreadState(language); + Object value = currentContext.getValue(node, threadState); + if (value == null) { + PDecimalContext initial = copyContext(language, defaultContext); + initial.setFlags(0); + currentContext.setValue(node, threadState, initial); + return initial; + } + return (PDecimalContext) value; + } + + public void setCurrentContext(Node node, PDecimalContext value) { + PythonContext context = PythonContext.get(node); + PythonLanguage language = context.getLanguage(node); + if (value == defaultContext || value == basicContext || value == extendedContext) { + value = copyContext(language, value); + value.setFlags(0); + } + currentContext.setValue(node, context.getThreadState(language), value); + } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java new file mode 100644 index 0000000000..bcf2f1b02a --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java @@ -0,0 +1,124 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; + +import com.oracle.graal.python.PythonLanguage; +import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.list.PList; +import com.oracle.graal.python.nodes.ErrorMessages; +import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.call.CallNode; +import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.GenerateCached; +import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.Node; + +public final class DecimalNodes { + private DecimalNodes() { + } + + /** Resolve an optional context argument, matching CPython's CONTEXT_CHECK_VA. */ + @GenerateInline + @GenerateCached(false) + @GenerateUncached + public abstract static class GetContextNode extends Node { + public abstract PDecimalContext execute(Node inliningTarget, Object context); + + @Specialization + static PDecimalContext doContext(@SuppressWarnings("unused") Node inliningTarget, PDecimalContext context) { + return context; + } + + @Specialization + static PDecimalContext doDefault(Node inliningTarget, @SuppressWarnings("unused") PNone context) { + return DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + } + + @Fallback + static PDecimalContext doInvalid(Node inliningTarget, @SuppressWarnings("unused") Object context, + @Cached PRaiseNode raiseNode) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.OPTIONAL_DECIMAL_CONTEXT); + } + } + + @GenerateInline(false) // Error path + @GenerateUncached + public abstract static class RaiseSignalNode extends Node { + public abstract void execute(VirtualFrame frame, int trapped); + + @Specialization + static void raise(VirtualFrame frame, int trapped, + @Bind Node inliningTarget, + @Bind PythonLanguage language, + @Cached CallNode callNode, + @Cached PRaiseNode raiseNode) { + DecimalModuleState state = DecimalContextUtils.getState(inliningTarget); + Object[] conditions = new Object[Integer.bitCount(trapped)]; + int index = 0; + for (DecimalExceptionTypes condition : DecimalContextUtils.INVALID_CONDITIONS) { + if ((trapped & DecimalContextUtils.conditionMask(condition)) != 0) { + conditions[index++] = state.getExceptionType(condition); + } + } + for (int i = 1; i < DecimalContextUtils.SIGNALS.length; i++) { + if ((trapped & DecimalContextUtils.mask(DecimalContextUtils.SIGNALS[i])) != 0) { + conditions[index++] = state.getExceptionType(DecimalContextUtils.SIGNALS[i]); + } + } + assert index == conditions.length; + for (DecimalExceptionTypes signal : DecimalContextUtils.SIGNALS) { + if ((trapped & DecimalContextUtils.mask(signal)) != 0) { + PList args = PFactory.createList(language, conditions); + Object exception = callNode.execute(frame, state.getExceptionType(signal), args); + throw raiseNode.raiseExceptionObject(inliningTarget, exception); + } + } + } + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalSignalDictBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalSignalDictBuiltins.java index 73d5d56092..a3353e42eb 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalSignalDictBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalSignalDictBuiltins.java @@ -40,30 +40,42 @@ */ package com.oracle.graal.python.builtins.objects.decimal; -import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; +import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; import java.util.List; +import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.annotations.Builtin; import com.oracle.graal.python.annotations.HashNotImplemented; +import com.oracle.graal.python.annotations.Slot; import com.oracle.graal.python.annotations.Slot.SlotKind; import com.oracle.graal.python.annotations.Slot.SlotSignature; -import com.oracle.graal.python.annotations.Slot; import com.oracle.graal.python.builtins.CoreFunctions; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.PNotImplemented; +import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageLen; +import com.oracle.graal.python.builtins.objects.dict.PDict; import com.oracle.graal.python.builtins.objects.function.PKeyword; import com.oracle.graal.python.builtins.objects.type.TpSlots; +import com.oracle.graal.python.builtins.objects.type.TypeNodes; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotBinaryFunc.MpSubscriptBuiltinNode; -import com.oracle.graal.python.builtins.objects.type.slots.TpSlotGetAttr.GetAttrBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotLen.LenBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotMpAssSubscript.MpAssSubscriptBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotRichCompare.RichCmpBuiltinNode; +import com.oracle.graal.python.lib.PyDictGetItem; +import com.oracle.graal.python.lib.PyObjectGetIter; +import com.oracle.graal.python.lib.PyObjectIsTrueNode; import com.oracle.graal.python.lib.RichCmpOp; +import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonVarargsBuiltinNode; +import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.GenerateNodeFactory; @@ -71,6 +83,7 @@ import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.strings.TruffleString; @CoreFunctions(extendClasses = PythonBuiltinClassType.PDecimalSignalDictMixin) @HashNotImplemented @@ -82,15 +95,14 @@ protected List> getNodeFa return DecimalSignalDictBuiltinsFactory.getFactories(); } - @Builtin(name = "copy", minNumOfPositionalArgs = 1) + @Slot(value = SlotKind.tp_new, isComplex = true) + @SlotSignature(name = "SignalDictMixin", minNumOfPositionalArgs = 1, takesVarArgs = true, takesVarKeywordArgs = true) @GenerateNodeFactory - public abstract static class CopyNode extends PythonUnaryBuiltinNode { + public abstract static class TpNewNode extends PythonVarargsBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement SignalDictMixin.copy. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static PDecimalSignalDict construct(Object cls, @SuppressWarnings("unused") Object[] args, @SuppressWarnings("unused") PKeyword[] kwargs, + @Cached TypeNodes.GetInstanceShape getShape) { + return PFactory.createDecimalSignalDict(cls, getShape.execute(cls), null, false); } } @@ -99,11 +111,9 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class TpInitNode extends PythonVarargsBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object[] args, PKeyword[] kwargs, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement SignalDictMixin.tp_init. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static Object initialize(PDecimalSignalDict self, @SuppressWarnings("unused") Object[] args, @SuppressWarnings("unused") PKeyword[] kwargs) { + self.detach(); + return PNone.NONE; } } @@ -111,23 +121,12 @@ static Object doMock(VirtualFrame frame, Object self, Object[] args, PKeyword[] @GenerateNodeFactory public abstract static class TpReprNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement SignalDictMixin.tp_repr. - throw raiseNode.raise(inliningTarget, NotImplementedError); - } - } - - @Slot(value = SlotKind.tp_getattro, isComplex = true) - @GenerateNodeFactory - public abstract static class TpGetattroNode extends GetAttrBuiltinNode { - @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static TruffleString repr(PDecimalSignalDict self, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement SignalDictMixin.tp_getattro. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached PRaiseNode raiseNode, + @Cached TruffleString.FromJavaStringNode fromJavaString) { + self.checkValid(raiseNode, inliningTarget); + return fromJavaString.execute(DecimalContextUtils.signalDictRepr(self.getFlags()), TS_ENCODING); } } @@ -135,11 +134,36 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class TpRichcompareNode extends RichCmpBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, RichCmpOp op, + static Object compare(VirtualFrame frame, PDecimalSignalDict self, Object other, RichCmpOp op, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement SignalDictMixin.tp_richcompare. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached PRaiseNode raiseNode, + @Cached HashingStorageLen lenNode, + @Cached PyDictGetItem getItem, + @Cached PyObjectIsTrueNode isTrue) { + self.checkValid(raiseNode, inliningTarget); + if (other instanceof PDecimalSignalDict otherSignals) { + otherSignals.checkValid(raiseNode, inliningTarget); + if (op == RichCmpOp.Py_EQ || op == RichCmpOp.Py_NE) { + return (self.getFlags() == otherSignals.getFlags()) != (op == RichCmpOp.Py_NE); + } + } else if (other instanceof PDict otherDict && (op == RichCmpOp.Py_EQ || op == RichCmpOp.Py_NE)) { + if (lenNode.execute(inliningTarget, otherDict.getDictStorage()) != DecimalContextUtils.SIGNALS.length) { + return PNotImplemented.NOT_IMPLEMENTED; + } + DecimalModuleState state = DecimalContextUtils.getState(inliningTarget); + int flags = 0; + for (DecimalExceptionTypes signal : DecimalContextUtils.SIGNALS) { + Object value = getItem.execute(frame, inliningTarget, otherDict, state.getExceptionType(signal)); + if (value == null) { + return PNotImplemented.NOT_IMPLEMENTED; + } + if (isTrue.execute(frame, value)) { + flags |= DecimalContextUtils.mask(signal); + } + } + return (self.getFlags() == flags) ^ (op == RichCmpOp.Py_NE); + } + return PNotImplemented.NOT_IMPLEMENTED; } } @@ -147,11 +171,18 @@ static Object doMock(VirtualFrame frame, Object self, Object other, RichCmpOp op @GenerateNodeFactory public abstract static class TpIterNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static Object iterator(VirtualFrame frame, PDecimalSignalDict self, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement SignalDictMixin.tp_iter. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Bind PythonLanguage language, + @Cached PRaiseNode raiseNode, + @Cached PyObjectGetIter getIter) { + self.checkValid(raiseNode, inliningTarget); + DecimalModuleState state = DecimalContextUtils.getState(inliningTarget); + Object[] keys = new Object[DecimalContextUtils.SIGNALS.length]; + for (int i = 0; i < keys.length; i++) { + keys[i] = state.getExceptionType(DecimalContextUtils.SIGNALS[i]); + } + return getIter.execute(frame, inliningTarget, PFactory.createTuple(language, keys)); } } @@ -159,11 +190,11 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class MpLengthNode extends LenBuiltinNode { @Specialization - static int doMock(VirtualFrame frame, Object self, + static int length(PDecimalSignalDict self, @Bind Node inliningTarget, @Cached PRaiseNode raiseNode) { - // TODO implement SignalDictMixin.mp_length. - throw raiseNode.raise(inliningTarget, NotImplementedError); + self.checkValid(raiseNode, inliningTarget); + return DecimalContextUtils.SIGNALS.length; } } @@ -171,11 +202,11 @@ static int doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class MpSubscriptNode extends MpSubscriptBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static boolean get(PDecimalSignalDict self, Object key, @Bind Node inliningTarget, @Cached PRaiseNode raiseNode) { - // TODO implement SignalDictMixin.mp_subscript. - throw raiseNode.raise(inliningTarget, NotImplementedError); + self.checkValid(raiseNode, inliningTarget); + return (self.getFlags() & DecimalContextUtils.signalMask(inliningTarget, raiseNode, key)) != 0; } } @@ -183,11 +214,29 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class MpAssSubscriptNode extends MpAssSubscriptBuiltinNode { @Specialization - static void doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static void set(VirtualFrame frame, PDecimalSignalDict self, Object key, Object value, @Bind Node inliningTarget, + @Cached PyObjectIsTrueNode isTrue, @Cached PRaiseNode raiseNode) { - // TODO implement SignalDictMixin.mp_ass_subscript. - throw raiseNode.raise(inliningTarget, NotImplementedError); + self.checkValid(raiseNode, inliningTarget); + if (value == PNone.NO_VALUE) { + throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.DECIMAL_SIGNAL_DELETE); + } + int mask = DecimalContextUtils.signalMask(inliningTarget, raiseNode, key); + boolean enabled = isTrue.execute(frame, value); + self.setFlags(enabled ? self.getFlags() | mask : self.getFlags() & ~mask); + } + } + + @Builtin(name = "copy", minNumOfPositionalArgs = 1) + @GenerateNodeFactory + public abstract static class CopyNode extends PythonUnaryBuiltinNode { + @Specialization + @TruffleBoundary + static PDict copy(PDecimalSignalDict self, + @Bind Node inliningTarget) { + self.checkValid(inliningTarget); + return DecimalContextUtils.signalsAsDict(inliningTarget, self.getFlags()); } } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContext.java index 2a3ab93609..22e716767b 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContext.java @@ -40,13 +40,113 @@ */ package com.oracle.graal.python.builtins.objects.decimal; +import static com.oracle.graal.python.builtins.modules.DecimalModuleBuiltins.T_ROUND_HALF_EVEN; + import com.oracle.graal.python.builtins.objects.object.PythonBuiltinObject; import com.oracle.truffle.api.object.Shape; +import com.oracle.truffle.api.strings.TruffleString; public final class PDecimalContext extends PythonBuiltinObject { - // TODO implement precision, rounding, exponent limits, capitals, clamp, flags, and traps. + private long precision = 28; + private long emin = -999999; + private long emax = 999999; + private TruffleString rounding = T_ROUND_HALF_EVEN; + private int capitals = 1; + private int clamp; + private int flags; + private int traps = DecimalContextUtils.DEFAULT_TRAPS; + private PDecimalSignalDict flagsDict; + private PDecimalSignalDict trapsDict; public PDecimalContext(Object cls, Shape instanceShape) { super(cls, instanceShape); } + + public long getPrecision() { + return precision; + } + + public void setPrecision(long value) { + precision = value; + } + + public long getEmin() { + return emin; + } + + public void setEmin(long value) { + emin = value; + } + + public long getEmax() { + return emax; + } + + public void setEmax(long value) { + emax = value; + } + + public TruffleString getRounding() { + return rounding; + } + + public void setRounding(TruffleString value) { + rounding = value; + } + + public int getCapitals() { + return capitals; + } + + public void setCapitals(int value) { + capitals = value; + } + + public int getClamp() { + return clamp; + } + + public void setClamp(int value) { + clamp = value; + } + + public int getFlags() { + return flags; + } + + public void setFlags(int value) { + flags = value; + } + + public int getTraps() { + return traps; + } + + public void setTraps(int value) { + traps = value; + } + + public PDecimalSignalDict getFlagsDict() { + return flagsDict; + } + + public PDecimalSignalDict getTrapsDict() { + return trapsDict; + } + + public void setSignalDicts(PDecimalSignalDict flagsDict, PDecimalSignalDict trapsDict) { + this.flagsDict = flagsDict; + this.trapsDict = trapsDict; + } + + public void copyFrom(PDecimalContext other) { + precision = other.precision; + emin = other.emin; + emax = other.emax; + rounding = other.rounding; + capitals = other.capitals; + clamp = other.clamp; + flags = other.flags; + traps = other.traps; + } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContextManager.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContextManager.java index 20ac8f67e6..096f39f3eb 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContextManager.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContextManager.java @@ -44,9 +44,20 @@ import com.oracle.truffle.api.object.Shape; public final class PDecimalContextManager extends PythonBuiltinObject { - // TODO implement the outer and local contexts and context restoration state. + private final PDecimalContext outer; + private final PDecimalContext local; - public PDecimalContextManager(Object cls, Shape instanceShape) { + public PDecimalContextManager(Object cls, Shape instanceShape, PDecimalContext outer, PDecimalContext local) { super(cls, instanceShape); + this.outer = outer; + this.local = local; + } + + public PDecimalContext getOuter() { + return outer; + } + + public PDecimalContext getLocal() { + return local; } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalSignalDict.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalSignalDict.java index a19626e375..8335278ca4 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalSignalDict.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalSignalDict.java @@ -40,13 +40,52 @@ */ package com.oracle.graal.python.builtins.objects.decimal; +import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.objects.object.PythonBuiltinObject; +import com.oracle.graal.python.nodes.ErrorMessages; +import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.object.Shape; public final class PDecimalSignalDict extends PythonBuiltinObject { - // TODO implement a view of context signals with flags/traps ownership. + private PDecimalContext context; + private final boolean traps; - public PDecimalSignalDict(Object cls, Shape instanceShape) { + public PDecimalSignalDict(Object cls, Shape instanceShape, PDecimalContext context, boolean traps) { super(cls, instanceShape); + this.context = context; + this.traps = traps; + } + + public boolean isValid() { + return context != null; + } + + public void detach() { + context = null; + } + + public int getFlags() { + return traps ? context.getTraps() : context.getFlags(); + } + + public void setFlags(int value) { + if (traps) { + context.setTraps(value); + } else { + context.setFlags(value); + } + } + + public void checkValid(Node node) { + if (!isValid()) { + throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.ValueError, ErrorMessages.DECIMAL_INVALID_SIGNAL_DICT); + } + } + + public void checkValid(PRaiseNode raise, Node inliningTarget) { + if (!isValid()) { + throw raise.raise(inliningTarget, PythonBuiltinClassType.ValueError, ErrorMessages.DECIMAL_INVALID_SIGNAL_DICT); + } } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java index b74b70035c..b0012caa86 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java @@ -1612,6 +1612,20 @@ public abstract class ErrorMessages { public static final TruffleString DECIMAL_EXPONENT = tsLiteral("exponent must be an integer"); public static final TruffleString DECIMAL_SPECIAL_EXPONENT = tsLiteral("string argument in the third position must be 'F', 'n' or 'N'"); public static final TruffleString DECIMAL_COEFFICIENT = tsLiteral("coefficient must be a tuple of digits"); + public static final TruffleString DECIMAL_INVALID_SIGNAL_DICT = tsLiteral("invalid signal dict"); + public static final TruffleString DECIMAL_EXPECTED_SIGNAL_DICT = tsLiteral("argument must be a signal dict"); + public static final TruffleString DECIMAL_SIGNAL_DELETE = tsLiteral("signal keys cannot be deleted"); + public static final TruffleString DECIMAL_CONTEXT_TYPE = tsLiteral("argument must be a context"); + public static final TruffleString DECIMAL_PREC_RANGE = tsLiteral("valid range for prec is [1, MAX_PREC]"); + public static final TruffleString DECIMAL_EMIN_RANGE = tsLiteral("valid range for Emin is [MIN_EMIN, 0]"); + public static final TruffleString DECIMAL_EMAX_RANGE = tsLiteral("valid range for Emax is [0, MAX_EMAX]"); + public static final TruffleString DECIMAL_CAPITALS_RANGE = tsLiteral("valid values for capitals are 0 or 1"); + public static final TruffleString DECIMAL_CLAMP_RANGE = tsLiteral("valid values for clamp are 0 or 1"); + public static final TruffleString DECIMAL_ROUNDING_VALUES = tsLiteral( + "valid values for rounding are:\n [ROUND_CEILING, ROUND_FLOOR, ROUND_UP, ROUND_DOWN,\n ROUND_HALF_UP, ROUND_HALF_DOWN, ROUND_HALF_EVEN,\n ROUND_05UP]"); + public static final TruffleString DECIMAL_INVALID_SIGNALS = tsLiteral( + "valid values for signals are:\n [InvalidOperation, FloatOperation, DivisionByZero,\n Overflow, Underflow, Subnormal, Inexact, Rounded,\n Clamped]"); + public static final TruffleString DECIMAL_INVALID_LOCALCONTEXT_KEYWORD = tsLiteral("'%s' is an invalid keyword argument for this function"); // re module errors public static final TruffleString BAD_ESCAPE_S = tsLiteral("bad escape %s"); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java index 9beaf3112d..38ad726c71 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java @@ -109,6 +109,9 @@ import com.oracle.graal.python.builtins.objects.contextvars.PContextVarsContext; import com.oracle.graal.python.builtins.objects.contextvars.PContextVarsToken; import com.oracle.graal.python.builtins.objects.decimal.PDecimal; +import com.oracle.graal.python.builtins.objects.decimal.PDecimalContext; +import com.oracle.graal.python.builtins.objects.decimal.PDecimalContextManager; +import com.oracle.graal.python.builtins.objects.decimal.PDecimalSignalDict; import com.oracle.graal.python.builtins.objects.deque.PDeque; import com.oracle.graal.python.builtins.objects.deque.PDequeIter; import com.oracle.graal.python.builtins.objects.dict.PDefaultDict; @@ -407,6 +410,18 @@ public static PDecimal createDecimal(Object cls, Shape shape, PDecimal value) { return new PDecimal(cls, shape, value); } + public static PDecimalContext createDecimalContext(Object cls, Shape shape) { + return new PDecimalContext(cls, shape); + } + + public static PDecimalSignalDict createDecimalSignalDict(Object cls, Shape shape, PDecimalContext context, boolean traps) { + return new PDecimalSignalDict(cls, shape, context, traps); + } + + public static PDecimalContextManager createDecimalContextManager(PythonLanguage language, PDecimalContext outer, PDecimalContext local) { + return new PDecimalContextManager(PythonBuiltinClassType.PDecimalContextManager, PythonBuiltinClassType.PDecimalContextManager.getInstanceShape(language), outer, local); + } + public static PComplex createComplex(Object cls, Shape shape, double real, double imag) { return new PComplex(cls, shape, real, imag); } From 034ae169d2d486c4244d170fd24a2a7945a84a9e Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Thu, 8 Oct 2026 18:09:29 +0200 Subject: [PATCH 05/31] Implement basic Decimal arithmetics --- .../modules/DecimalModuleBuiltins.java | 17 +- .../objects/decimal/DecimalArithmetic.java | 861 ++++++++++++++++++ .../objects/decimal/DecimalBuiltins.java | 176 ++-- .../decimal/DecimalContextBuiltins.java | 10 +- .../objects/decimal/DecimalContextUtils.java | 20 +- .../objects/decimal/DecimalModuleState.java | 6 +- .../objects/decimal/DecimalNodes.java | 484 ++++++++++ .../objects/decimal/DecimalPowerMath.java | 219 +++++ .../objects/decimal/DecimalRounding.java | 67 ++ .../objects/decimal/DecimalUtils.java | 52 +- .../builtins/objects/decimal/PDecimal.java | 12 +- .../objects/decimal/PDecimalContext.java | 15 +- .../graal/python/nodes/ErrorMessages.java | 3 + .../graal/python/runtime/object/PFactory.java | 16 + 14 files changed, 1860 insertions(+), 98 deletions(-) create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalPowerMath.java create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalRounding.java diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java index f0489b49ab..ed503182fb 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java @@ -63,6 +63,7 @@ import com.oracle.graal.python.builtins.objects.decimal.DecimalContextUtils; import com.oracle.graal.python.builtins.objects.decimal.DecimalExceptionTypes; import com.oracle.graal.python.builtins.objects.decimal.DecimalModuleState; +import com.oracle.graal.python.builtins.objects.decimal.DecimalRounding; import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils; import com.oracle.graal.python.builtins.objects.decimal.PDecimalContext; import com.oracle.graal.python.builtins.objects.decimal.PDecimalContextManager; @@ -95,14 +96,14 @@ public final class DecimalModuleBuiltins extends PythonBuiltins { public static final TruffleString T_BASIC_CONTEXT = tsLiteral("BasicContext"); public static final TruffleString T_EXTENDED_CONTEXT = tsLiteral("ExtendedContext"); - public static final TruffleString T_ROUND_DOWN = tsLiteral("ROUND_DOWN"); - public static final TruffleString T_ROUND_UP = tsLiteral("ROUND_UP"); - public static final TruffleString T_ROUND_HALF_EVEN = tsLiteral("ROUND_HALF_EVEN"); - public static final TruffleString T_ROUND_HALF_DOWN = tsLiteral("ROUND_HALF_DOWN"); - public static final TruffleString T_ROUND_HALF_UP = tsLiteral("ROUND_HALF_UP"); - public static final TruffleString T_ROUND_CEILING = tsLiteral("ROUND_CEILING"); - public static final TruffleString T_ROUND_FLOOR = tsLiteral("ROUND_FLOOR"); - public static final TruffleString T_ROUND_05UP = tsLiteral("ROUND_05UP"); + public static final TruffleString T_ROUND_DOWN = DecimalRounding.ROUND_DOWN.getPythonName(); + public static final TruffleString T_ROUND_UP = DecimalRounding.ROUND_UP.getPythonName(); + public static final TruffleString T_ROUND_HALF_EVEN = DecimalRounding.ROUND_HALF_EVEN.getPythonName(); + public static final TruffleString T_ROUND_HALF_DOWN = DecimalRounding.ROUND_HALF_DOWN.getPythonName(); + public static final TruffleString T_ROUND_HALF_UP = DecimalRounding.ROUND_HALF_UP.getPythonName(); + public static final TruffleString T_ROUND_CEILING = DecimalRounding.ROUND_CEILING.getPythonName(); + public static final TruffleString T_ROUND_FLOOR = DecimalRounding.ROUND_FLOOR.getPythonName(); + public static final TruffleString T_ROUND_05UP = DecimalRounding.ROUND_05UP.getPythonName(); public DecimalModuleBuiltins() { addBuiltinConstant("MAX_PREC", DecimalUtils.MAX_PREC); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java new file mode 100644 index 0000000000..9887b5c469 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java @@ -0,0 +1,861 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import static com.oracle.graal.python.builtins.objects.decimal.DecimalRounding.ROUND_FLOOR; +import static com.oracle.graal.python.builtins.objects.decimal.DecimalRounding.ROUND_HALF_EVEN; +import static com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.pow10; + +import java.math.BigInteger; + +import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.Value; +import com.oracle.graal.python.builtins.objects.decimal.PDecimal.Kind; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; + +/** + * Decimal coefficient arithmetic and context finalization. The algorithms follow CPython's + * _pydecimal, using integer remainders (rather than pre-rounded BigDecimal operations) to retain + * exactness and rounding information. No Python calls or native arithmetic are involved. + */ +final class DecimalArithmetic { + record Result(Value value, int status) { + } + + record ResultWithRemainder(Value value, Value remainder, int status) { + } + + private static final BigInteger FIVE = BigInteger.valueOf(5); + private static final int INVALID = DecimalContextUtils.conditionMask(DecimalExceptionTypes.InvalidOperation); + private static final int INEXACT = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Inexact); + private static final int ROUNDED = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Rounded); + private static final int CLAMPED = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Clamped); + private static final int SUBNORMAL = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Subnormal); + private static final int UNDERFLOW = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Underflow); + private static final int OVERFLOW = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Overflow); + private static final int DIVISION_ZERO = DecimalContextUtils.conditionMask(DecimalExceptionTypes.DivisionByZero); + private static final int DIVISION_UNDEFINED = DecimalContextUtils.conditionMask(DecimalExceptionTypes.DivisionUndefined); + private static final int DIVISION_IMPOSSIBLE = DecimalContextUtils.conditionMask(DecimalExceptionTypes.DivisionImpossible); + + private final long precision; + private final long emin; + private final long emax; + private final int clamp; + private final DecimalRounding rounding; + private int status; + + private DecimalArithmetic(PDecimalContext context) { + this(context.getPrecision(), context.getEmin(), context.getEmax(), context.getRounding(), context.getClamp()); + } + + private DecimalArithmetic(long precision, long emin, long emax, DecimalRounding rounding, int clamp) { + this.precision = precision; + this.emin = emin; + this.emax = emax; + this.rounding = rounding; + this.clamp = clamp; + } + + static int digits(long coefficient) { + return DecimalUtils.digits(coefficient); + } + + private static int digits(BigInteger coefficient) { + return DecimalUtils.digits(coefficient); + } + + static boolean fitsContext(boolean zero, long exponent, int digits, PDecimalContext context) { + long etiny = context.getEmin() - context.getPrecision() + 1; + long etop = context.getEmax() - context.getPrecision() + 1; + if (zero) { + return exponent >= etiny && exponent <= (context.getClamp() == 0 ? context.getEmax() : etop); + } + long adjusted = exponent + digits - 1; + return digits <= context.getPrecision() && adjusted >= context.getEmin() && adjusted <= context.getEmax() && + (context.getClamp() == 0 || exponent <= etop); + } + + private static Value value(PDecimal decimal) { + return new Value(decimal.isNegative(), decimal.getCoefficient(), decimal.getExponent(), decimal.getKind()); + } + + private static Value finite(boolean negative, BigInteger coefficient, long exponent) { + return new Value(negative, coefficient, exponent, Kind.FINITE); + } + + private static Value zero(boolean negative, long exponent) { + return finite(negative, BigInteger.ZERO, exponent); + } + + private static Value infinity(boolean negative) { + return new Value(negative, BigInteger.ZERO, 0, Kind.INFINITY); + } + + private static boolean isZero(Value value) { + return value.kind() == Kind.FINITE && value.coefficient().signum() == 0; + } + + private static long adjusted(Value value) { + return value.exponent() + digits(value.coefficient()) - 1; + } + + private Value invalid(int condition) { + status |= condition; + return new Value(false, BigInteger.ZERO, 0, Kind.NAN); + } + + private Value checkNaNs(Value operand) { + return checkNaNs(operand, null, null); + } + + private Value checkNaNs(Value left, Value right) { + return checkNaNs(left, right, null); + } + + private Value checkNaNs(Value first, Value second, Value third) { + // Signaling NaNs take precedence over quiet NaNs, in operand order. + Value signaling = firstNaN(Kind.SNAN, first, second, third); + if (signaling != null) { + status |= INVALID; + return fixNaN(signaling); + } + Value quiet = firstNaN(Kind.NAN, first, second, third); + return quiet == null ? null : fixNaN(quiet); + } + + private static Value firstNaN(Kind kind, Value first, Value second, Value third) { + if (first.kind() == kind) { + return first; + } + if (second != null && second.kind() == kind) { + return second; + } + return third != null && third.kind() == kind ? third : null; + } + + private Value fixNaN(Value operand) { + BigInteger payload = operand.coefficient(); + long limit = precision - clamp; + if (payload.signum() != 0 && digits(payload) > limit) { + payload = payload.remainder(pow10(limit)); + } + return new Value(operand.negative(), payload, 0, Kind.NAN); + } + + private boolean increment(boolean negative, BigInteger quotient, BigInteger remainder, BigInteger divisor) { + if (remainder.signum() == 0) { + return false; + } + return switch (rounding) { + case ROUND_DOWN -> false; + case ROUND_UP -> true; + case ROUND_CEILING -> !negative; + case ROUND_FLOOR -> negative; + case ROUND_05UP -> quotient.remainder(FIVE).signum() == 0; + case ROUND_HALF_UP -> remainder.shiftLeft(1).compareTo(divisor) >= 0; + case ROUND_HALF_DOWN -> remainder.shiftLeft(1).compareTo(divisor) > 0; + case ROUND_HALF_EVEN -> { + int half = remainder.shiftLeft(1).compareTo(divisor); + yield half > 0 || half == 0 && quotient.testBit(0); + } + }; + } + + private Value overflow(boolean negative) { + status |= OVERFLOW | INEXACT | ROUNDED; + boolean finite = switch (rounding) { + case ROUND_DOWN, ROUND_05UP -> true; + case ROUND_CEILING -> negative; + case ROUND_FLOOR -> !negative; + case ROUND_UP, ROUND_HALF_UP, ROUND_HALF_DOWN, ROUND_HALF_EVEN -> false; + }; + if (finite) { + return finite(negative, pow10(precision).subtract(BigInteger.ONE), emax - precision + 1); + } + return infinity(negative); + } + + private Value finish(Value operand) { + if (operand.kind() != Kind.FINITE) { + return operand; + } + long etiny = emin - precision + 1; + long etop = emax - precision + 1; + long exponent = operand.exponent(); + BigInteger coefficient = operand.coefficient(); + if (coefficient.signum() == 0) { + long max = clamp == 0 ? emax : etop; + long clamped = Math.min(Math.max(exponent, etiny), max); + if (clamped != exponent) { + status |= CLAMPED; + } + return zero(operand.negative(), clamped); + } + int length = digits(coefficient); + long adjusted = exponent + length - 1; + if (adjusted > emax) { + return overflow(operand.negative()); + } + boolean subnormal = adjusted < emin; + if (subnormal) { + status |= SUBNORMAL; + } + // Apply precision and the subnormal exponent floor in one rounding step. + long targetExponent = Math.max(exponent + length - precision, etiny); + if (exponent < targetExponent) { + long shift = targetExponent - exponent; + BigInteger quotient; + BigInteger remainder; + BigInteger divisor; + if (shift > length) { + quotient = BigInteger.ZERO; + remainder = BigInteger.ONE; + divisor = BigInteger.TEN; + } else { + divisor = pow10(shift); + BigInteger[] qr = coefficient.divideAndRemainder(divisor); + quotient = qr[0]; + remainder = qr[1]; + } + status |= ROUNDED; + if (remainder.signum() != 0) { + status |= INEXACT; + if (subnormal) { + status |= UNDERFLOW; + } + } + if (increment(operand.negative(), quotient, remainder, divisor)) { + coefficient = quotient.add(BigInteger.ONE); + } else { + coefficient = quotient; + } + exponent = targetExponent; + length = digits(coefficient); + if (length > precision) { + coefficient = coefficient.divide(BigInteger.TEN); + exponent++; + length--; + } + if (exponent + length - 1 > emax) { + return overflow(operand.negative()); + } + if (coefficient.signum() == 0) { + status |= CLAMPED; + } + } + if (clamp != 0 && exponent > etop) { + coefficient = coefficient.multiply(pow10(exponent - etop)); + exponent = etop; + status |= CLAMPED; + } + return finite(operand.negative(), coefficient, exponent); + } + + @TruffleBoundary + static Result plus(PDecimal self, PDecimalContext context) { + return applySign(self, context, self.isNegative()); + } + + @TruffleBoundary + static Result minus(PDecimal self, PDecimalContext context) { + return applySign(self, context, !self.isNegative()); + } + + @TruffleBoundary + static Result abs(PDecimal self, PDecimalContext context) { + return applySign(self, context, false); + } + + private static Result applySign(PDecimal self, PDecimalContext context, boolean negative) { + DecimalArithmetic arithmetic = new DecimalArithmetic(context); + Value operand = value(self); + Value result = arithmetic.checkNaNs(operand); + if (result == null) { + if (isZero(operand) && context.getRounding() != ROUND_FLOOR) { + negative = false; + } + result = arithmetic.finish(new Value(negative, operand.coefficient(), operand.exponent(), operand.kind())); + } + return new Result(result, arithmetic.status); + } + + @TruffleBoundary + static Result add(PDecimal left, PDecimal right, PDecimalContext context) { + DecimalArithmetic arithmetic = new DecimalArithmetic(context); + Value a = value(left); + Value b = value(right); + Value result = arithmetic.checkNaNs(a, b); + if (result == null) { + result = arithmetic.add(a, b); + } + return new Result(result, arithmetic.status); + } + + @TruffleBoundary + static Result subtract(PDecimal left, PDecimal right, PDecimalContext context) { + DecimalArithmetic arithmetic = new DecimalArithmetic(context); + Value a = value(left); + Value b = value(right); + Value result = arithmetic.checkNaNs(a, b); + if (result == null) { + b = new Value(!b.negative(), b.coefficient(), b.exponent(), b.kind()); + result = arithmetic.add(a, b); + } + return new Result(result, arithmetic.status); + } + + @TruffleBoundary + static Result multiply(PDecimal left, PDecimal right, PDecimalContext context) { + DecimalArithmetic arithmetic = new DecimalArithmetic(context); + Value a = value(left); + Value b = value(right); + Value result = arithmetic.checkNaNs(a, b); + if (result == null) { + result = arithmetic.multiply(a, b); + } + return new Result(result, arithmetic.status); + } + + @TruffleBoundary + static Result divide(PDecimal left, PDecimal right, PDecimalContext context) { + DecimalArithmetic arithmetic = new DecimalArithmetic(context); + Value a = value(left); + Value b = value(right); + Value result = arithmetic.checkNaNs(a, b); + if (result == null) { + result = arithmetic.divide(a, b); + } + return new Result(result, arithmetic.status); + } + + @TruffleBoundary + static Result divideInt(PDecimal left, PDecimal right, PDecimalContext context) { + DecimalArithmetic arithmetic = new DecimalArithmetic(context); + Value a = value(left); + Value b = value(right); + Value result = arithmetic.checkNaNs(a, b); + if (result == null) { + result = arithmetic.divmod(a, b, true, false).value(); + } + return new Result(result, arithmetic.status); + } + + @TruffleBoundary + static Result remainder(PDecimal left, PDecimal right, PDecimalContext context) { + DecimalArithmetic arithmetic = new DecimalArithmetic(context); + Value a = value(left); + Value b = value(right); + Value result = arithmetic.checkNaNs(a, b); + if (result == null) { + result = arithmetic.divmod(a, b, false, true).remainder(); + } + return new Result(result, arithmetic.status); + } + + @TruffleBoundary + static ResultWithRemainder divmod(PDecimal left, PDecimal right, PDecimalContext context) { + DecimalArithmetic arithmetic = new DecimalArithmetic(context); + Value a = value(left); + Value b = value(right); + Value result = arithmetic.checkNaNs(a, b); + if (result == null) { + return arithmetic.divmod(a, b, true, true); + } + return new ResultWithRemainder(result, result, arithmetic.status); + } + + private Value add(Value a, Value b) { + if (a.kind() == Kind.INFINITY || b.kind() == Kind.INFINITY) { + if (a.kind() == b.kind() && a.negative() != b.negative()) { + return invalid(INVALID); + } + return a.kind() == Kind.INFINITY ? a : b; + } + long exponent = Math.min(a.exponent(), b.exponent()); + boolean negativeZero; + if (a.negative() == b.negative()) { + negativeZero = a.negative(); + } else { + negativeZero = rounding == ROUND_FLOOR; + } + if (isZero(a) && isZero(b)) { + return finish(zero(negativeZero, exponent)); + } + if (isZero(a) || isZero(b)) { + Value nonzero = isZero(a) ? b : a; + exponent = Math.max(exponent, nonzero.exponent() - precision - 1); + return finish(finite(nonzero.negative(), nonzero.coefficient().multiply(pow10(nonzero.exponent() - exponent)), exponent)); + } + // Replace an operand too small to affect the retained digits with a signed sticky + // digit. This also handles subtraction correctly, without enormous coefficient shifts. + Value big = a.exponent() >= b.exponent() ? a : b; + Value small = a.exponent() >= b.exponent() ? b : a; + long cutoff = big.exponent() + Math.min(-1, digits(big.coefficient()) - precision - 2); + if (adjusted(small) < cutoff) { + small = finite(small.negative(), BigInteger.ONE, cutoff); + } + long commonExponent = Math.min(big.exponent(), small.exponent()); + BigInteger bigCoefficient = big.coefficient().multiply(pow10(big.exponent() - commonExponent)); + BigInteger smallCoefficient = small.coefficient().multiply(pow10(small.exponent() - commonExponent)); + if (big.negative()) { + bigCoefficient = bigCoefficient.negate(); + } + if (small.negative()) { + smallCoefficient = smallCoefficient.negate(); + } + BigInteger sum = bigCoefficient.add(smallCoefficient); + if (sum.signum() == 0) { + return finish(finite(negativeZero, sum.abs(), exponent)); + } + return finish(finite(sum.signum() < 0, sum.abs(), commonExponent)); + } + + private Value multiply(Value a, Value b) { + boolean negative = a.negative() ^ b.negative(); + if (a.kind() == Kind.INFINITY || b.kind() == Kind.INFINITY) { + return isZero(a) || isZero(b) ? invalid(INVALID) : infinity(negative); + } + return finish(finite(negative, a.coefficient().multiply(b.coefficient()), a.exponent() + b.exponent())); + } + + private Value divide(Value a, Value b) { + boolean negative = a.negative() ^ b.negative(); + if (a.kind() == Kind.INFINITY) { + return b.kind() == Kind.INFINITY ? invalid(INVALID) : infinity(negative); + } + if (b.kind() == Kind.INFINITY) { + status |= CLAMPED; + return zero(negative, emin - precision + 1); + } + if (isZero(b)) { + if (isZero(a)) { + return invalid(DIVISION_UNDEFINED); + } + status |= DIVISION_ZERO; + return infinity(negative); + } + long idealExponent = a.exponent() - b.exponent(); + if (isZero(a)) { + return finish(zero(negative, idealExponent)); + } + long shift = digits(b.coefficient()) - digits(a.coefficient()) + precision + 1; + BigInteger numerator = a.coefficient(); + BigInteger denominator = b.coefficient(); + if (shift >= 0) { + numerator = numerator.multiply(pow10(shift)); + } else { + denominator = denominator.multiply(pow10(-shift)); + } + BigInteger[] qr = numerator.divideAndRemainder(denominator); + BigInteger coefficient = qr[0]; + long exponent = idealExponent - shift; + if (qr[1].signum() != 0) { + // A nonzero remainder beyond a zero or five rounding digit must remain sticky. + if (coefficient.remainder(FIVE).signum() == 0) { + coefficient = coefficient.add(BigInteger.ONE); + } + } else { + while (exponent < idealExponent && coefficient.remainder(BigInteger.TEN).signum() == 0) { + coefficient = coefficient.divide(BigInteger.TEN); + exponent++; + } + } + return finish(finite(negative, coefficient, exponent)); + } + + private ResultWithRemainder divmod(Value a, Value b, boolean quotientNeeded, boolean remainderNeeded) { + boolean negative = a.negative() ^ b.negative(); + Value quotient; + Value remainder; + if (a.kind() == Kind.INFINITY) { + quotient = b.kind() == Kind.INFINITY ? invalid(INVALID) : infinity(negative); + remainder = remainderNeeded ? invalid(INVALID) : null; + } else if (isZero(b)) { + if (isZero(a)) { + quotient = invalid(DIVISION_UNDEFINED); + remainder = quotient; + } else { + quotient = infinity(negative); + if (quotientNeeded) { + status |= DIVISION_ZERO; + } + remainder = remainderNeeded ? invalid(INVALID) : null; + } + } else if (b.kind() == Kind.INFINITY) { + quotient = zero(negative, 0); + remainder = remainderNeeded ? finish(a) : null; + } else { + long exponent = Math.min(a.exponent(), b.exponent()); + long expdiff = adjusted(a) - adjusted(b); + if (!isZero(a) && expdiff > precision) { + quotient = invalid(DIVISION_IMPOSSIBLE); + remainder = quotient; + } else { + BigInteger q; + BigInteger r; + if (isZero(a) || expdiff < 0 && compareMagnitude(a, b) < 0) { + q = BigInteger.ZERO; + if (remainderNeeded) { + r = a.coefficient().multiply(pow10(a.exponent() - exponent)); + } else { + r = BigInteger.ZERO; + } + } else { + BigInteger numerator = a.coefficient().multiply(pow10(a.exponent() - exponent)); + BigInteger denominator = b.coefficient().multiply(pow10(b.exponent() - exponent)); + BigInteger[] qr = numerator.divideAndRemainder(denominator); + q = qr[0]; + r = qr[1]; + } + if (digits(q) > precision) { + quotient = invalid(DIVISION_IMPOSSIBLE); + remainder = quotient; + } else { + quotient = zero(negative, 0); + if (q.signum() != 0) { + quotient = finite(negative, q, 0); + } + if (quotientNeeded) { + quotient = finish(quotient); + } + if (remainderNeeded) { + remainder = finish(finite(a.negative(), r, exponent)); + } else { + remainder = null; + } + } + } + } + return new ResultWithRemainder(quotient, remainder, status); + } + + private static int compareMagnitude(Value a, Value b) { + if (isZero(a) || isZero(b)) { + return a.coefficient().signum() - b.coefficient().signum(); + } + int cmp = Long.compare(adjusted(a), adjusted(b)); + if (cmp != 0) { + return cmp; + } + long exponent = Math.min(a.exponent(), b.exponent()); + return a.coefficient().multiply(pow10(a.exponent() - exponent)).compareTo(b.coefficient().multiply(pow10(b.exponent() - exponent))); + } + + private static boolean integral(Value value) { + if (value.kind() != Kind.FINITE) { + return false; + } + if (isZero(value) || value.exponent() >= 0) { + return true; + } + return -value.exponent() < digits(value.coefficient()) && value.coefficient().remainder(pow10(-value.exponent())).signum() == 0; + } + + private static Value integralParts(Value value) { + if (isZero(value)) { + return zero(value.negative(), 0); + } + if (value.exponent() >= 0) { + return value; + } + return finite(value.negative(), value.coefficient().divide(pow10(-value.exponent())), 0); + } + + private static boolean odd(Value value) { + return !isZero(value) && value.exponent() <= 0 && integralParts(value).coefficient().testBit(0); + } + + @TruffleBoundary + static Result power(PDecimal left, PDecimal right, PDecimal modulo, PDecimalContext context) { + DecimalArithmetic arithmetic = new DecimalArithmetic(context); + Value a = value(left); + Value b = value(right); + Value result; + if (modulo == null) { + result = arithmetic.checkNaNs(a, b); + if (result == null) { + result = arithmetic.power(a, b); + } + } else { + Value m = value(modulo); + result = arithmetic.checkNaNs(a, b, m); + if (result == null) { + result = arithmetic.powerModulo(a, b, m); + } + } + return new Result(result, arithmetic.status); + } + + private Value powerModulo(Value a, Value b, Value modulo) { + if (!integral(a) || !integral(b) || !integral(modulo) || b.negative() && !isZero(b) || isZero(modulo) || + adjusted(modulo) >= precision || isZero(a) && isZero(b)) { + return invalid(INVALID); + } + boolean negative = a.negative() && odd(b); + Value modulus = integralParts(modulo); + BigInteger m = modulus.coefficient().multiply(pow10(modulus.exponent())); + if (isZero(a)) { + return zero(negative, 0); + } + Value base = integralParts(a); + Value exponent = integralParts(b); + BigInteger reduced = base.coefficient().mod(m).multiply(BigInteger.TEN.modPow(BigInteger.valueOf(base.exponent()), m)).mod(m); + for (long i = 0; i < exponent.exponent(); i++) { + BigInteger next = reduced.modPow(BigInteger.TEN, m); + if (next.equals(reduced)) { + break; + } + reduced = next; + } + return finite(negative, reduced.modPow(exponent.coefficient(), m), 0); + } + + private Value power(Value a, Value b) { + boolean integerExponent = integral(b); + boolean negative = a.negative() && integerExponent && odd(b); + if (isZero(b)) { + return isZero(a) ? invalid(INVALID) : finite(false, BigInteger.ONE, 0); + } + if (isZero(a)) { + return b.negative() ? infinity(negative) : zero(negative, 0); + } + if (a.negative() && !integerExponent) { + return invalid(INVALID); + } + if (a.kind() == Kind.INFINITY) { + return b.negative() ? zero(negative, 0) : infinity(negative); + } + Value one = finite(false, BigInteger.ONE, 0); + int comparedToOne = compareMagnitude(a, one); + if (comparedToOne == 0) { + long exponent; + if (!integerExponent) { + status |= INEXACT | ROUNDED; + exponent = 1 - precision; + } else if (b.negative() || a.exponent() == 0) { + exponent = 0; + } else { + Value parts = integralParts(b); + BigInteger multiplier = parts.coefficient(); + // Only need to distinguish the ideal quantum from the precision floor. + if (adjusted(b) >= digits(precision)) { + multiplier = BigInteger.valueOf(precision); + } else { + multiplier = multiplier.multiply(pow10(parts.exponent())); + } + BigInteger ideal = BigInteger.valueOf(a.exponent()).multiply(multiplier); + long floor = 1 - precision; + if (ideal.compareTo(BigInteger.valueOf(floor)) < 0) { + status |= ROUNDED; + exponent = floor; + } else { + exponent = ideal.longValueExact(); + } + } + return finish(finite(negative, pow10(-exponent), exponent)); + } + if (b.kind() == Kind.INFINITY) { + if ((comparedToOne > 0) != b.negative()) { + return infinity(negative); + } + return zero(negative, 0); + } + long bound = logExponentBound(a) + adjusted(b); + boolean growing = (comparedToOne > 0) != b.negative(); + long etiny = emin - precision + 1; + long limit = growing ? emax : -etiny; + if (bound > digits(limit)) { + return finish(finite(negative, BigInteger.ONE, growing ? emax + 1 : etiny - 1)); + } + if (integerExponent) { + return integerPower(a, b, negative); + } + return realPower(a, b); + } + + /** libmpdec computes real powers as exp(rounded(y * rounded(ln(x)))). */ + private Value realPower(Value base, Value exponent) { + long workPrecision = Math.max(precision, digits(base.coefficient())) + 4 + 19; + DecimalArithmetic work = new DecimalArithmetic(workPrecision, DecimalUtils.MIN_EMIN, DecimalUtils.MAX_EMAX, ROUND_HALF_EVEN, 0); + Value logarithm; + long bound = logExponentBound(base); + for (long extra = 3;; extra += 3) { + Value approximation = DecimalPowerMath.logarithm(base.coefficient(), base.exponent(), workPrecision - bound + extra); + logarithm = work.roundInterval(approximation); + if (logarithm != null) { + break; + } + } + Value argument = work.multiply(logarithm, exponent); + Value result; + if (isZero(argument) || adjusted(argument) < -workPrecision - 1) { + result = finite(false, pow10(workPrecision - 1), 1 - workPrecision); + } else { + for (long extra = 3;; extra += 3) { + Value approximation = DecimalPowerMath.exponential(argument, workPrecision + extra); + result = work.roundInterval(approximation); + if (result != null) { + break; + } + } + } + status |= INEXACT | ROUNDED; + if (result.kind() == Kind.INFINITY) { + result = finite(false, BigInteger.ONE, 4 * DecimalUtils.MAX_EMAX); + } else if (compareMagnitude(result, finite(false, BigInteger.ONE, 0)) == 0) { + result = finite(false, pow10(precision - 1), 1 - precision); + } + result = finish(result); + if ((status & SUBNORMAL) != 0) { + status |= UNDERFLOW; + } + return result; + } + + /** Round both ends of the proven approximation interval, including tininess detection. */ + private Value roundInterval(Value approximation) { + DecimalArithmetic low = new DecimalArithmetic(precision, emin, emax, rounding, clamp); + DecimalArithmetic high = new DecimalArithmetic(precision, emin, emax, rounding, clamp); + Value lower = low.finish(finite(approximation.negative(), approximation.coefficient().subtract(BigInteger.ONE), approximation.exponent())); + Value upper = high.finish(finite(approximation.negative(), approximation.coefficient().add(BigInteger.ONE), approximation.exponent())); + if (lower.equals(upper) && (low.status & SUBNORMAL) == (high.status & SUBNORMAL)) { + return lower; + } + return null; + } + + /** Match libmpdec's integer-power working precision and rounding of intermediate values. */ + private Value integerPower(Value base, Value exponent, boolean negative) { + long workPrecision = precision + digits(exponent.coefficient()) + exponent.exponent() + 2 + (exponent.negative() ? 1 : 0); + DecimalArithmetic work = new DecimalArithmetic(workPrecision, emin, emax, ROUND_HALF_EVEN, 0); + Value positiveBase = finite(false, base.coefficient(), base.exponent()); + if (exponent.negative()) { + positiveBase = work.divide(finite(false, BigInteger.ONE, 0), positiveBase); + } + Value parts = integralParts(exponent); + BigInteger n = parts.coefficient().multiply(pow10(parts.exponent())); + Value result; + if (n.bitLength() <= 64) { + // libmpdec's machine-word implementation traverses the exponent from the top. + result = positiveBase; + for (int bit = n.bitLength() - 2; bit >= 0; bit--) { + result = work.multiply(result, result); + if (n.testBit(bit)) { + result = work.multiply(result, positiveBase); + } + if (result.kind() != Kind.FINITE || isZero(result) && (work.status & CLAMPED) != 0) { + break; + } + } + } else { + result = finite(false, BigInteger.ONE, 0); + while (n.signum() != 0) { + if (n.testBit(0)) { + result = work.multiply(result, positiveBase); + if (result.kind() != Kind.FINITE || isZero(result) && (work.status & CLAMPED) != 0) { + break; + } + } + positiveBase = work.multiply(positiveBase, positiveBase); + n = n.shiftRight(1); + } + } + status |= work.status; + if (result.kind() == Kind.INFINITY) { + // Reapply the user's rounding mode to an overflow discovered in the work context. + return overflow(negative); + } + return finish(finite(negative, result.coefficient(), result.exponent())); + } + + /** Lower bound for the decimal exponent of abs(log10(a)), for early range checks. */ + private static long logExponentBound(Value a) { + long adjusted = adjusted(a); + if (adjusted >= 1) { + return digits(adjusted) - 1; + } + if (adjusted <= -2) { + return digits(-1 - adjusted) - 1; + } + BigInteger difference = a.coefficient().subtract(pow10(-a.exponent())).abs(); + return a.exponent() + digits(difference) - 1 - (adjusted == 0 ? 2 : 1); + } + + @TruffleBoundary + static BigInteger toInteger(PDecimal decimal) { + BigInteger coefficient = decimal.getCoefficient(); + if (coefficient.signum() == 0) { + return BigInteger.ZERO; + } + long exponent = decimal.getExponent(); + if (exponent >= 0) { + coefficient = coefficient.multiply(pow10(exponent)); + } else if (-exponent >= digits(coefficient)) { + return BigInteger.ZERO; + } else { + coefficient = coefficient.divide(pow10(-exponent)); + } + if (decimal.isNegative()) { + return coefficient.negate(); + } + return coefficient; + } + + @TruffleBoundary + static double toFloat(PDecimal decimal) { + if (decimal.getKind() == Kind.INFINITY) { + return decimal.isNegative() ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; + } + if (decimal.getKind() == Kind.NAN) { + return Math.copySign(Double.NaN, decimal.isNegative() ? -1.0 : 1.0); + } + if (decimal.getCoefficient().signum() == 0) { + return decimal.isNegative() ? -0.0 : 0.0; + } + long adjusted = decimal.getExponent() + digits(decimal.getCoefficient()) - 1; + if (adjusted > 308) { + return decimal.isNegative() ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; + } + if (adjusted < -324) { + return decimal.isNegative() ? -0.0 : 0.0; + } + return Double.parseDouble(decimal.toScientificString(true)); + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java index 09726ee710..9b91b87e15 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java @@ -40,7 +40,9 @@ */ package com.oracle.graal.python.builtins.objects.decimal; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.MemoryError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.OverflowError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; @@ -48,6 +50,7 @@ import java.math.BigInteger; import java.util.List; +import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.annotations.Builtin; import com.oracle.graal.python.annotations.Slot; import com.oracle.graal.python.annotations.Slot.SlotKind; @@ -56,9 +59,21 @@ import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.PNotImplemented; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.AbsDecimalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.AddDecimalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.DivideDecimalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.DivideIntDecimalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.DivmodDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.GetContextNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.MinusDecimalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.MultiplyDecimalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.PlusDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.RaiseSignalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.RemainderDecimalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.SubtractDecimalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.ToDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.ConversionException; import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.Value; import com.oracle.graal.python.builtins.objects.decimal.PDecimal.Kind; @@ -113,6 +128,7 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.profiles.InlinedBranchProfile; +import com.oracle.truffle.api.profiles.InlinedConditionProfile; import com.oracle.truffle.api.strings.TruffleString; @CoreFunctions(extendClasses = PythonBuiltinClassType.PDecimal) @@ -1165,8 +1181,16 @@ private static Value fromSequence(SequenceStorage triple) throws ConversionExcep coefficient.append((char) ('0' + n)); } } - BigInteger integer = coefficient.isEmpty() ? BigInteger.ZERO : new BigInteger(coefficient.toString()); - return kind == Kind.FINITE ? DecimalUtils.finite(signValue != 0, integer, exponent) : new Value(signValue != 0, integer, 0, kind); + BigInteger integer; + if (coefficient.isEmpty()) { + integer = BigInteger.ZERO; + } else { + integer = new BigInteger(coefficient.toString()); + } + if (kind == Kind.FINITE) { + return DecimalUtils.finite(signValue != 0, integer, exponent); + } + return new Value(signValue != 0, integer, 0, kind); } } } @@ -1178,7 +1202,7 @@ public abstract static class TpReprNode extends PythonUnaryBuiltinNode { static TruffleString repr(PDecimal self, @Bind Node inliningTarget, @Cached TruffleString.FromJavaStringNode fromJavaString) { - boolean capitals = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget).getCapitals() != 0; + boolean capitals = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget).getCapitals(); return fromJavaString.execute(reprString(self, capitals), TS_ENCODING); } @@ -1204,11 +1228,10 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class NbNegativeNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static PDecimal unary(VirtualFrame frame, PDecimal self, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.nb_negative. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached MinusDecimalNode operation) { + return operation.execute(frame, inliningTarget, self); } } @@ -1216,11 +1239,10 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class NbPositiveNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static PDecimal unary(VirtualFrame frame, PDecimal self, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.nb_positive. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached PlusDecimalNode operation) { + return operation.execute(frame, inliningTarget, self); } } @@ -1228,11 +1250,10 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class NbAbsoluteNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static PDecimal unary(VirtualFrame frame, PDecimal self, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.nb_absolute. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached AbsDecimalNode operation) { + return operation.execute(frame, inliningTarget, self); } } @@ -1240,11 +1261,41 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class NbIntNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static Object integer(PDecimal self, @Bind Node inliningTarget, + @Cached InlinedBranchProfile nonCompactProfile, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.nb_int. - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (self.getKind() == Kind.INFINITY) { + throw raiseNode.raise(inliningTarget, OverflowError, ErrorMessages.DECIMAL_INFINITY_TO_INTEGER); + } + if (self.getKind() != Kind.FINITE) { + throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.DECIMAL_NAN_TO_INTEGER); + } + if (self.isCompact()) { + long coefficient = self.getCompactCoefficient(); + long exponent = self.getExponent(); + if (exponent > 0) { + coefficient = DecimalNodes.scaleCompactCoefficient(coefficient, exponent); + } else { + // Truncate toward zero; any compact coefficient vanishes within 19 steps. + while (exponent < 0 && coefficient != 0) { + coefficient /= 10; + exponent++; + } + } + if (coefficient >= 0) { + if (self.isNegative()) { + return -coefficient; + } + return coefficient; + } + } + nonCompactProfile.enter(inliningTarget); + try { + return PFactory.createInt(PythonLanguage.get(inliningTarget), DecimalArithmetic.toInteger(self)); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } } } @@ -1252,11 +1303,13 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class NbFloatNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static double floating(PDecimal self, @Bind Node inliningTarget, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.nb_float. - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (self.getKind() == Kind.SNAN) { + throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.DECIMAL_SNAN_TO_FLOAT); + } + return DecimalArithmetic.toFloat(self); } } @@ -1300,11 +1353,10 @@ static Object doMock(VirtualFrame frame, Object self, Object other, RichCmpOp op @GenerateNodeFactory public abstract static class NbAddNode extends BinaryOpBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static Object binary(VirtualFrame frame, Object left, Object right, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.nb_add. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached AddDecimalNode operation) { + return operation.execute(frame, inliningTarget, left, right); } } @@ -1312,11 +1364,10 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class NbSubtractNode extends BinaryOpBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static Object binary(VirtualFrame frame, Object left, Object right, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.nb_subtract. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached SubtractDecimalNode operation) { + return operation.execute(frame, inliningTarget, left, right); } } @@ -1324,11 +1375,10 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class NbMultiplyNode extends BinaryOpBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static Object binary(VirtualFrame frame, Object left, Object right, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.nb_multiply. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached MultiplyDecimalNode operation) { + return operation.execute(frame, inliningTarget, left, right); } } @@ -1336,11 +1386,10 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class NbRemainderNode extends BinaryOpBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static Object binary(VirtualFrame frame, Object left, Object right, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.nb_remainder. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RemainderDecimalNode operation) { + return operation.execute(frame, inliningTarget, left, right); } } @@ -1348,11 +1397,10 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class NbDivmodNode extends BinaryOpBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static Object binary(VirtualFrame frame, Object left, Object right, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.nb_divmod. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached DivmodDecimalNode operation) { + return operation.execute(frame, inliningTarget, left, right); } } @@ -1360,11 +1408,10 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class NbFloorDivideNode extends BinaryOpBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static Object binary(VirtualFrame frame, Object left, Object right, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.nb_floor_divide. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached DivideIntDecimalNode operation) { + return operation.execute(frame, inliningTarget, left, right); } } @@ -1372,11 +1419,10 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class NbTrueDivideNode extends BinaryOpBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static Object binary(VirtualFrame frame, Object left, Object right, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.nb_true_divide. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached DivideDecimalNode operation) { + return operation.execute(frame, inliningTarget, left, right); } } @@ -1384,11 +1430,30 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class NbPowerNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static Object power(VirtualFrame frame, Object left, Object right, Object mod, @Bind Node inliningTarget, + @Cached ToDecimalNode toLeft, + @Cached ToDecimalNode toRight, + @Cached ToDecimalNode toModulo, + @Cached InlinedConditionProfile notImplementedProfile, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.nb_power. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal a = toLeft.execute(inliningTarget, left); + PDecimal b = toRight.execute(inliningTarget, right); + boolean hasModulo = !(mod instanceof PNone); + PDecimal modulo = hasModulo ? toModulo.execute(inliningTarget, mod) : null; + if (notImplementedProfile.profile(inliningTarget, a == null || b == null || hasModulo && modulo == null)) { + return PNotImplemented.NOT_IMPLEMENTED; + } + PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.power(a, b, modulo, context); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, context, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } @@ -1396,11 +1461,8 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class NbBoolNode extends NbBoolBuiltinNode { @Specialization - static boolean doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.nb_bool. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static boolean bool(PDecimal self) { + return self.getKind() != Kind.FINITE || !self.isCompact() || self.getCompactCoefficient() != 0; } } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java index ef6fcec0d8..22cf4da345 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java @@ -492,7 +492,7 @@ static long get(PDecimalContext self) { public abstract static class RadixNode extends PythonUnaryBuiltinNode { @Specialization static PDecimal radix(@SuppressWarnings("unused") PDecimalContext self, @Bind PythonLanguage language) { - return PFactory.createDecimal(PythonBuiltinClassType.PDecimal, PythonBuiltinClassType.PDecimal.getInstanceShape(language), 10L); + return PFactory.createDecimal(language, 10L); } } @@ -896,7 +896,7 @@ static Object reduce(PDecimalContext self, @Bind PythonLanguage language, @Cached GetClassNode getClass) { Object args = PFactory.createTuple(language, new Object[]{ - self.getPrecision(), self.getRounding(), self.getEmin(), self.getEmax(), self.getCapitals(), self.getClamp(), + self.getPrecision(), self.getRounding().getPythonName(), self.getEmin(), self.getEmax(), self.getCapitals() ? 1 : 0, self.getClamp(), DecimalContextUtils.signalsAsList(inliningTarget, self.getFlags()), DecimalContextUtils.signalsAsList(inliningTarget, self.getTraps()) }); return PFactory.createTuple(language, new Object[]{getClass.execute(inliningTarget, self), args}); @@ -1012,7 +1012,7 @@ static Object set(VirtualFrame frame, PDecimalContext self, Object value, public abstract static class RoundingNode extends PythonBinaryBuiltinNode { @Specialization(guards = "isNoValue(value)") static TruffleString get(PDecimalContext self, @SuppressWarnings("unused") PNone value) { - return self.getRounding(); + return self.getRounding().getPythonName(); } @Specialization(guards = "!isNoValue(value)") @@ -1028,7 +1028,7 @@ static Object set(PDecimalContext self, Object value, public abstract static class CapitalsNode extends PythonBinaryBuiltinNode { @Specialization(guards = "isNoValue(value)") static int get(PDecimalContext self, @SuppressWarnings("unused") PNone value) { - return self.getCapitals(); + return self.getCapitals() ? 1 : 0; } @Specialization(guards = "!isNoValue(value)") @@ -1041,7 +1041,7 @@ static Object set(VirtualFrame frame, PDecimalContext self, Object value, throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.INTEGER_REQUIRED); } long number = asLong.execute(frame, inliningTarget, value); - self.setCapitals((int) DecimalContextUtils.validateLong(inliningTarget, raiseNode, number, 0, 1, ErrorMessages.DECIMAL_CAPITALS_RANGE)); + self.setCapitals(DecimalContextUtils.validateLong(inliningTarget, raiseNode, number, 0, 1, ErrorMessages.DECIMAL_CAPITALS_RANGE) != 0); return PNone.NONE; } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextUtils.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextUtils.java index 796c4a0e4c..69a955b921 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextUtils.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextUtils.java @@ -51,7 +51,6 @@ import java.util.List; import com.oracle.graal.python.PythonLanguage; -import com.oracle.graal.python.builtins.modules.DecimalModuleBuiltins; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageLen; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.GetItemScalarNode; @@ -92,12 +91,7 @@ public final class DecimalContextUtils { DecimalExceptionTypes.InvalidOperation, DecimalExceptionTypes.ConversionSyntax, DecimalExceptionTypes.DivisionImpossible, DecimalExceptionTypes.DivisionUndefined, DecimalExceptionTypes.InvalidContext }; - private static final TruffleString[] ROUNDING_MODES = { - DecimalModuleBuiltins.T_ROUND_CEILING, DecimalModuleBuiltins.T_ROUND_FLOOR, - DecimalModuleBuiltins.T_ROUND_UP, DecimalModuleBuiltins.T_ROUND_DOWN, - DecimalModuleBuiltins.T_ROUND_HALF_UP, DecimalModuleBuiltins.T_ROUND_HALF_DOWN, - DecimalModuleBuiltins.T_ROUND_HALF_EVEN, DecimalModuleBuiltins.T_ROUND_05UP - }; + private static final DecimalRounding[] ROUNDING_MODES = DecimalRounding.values(); public static final int DEFAULT_TRAPS = mask(DecimalExceptionTypes.InvalidOperation) | mask(DecimalExceptionTypes.DivisionByZero) | mask(DecimalExceptionTypes.Overflow); public static final TruffleString T_FLAGS = tsLiteral("flags"); @@ -212,11 +206,11 @@ public static int validateClamp(Node node, PRaiseNode raiseNode, long value) { } @TruffleBoundary - public static TruffleString validateRounding(Node node, Object value) { + public static DecimalRounding validateRounding(Node node, Object value) { try { TruffleString string = CastToTruffleStringNode.executeUncached(value); - for (TruffleString rounding : ROUNDING_MODES) { - if (rounding.equalsUncached(string, TS_ENCODING)) { + for (DecimalRounding rounding : ROUNDING_MODES) { + if (rounding.getPythonName().equalsUncached(string, TS_ENCODING)) { return rounding; } } @@ -243,7 +237,7 @@ public static void setAttributes(Node node, PDecimalContext self, Object prec, O self.setEmax(validateLong(node, PRaiseNode.getUncached(), asContextLong(node, emax), 0, DecimalUtils.MAX_EMAX, ErrorMessages.DECIMAL_EMAX_RANGE)); } if (isProvided(capitals)) { - self.setCapitals((int) validateLong(node, PRaiseNode.getUncached(), asContextLong(node, capitals), 0, 1, ErrorMessages.DECIMAL_CAPITALS_RANGE)); + self.setCapitals(validateLong(node, PRaiseNode.getUncached(), asContextLong(node, capitals), 0, 1, ErrorMessages.DECIMAL_CAPITALS_RANGE) != 0); } if (isProvided(clamp)) { self.setClamp(validateClamp(node, PRaiseNode.getUncached(), asContextLong(node, clamp))); @@ -270,8 +264,8 @@ private static boolean isProvided(Object value) { @TruffleBoundary public static String contextRepr(PDecimalContext self) { - return "Context(prec=" + self.getPrecision() + ", rounding=" + self.getRounding().toJavaStringUncached() + - ", Emin=" + self.getEmin() + ", Emax=" + self.getEmax() + ", capitals=" + self.getCapitals() + + return "Context(prec=" + self.getPrecision() + ", rounding=" + self.getRounding().getPythonName().toJavaStringUncached() + + ", Emin=" + self.getEmin() + ", Emax=" + self.getEmax() + ", capitals=" + (self.getCapitals() ? 1 : 0) + ", clamp=" + self.getClamp() + ", flags=" + signalNames(self.getFlags()) + ", traps=" + signalNames(self.getTraps()) + ")"; } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java index e87c0b1e00..da9f66d697 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java @@ -40,7 +40,7 @@ */ package com.oracle.graal.python.builtins.objects.decimal; -import static com.oracle.graal.python.builtins.modules.DecimalModuleBuiltins.T_ROUND_HALF_UP; +import static com.oracle.graal.python.builtins.objects.decimal.DecimalRounding.ROUND_HALF_UP; import static com.oracle.graal.python.util.PythonUtils.tsLiteral; import com.oracle.graal.python.PythonLanguage; @@ -90,7 +90,7 @@ public void initializeContexts(PythonLanguage language) { defaultContext = newContext(cls, shape); basicContext = newContext(cls, shape); basicContext.setPrecision(9); - basicContext.setRounding(T_ROUND_HALF_UP); + basicContext.setRounding(ROUND_HALF_UP); basicContext.setTraps(DecimalContextUtils.DEFAULT_TRAPS | DecimalContextUtils.mask(DecimalExceptionTypes.Underflow) | DecimalContextUtils.mask(DecimalExceptionTypes.Clamped)); extendedContext = newContext(cls, shape); extendedContext.setPrecision(9); @@ -114,7 +114,7 @@ public PDecimalContext newContext(Object cls, Shape shape) { if (defaultContext != null) { result.copyFrom(defaultContext); // Context.__new__ inherits arithmetic settings and signals, but not capitals. - result.setCapitals(1); + result.setCapitals(true); } result.setSignalDicts(PFactory.createDecimalSignalDict(signalDictType, signalDictType.getInstanceShape(), result, false), PFactory.createDecimalSignalDict(signalDictType, signalDictType.getInstanceShape(), result, true)); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java index bcf2f1b02a..c1c16c7bee 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java @@ -40,15 +40,26 @@ */ package com.oracle.graal.python.builtins.objects.decimal; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.MemoryError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.PNotImplemented; +import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; +import com.oracle.graal.python.builtins.objects.decimal.DecimalArithmetic.Result; +import com.oracle.graal.python.builtins.objects.decimal.DecimalArithmetic.ResultWithRemainder; +import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.Value; +import com.oracle.graal.python.builtins.objects.decimal.PDecimal.Kind; +import com.oracle.graal.python.builtins.objects.ints.PInt; import com.oracle.graal.python.builtins.objects.list.PList; +import com.oracle.graal.python.lib.PyLongCheckNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.call.CallNode; +import com.oracle.graal.python.nodes.util.CastToJavaBigIntegerNode; import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Fallback; @@ -58,6 +69,8 @@ import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.profiles.InlinedBranchProfile; +import com.oracle.truffle.api.profiles.InlinedConditionProfile; public final class DecimalNodes { private DecimalNodes() { @@ -87,6 +100,477 @@ static PDecimalContext doInvalid(Node inliningTarget, @SuppressWarnings("unused" } } + /** Numeric slots accept Decimal and int operands, without invoking conversion protocols. */ + @GenerateInline + @GenerateCached(false) + @GenerateUncached + public abstract static class ToDecimalNode extends Node { + public abstract PDecimal execute(Node inliningTarget, Object value); + + @Specialization + static PDecimal decimal(@SuppressWarnings("unused") Node inliningTarget, PDecimal value) { + return value; + } + + @Specialization + static PDecimal smallInteger(Node inliningTarget, int value) { + return integer(inliningTarget, value); + } + + @Specialization + static PDecimal integer(Node inliningTarget, long value) { + PythonLanguage language = PythonLanguage.get(inliningTarget); + return PFactory.createDecimal(language, value); + } + + @Specialization + static PDecimal bool(Node inliningTarget, boolean value) { + return integer(inliningTarget, value ? 1L : 0L); + } + + @Specialization + static PDecimal bigInteger(Node inliningTarget, PInt value) { + DecimalUtils.Value integer = DecimalUtils.fromInteger(value.getValue()); + PythonLanguage language = PythonLanguage.get(inliningTarget); + return create(language, integer); + } + + @Specialization + @TruffleBoundary + static PDecimal nativeInteger(Node inliningTarget, PythonAbstractNativeObject value) { + if (!PyLongCheckNode.executeUncached(value)) { + return null; + } + return create(PythonLanguage.get(inliningTarget), DecimalUtils.fromInteger(CastToJavaBigIntegerNode.executeUncached(value))); + } + + @Fallback + static PDecimal unsupported(@SuppressWarnings("unused") Node inliningTarget, @SuppressWarnings("unused") Object value) { + return null; + } + } + + static PDecimal create(PythonLanguage language, Value value) { + return PFactory.createDecimal(language, value.negative(), value.coefficient(), value.exponent(), value.kind()); + } + + private static PDecimal compact(PythonLanguage language, boolean negative, long coefficient, long exponent) { + return PFactory.createDecimal(language, negative, coefficient, exponent, Kind.FINITE); + } + + @GenerateInline + @GenerateCached(false) + public abstract static class PlusDecimalNode extends Node { + public abstract PDecimal execute(VirtualFrame frame, Node inliningTarget, PDecimal self); + + @Specialization + static PDecimal plus(VirtualFrame frame, Node inliningTarget, PDecimal self, + @Cached InlinedBranchProfile nonCompactProfile, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode) { + PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PythonLanguage language = PythonLanguage.get(inliningTarget); + PDecimal fast = compactUnary(language, self, context, self.isNegative()); + if (fast != null) { + return fast; + } + nonCompactProfile.enter(inliningTarget); + Result result; + try { + result = DecimalArithmetic.plus(self, context); + } catch (ArithmeticException e) { + // BigInteger cannot allocate coefficients beyond its int-sized representation. + throw raiseNode.raise(inliningTarget, MemoryError); + } + return finishResult(frame, language, context, result, raiseSignal); + } + } + + @GenerateInline + @GenerateCached(false) + public abstract static class MinusDecimalNode extends Node { + public abstract PDecimal execute(VirtualFrame frame, Node inliningTarget, PDecimal self); + + @Specialization + static PDecimal minus(VirtualFrame frame, Node inliningTarget, PDecimal self, + @Cached InlinedBranchProfile nonCompactProfile, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode) { + PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PythonLanguage language = PythonLanguage.get(inliningTarget); + PDecimal fast = compactUnary(language, self, context, !self.isNegative()); + if (fast != null) { + return fast; + } + nonCompactProfile.enter(inliningTarget); + Result result; + try { + result = DecimalArithmetic.minus(self, context); + } catch (ArithmeticException e) { + // BigInteger cannot allocate coefficients beyond its int-sized representation. + throw raiseNode.raise(inliningTarget, MemoryError); + } + return finishResult(frame, language, context, result, raiseSignal); + } + } + + @GenerateInline + @GenerateCached(false) + public abstract static class AbsDecimalNode extends Node { + public abstract PDecimal execute(VirtualFrame frame, Node inliningTarget, PDecimal self); + + @Specialization + static PDecimal abs(VirtualFrame frame, Node inliningTarget, PDecimal self, + @Cached InlinedBranchProfile nonCompactProfile, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode) { + PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PythonLanguage language = PythonLanguage.get(inliningTarget); + PDecimal fast = compactUnary(language, self, context, false); + if (fast != null) { + return fast; + } + nonCompactProfile.enter(inliningTarget); + Result result; + try { + result = DecimalArithmetic.abs(self, context); + } catch (ArithmeticException e) { + // BigInteger cannot allocate coefficients beyond its int-sized representation. + throw raiseNode.raise(inliningTarget, MemoryError); + } + return finishResult(frame, language, context, result, raiseSignal); + } + } + + @GenerateInline + @GenerateCached(false) + public abstract static class AddDecimalNode extends Node { + public abstract Object execute(VirtualFrame frame, Node inliningTarget, Object left, Object right); + + @Specialization + static Object add(VirtualFrame frame, Node inliningTarget, Object left, Object right, + @Cached ToDecimalNode toLeft, + @Cached ToDecimalNode toRight, + @Cached InlinedConditionProfile notImplementedProfile, + @Cached InlinedBranchProfile nonCompactProfile, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode) { + PDecimal a = toLeft.execute(inliningTarget, left); + PDecimal b = toRight.execute(inliningTarget, right); + if (notImplementedProfile.profile(inliningTarget, a == null || b == null)) { + return PNotImplemented.NOT_IMPLEMENTED; + } + PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PythonLanguage language = PythonLanguage.get(inliningTarget); + PDecimal fast = compactAdd(language, a, b, context, b.isNegative()); + if (fast != null) { + return fast; + } + nonCompactProfile.enter(inliningTarget); + Result result; + try { + result = DecimalArithmetic.add(a, b, context); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + return finishResult(frame, language, context, result, raiseSignal); + } + } + + @GenerateInline + @GenerateCached(false) + public abstract static class SubtractDecimalNode extends Node { + public abstract Object execute(VirtualFrame frame, Node inliningTarget, Object left, Object right); + + @Specialization + static Object subtract(VirtualFrame frame, Node inliningTarget, Object left, Object right, + @Cached ToDecimalNode toLeft, + @Cached ToDecimalNode toRight, + @Cached InlinedConditionProfile notImplementedProfile, + @Cached InlinedBranchProfile nonCompactProfile, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode) { + PDecimal a = toLeft.execute(inliningTarget, left); + PDecimal b = toRight.execute(inliningTarget, right); + if (notImplementedProfile.profile(inliningTarget, a == null || b == null)) { + return PNotImplemented.NOT_IMPLEMENTED; + } + PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PythonLanguage language = PythonLanguage.get(inliningTarget); + PDecimal fast = compactAdd(language, a, b, context, !b.isNegative()); + if (fast != null) { + return fast; + } + nonCompactProfile.enter(inliningTarget); + Result result; + try { + result = DecimalArithmetic.subtract(a, b, context); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + return finishResult(frame, language, context, result, raiseSignal); + } + } + + @GenerateInline + @GenerateCached(false) + public abstract static class MultiplyDecimalNode extends Node { + public abstract Object execute(VirtualFrame frame, Node inliningTarget, Object left, Object right); + + @Specialization + static Object multiply(VirtualFrame frame, Node inliningTarget, Object left, Object right, + @Cached ToDecimalNode toLeft, + @Cached ToDecimalNode toRight, + @Cached InlinedConditionProfile notImplementedProfile, + @Cached InlinedBranchProfile nonCompactProfile, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode) { + PDecimal a = toLeft.execute(inliningTarget, left); + PDecimal b = toRight.execute(inliningTarget, right); + if (notImplementedProfile.profile(inliningTarget, a == null || b == null)) { + return PNotImplemented.NOT_IMPLEMENTED; + } + PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PythonLanguage language = PythonLanguage.get(inliningTarget); + PDecimal fast = compactMultiply(language, a, b, context); + if (fast != null) { + return fast; + } + nonCompactProfile.enter(inliningTarget); + Result result; + try { + result = DecimalArithmetic.multiply(a, b, context); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + return finishResult(frame, language, context, result, raiseSignal); + } + } + + @GenerateInline + @GenerateCached(false) + public abstract static class DivideDecimalNode extends Node { + public abstract Object execute(VirtualFrame frame, Node inliningTarget, Object left, Object right); + + @Specialization + static Object divide(VirtualFrame frame, Node inliningTarget, Object left, Object right, + @Cached ToDecimalNode toLeft, + @Cached ToDecimalNode toRight, + @Cached InlinedConditionProfile notImplementedProfile, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode) { + PDecimal a = toLeft.execute(inliningTarget, left); + PDecimal b = toRight.execute(inliningTarget, right); + if (notImplementedProfile.profile(inliningTarget, a == null || b == null)) { + return PNotImplemented.NOT_IMPLEMENTED; + } + PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + Result result; + try { + result = DecimalArithmetic.divide(a, b, context); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + PythonLanguage language = PythonLanguage.get(inliningTarget); + return finishResult(frame, language, context, result, raiseSignal); + } + } + + @GenerateInline + @GenerateCached(false) + public abstract static class DivideIntDecimalNode extends Node { + public abstract Object execute(VirtualFrame frame, Node inliningTarget, Object left, Object right); + + @Specialization + static Object divideInt(VirtualFrame frame, Node inliningTarget, Object left, Object right, + @Cached ToDecimalNode toLeft, + @Cached ToDecimalNode toRight, + @Cached InlinedConditionProfile notImplementedProfile, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode) { + PDecimal a = toLeft.execute(inliningTarget, left); + PDecimal b = toRight.execute(inliningTarget, right); + if (notImplementedProfile.profile(inliningTarget, a == null || b == null)) { + return PNotImplemented.NOT_IMPLEMENTED; + } + PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + Result result; + try { + result = DecimalArithmetic.divideInt(a, b, context); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + PythonLanguage language = PythonLanguage.get(inliningTarget); + return finishResult(frame, language, context, result, raiseSignal); + } + } + + @GenerateInline + @GenerateCached(false) + public abstract static class RemainderDecimalNode extends Node { + public abstract Object execute(VirtualFrame frame, Node inliningTarget, Object left, Object right); + + @Specialization + static Object remainder(VirtualFrame frame, Node inliningTarget, Object left, Object right, + @Cached ToDecimalNode toLeft, + @Cached ToDecimalNode toRight, + @Cached InlinedConditionProfile notImplementedProfile, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode) { + PDecimal a = toLeft.execute(inliningTarget, left); + PDecimal b = toRight.execute(inliningTarget, right); + if (notImplementedProfile.profile(inliningTarget, a == null || b == null)) { + return PNotImplemented.NOT_IMPLEMENTED; + } + PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + Result result; + try { + result = DecimalArithmetic.remainder(a, b, context); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + PythonLanguage language = PythonLanguage.get(inliningTarget); + return finishResult(frame, language, context, result, raiseSignal); + } + } + + @GenerateInline + @GenerateCached(false) + public abstract static class DivmodDecimalNode extends Node { + public abstract Object execute(VirtualFrame frame, Node inliningTarget, Object left, Object right); + + @Specialization + static Object divmod(VirtualFrame frame, Node inliningTarget, Object left, Object right, + @Cached ToDecimalNode toLeft, + @Cached ToDecimalNode toRight, + @Cached InlinedConditionProfile notImplementedProfile, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode) { + PDecimal a = toLeft.execute(inliningTarget, left); + PDecimal b = toRight.execute(inliningTarget, right); + if (notImplementedProfile.profile(inliningTarget, a == null || b == null)) { + return PNotImplemented.NOT_IMPLEMENTED; + } + PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + ResultWithRemainder result; + try { + result = DecimalArithmetic.divmod(a, b, context); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, context, result.status(), raiseSignal); + PythonLanguage language = PythonLanguage.get(inliningTarget); + PDecimal value = create(language, result.value()); + PDecimal remainder = create(language, result.remainder()); + return PFactory.createTuple(language, new Object[]{value, remainder}); + } + } + + private static PDecimal finishResult(VirtualFrame frame, PythonLanguage language, PDecimalContext context, Result result, RaiseSignalNode raiseSignal) { + DecimalContextUtils.addStatus(frame, context, result.status(), raiseSignal); + return create(language, result.value()); + } + + private static boolean isCompactFinite(PDecimal value) { + return value.getKind() == Kind.FINITE && value.isCompact(); + } + + private static PDecimal compactUnary(PythonLanguage language, PDecimal self, PDecimalContext context, boolean negative) { + if (!isCompactFinite(self) || !DecimalArithmetic.fitsContext(self.getCompactCoefficient() == 0, self.getExponent(), DecimalArithmetic.digits(self.getCompactCoefficient()), context)) { + return null; + } + if (self.getCompactCoefficient() == 0 && context.getRounding() != DecimalRounding.ROUND_FLOOR) { + negative = false; + } + return compact(language, negative, self.getCompactCoefficient(), self.getExponent()); + } + + /** Scale a nonnegative coefficient, or return -1 if it no longer fits a signed long. */ + static long scaleCompactCoefficient(long coefficient, long shift) { + assert coefficient >= 0 && shift >= 0; + if (coefficient == 0) { + return 0; + } + // Even a one-digit coefficient cannot fit after a shift of 19 decimal places. + if (shift > 18) { + return -1; + } + for (int i = 0; i < shift; i++) { + if (coefficient > Long.MAX_VALUE / 10) { + return -1; + } + coefficient *= 10; + } + return coefficient; + } + + private static PDecimal compactAdd(PythonLanguage language, PDecimal a, PDecimal b, PDecimalContext context, boolean rightNegative) { + if (!isCompactFinite(a) || !isCompactFinite(b)) { + return null; + } + // Preserve the finer quantum by scaling the operand with the larger exponent. + long left = a.getCompactCoefficient(); + long right = b.getCompactCoefficient(); + long exponent = a.getExponent(); + if (exponent > b.getExponent()) { + left = scaleCompactCoefficient(left, exponent - b.getExponent()); + exponent = b.getExponent(); + } else if (exponent < b.getExponent()) { + right = scaleCompactCoefficient(right, b.getExponent() - exponent); + } + if (left < 0 || right < 0) { + return null; + } + if (a.isNegative()) { + left = -left; + } + if (rightNegative) { + right = -right; + } + long sum; + try { + sum = Math.addExact(left, right); + } catch (ArithmeticException e) { + return null; + } + if (sum == Long.MIN_VALUE) { + return null; + } + long coefficient = Math.abs(sum); + boolean negative; + if (sum == 0) { + if (a.isNegative() == rightNegative) { + negative = a.isNegative(); + } else { + negative = context.getRounding() == DecimalRounding.ROUND_FLOOR; + } + } else { + negative = sum < 0; + } + if (DecimalArithmetic.fitsContext(coefficient == 0, exponent, DecimalArithmetic.digits(coefficient), context)) { + return compact(language, negative, coefficient, exponent); + } + return null; + } + + private static PDecimal compactMultiply(PythonLanguage language, PDecimal a, PDecimal b, PDecimalContext context) { + if (!isCompactFinite(a) || !isCompactFinite(b)) { + return null; + } + long coefficient; + try { + coefficient = Math.multiplyExact(a.getCompactCoefficient(), b.getCompactCoefficient()); + } catch (ArithmeticException e) { + return null; + } + long exponent = a.getExponent() + b.getExponent(); + boolean negative = a.isNegative() ^ b.isNegative(); + if (DecimalArithmetic.fitsContext(coefficient == 0, exponent, DecimalArithmetic.digits(coefficient), context)) { + return compact(language, negative, coefficient, exponent); + } + return null; + } + @GenerateInline(false) // Error path @GenerateUncached public abstract static class RaiseSignalNode extends Node { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalPowerMath.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalPowerMath.java new file mode 100644 index 0000000000..129a895ef8 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalPowerMath.java @@ -0,0 +1,219 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import static com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.digits; +import static com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.pow10; + +import java.math.BigInteger; + +import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.Value; +import com.oracle.graal.python.builtins.objects.decimal.PDecimal.Kind; + +/** + * Fixed-point logarithm and exponential algorithms from CPython's _pydecimal. Their integer + * approximations have explicit error bounds, allowing power to increase precision until the + * result is unambiguously roundable, including values close to a rounding boundary. + */ +final class DecimalPowerMath { + private static final BigInteger HUNDRED = BigInteger.valueOf(100); + private static final BigInteger THOUSAND = BigInteger.valueOf(1000); + private static String logTenDigits = "23025850929940456840179914546843642076011014886"; + + private DecimalPowerMath() { + } + + private static BigInteger[] floorDivmod(BigInteger a, BigInteger b) { + BigInteger[] qr = a.divideAndRemainder(b); + if (qr[1].signum() < 0) { + qr[0] = qr[0].subtract(BigInteger.ONE); + qr[1] = qr[1].add(b); + } + return qr; + } + + private static BigInteger nearestDiv(BigInteger a, BigInteger b) { + BigInteger[] qr = floorDivmod(a, b); + int half = qr[1].shiftLeft(1).compareTo(b); + if (half > 0 || half == 0 && qr[0].testBit(0)) { + return qr[0].add(BigInteger.ONE); + } + return qr[0]; + } + + private static BigInteger nearestShift(BigInteger a, int shift) { + return nearestDiv(a, BigInteger.ONE.shiftLeft(shift)); + } + + private static BigInteger nearestSqrt(BigInteger n) { + BigInteger root = n.sqrt(); + if (n.subtract(root.multiply(root)).compareTo(root) > 0) { + return root.add(BigInteger.ONE); + } + return root; + } + + private static BigInteger integerLog(BigInteger x, BigInteger scale, long scaleDigits) { + BigInteger y = x.subtract(scale); + int reductions = 0; + // L=8: after reduction the log1p argument has magnitude less than 2^-8. + // The resulting error is at most 22 scale units for 0.1 <= x/scale <= 10. + while (true) { + BigInteger magnitude; + if (reductions <= 8) { + magnitude = y.abs().shiftLeft(8 - reductions); + } else { + magnitude = y.abs().shiftRight(reductions - 8); + } + if (magnitude.compareTo(scale) < 0) { + break; + } + y = nearestDiv(scale.multiply(y).shiftLeft(1), scale.add(nearestSqrt(scale.multiply(scale.add(nearestShift(y, reductions)))))); + reductions++; + } + int terms = Math.toIntExact((10 * scaleDigits + 23) / 24); + BigInteger reduced = nearestShift(y, reductions); + BigInteger w = nearestDiv(scale, BigInteger.valueOf(terms)); + for (int k = terms - 1; k > 0; k--) { + w = nearestDiv(scale, BigInteger.valueOf(k)).subtract(nearestDiv(reduced.multiply(w), scale)); + } + return nearestDiv(w.multiply(y), scale); + } + + private static BigInteger logTen(long places) { + int p = Math.toIntExact(places); + String logTenDigits = DecimalPowerMath.logTenDigits; + if (p >= logTenDigits.length()) { + for (int extra = 3;; extra += 3) { + long scaleExponent = (long) p + extra + 2; + BigInteger scale = pow10(scaleExponent); + String approximation = nearestDiv(integerLog(scale.multiply(BigInteger.TEN), scale, scaleExponent + 1), HUNDRED).toString(); + if (!approximation.endsWith("0".repeat(extra))) { + int end = approximation.length(); + while (approximation.charAt(end - 1) == '0') { + end--; + } + logTenDigits = approximation.substring(0, end - 1); + DecimalPowerMath.logTenDigits = logTenDigits; + break; + } + } + } + return new BigInteger(logTenDigits.substring(0, p + 1)); + } + + /** Approximate 10^places * ln(c * 10^e), with absolute error at most one. */ + private static BigInteger decimalLog(BigInteger c, long e, long places) { + long p = places + 2; + long f = e + digits(c); + if (f >= 1) { + f--; + } + BigInteger logSignificand = BigInteger.ZERO; + if (p > 0) { + long shift = e + p - f; + BigInteger scaled; + if (shift >= 0) { + scaled = c.multiply(pow10(shift)); + } else { + scaled = nearestDiv(c, pow10(-shift)); + } + logSignificand = integerLog(scaled, pow10(p), p + 1); + } + BigInteger logPower = BigInteger.ZERO; + if (f != 0) { + int extra = digits(Math.abs(f)) - 1; + if (p + extra >= 0) { + logPower = nearestDiv(BigInteger.valueOf(f).multiply(logTen(p + extra)), pow10(extra)); + } + } + return nearestDiv(logPower.add(logSignificand), HUNDRED); + } + + private static BigInteger integerExp(BigInteger x, BigInteger scale, long scaleDigits) { + // x/scale is between 0 and ln(10). With L=8 the absolute error is at most 60. + int reductions = x.shiftLeft(8).divide(scale).bitLength(); + int terms = Math.toIntExact((10 * scaleDigits + 23) / 24); + BigInteger y = nearestDiv(x, BigInteger.valueOf(terms)); + BigInteger shiftedScale = scale.shiftLeft(reductions); + for (int i = terms - 1; i > 0; i--) { + y = nearestDiv(x.multiply(shiftedScale.add(y)), shiftedScale.multiply(BigInteger.valueOf(i))); + } + for (int k = reductions - 1; k >= 0; k--) { + shiftedScale = scale.shiftLeft(k + 2); + y = nearestDiv(y.multiply(y.add(shiftedScale)), shiftedScale); + } + return scale.add(y); + } + + private static Value decimalExp(BigInteger c, long e, long precision) { + long p = precision + 2; + long extra = Math.max(0, e + digits(c) - 1); + long q = p + extra; + long shift = e + q; + BigInteger scaled; + if (shift >= 0) { + scaled = c.multiply(pow10(shift)); + } else { + scaled = floorDivmod(c, pow10(-shift))[0]; + } + BigInteger[] qr = floorDivmod(scaled, logTen(q)); + BigInteger remainder = nearestDiv(qr[1], pow10(extra)); + // Beyond the implementation's decimal exponent limits only the direction matters. + // Keep a finite sentinel so finalization can apply the user's overflow rounding mode. + BigInteger limit = BigInteger.valueOf(4 * DecimalUtils.MAX_EMAX); + long exponent = qr[0].max(limit.negate()).min(limit).longValueExact(); + return new Value(false, nearestDiv(integerExp(remainder, pow10(p), p + 1), THOUSAND), exponent - p + 3, Kind.FINITE); + } + + static Value logarithm(BigInteger coefficient, long exponent, long places) { + BigInteger approximation = decimalLog(coefficient, exponent, places); + return new Value(approximation.signum() < 0, approximation.abs(), -places, Kind.FINITE); + } + + static Value exponential(Value argument, long precision) { + BigInteger coefficient = argument.coefficient(); + if (argument.negative()) { + coefficient = coefficient.negate(); + } + return decimalExp(coefficient, argument.exponent(), precision); + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalRounding.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalRounding.java new file mode 100644 index 0000000000..36a9708667 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalRounding.java @@ -0,0 +1,67 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import static com.oracle.graal.python.util.PythonUtils.tsLiteral; + +import com.oracle.truffle.api.strings.TruffleString; + +/** Internal rounding modes and their names in the Python decimal API. */ +public enum DecimalRounding { + ROUND_CEILING, + ROUND_FLOOR, + ROUND_UP, + ROUND_DOWN, + ROUND_HALF_UP, + ROUND_HALF_DOWN, + ROUND_HALF_EVEN, + ROUND_05UP; + + private final TruffleString pythonName; + + DecimalRounding() { + pythonName = tsLiteral(name()); + } + + public TruffleString getPythonName() { + return pythonName; + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java index 615f3cb616..82faef90b8 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java @@ -62,9 +62,51 @@ public final class DecimalUtils { // precision. The precision - 1 offset accounts for the remaining coefficient digits. public static final long MIN_ETINY = MIN_EMIN - (MAX_PREC - 1); + // Bound retained storage independently of the user's precision and exponent limits. + private static final BigInteger[] SMALL_POWERS_OF_TEN = createSmallPowersOfTen(); + private DecimalUtils() { } + private static BigInteger[] createSmallPowersOfTen() { + BigInteger[] powers = new BigInteger[128]; + powers[0] = BigInteger.ONE; + for (int i = 1; i < powers.length; i++) { + powers[i] = powers[i - 1].multiply(BigInteger.TEN); + } + return powers; + } + + static BigInteger pow10(long exponent) { + if (exponent >= 0 && exponent < SMALL_POWERS_OF_TEN.length) { + return SMALL_POWERS_OF_TEN[(int) exponent]; + } + // Java arrays (and BigInteger itself) have int-sized lengths. Check before narrowing. + return BigInteger.TEN.pow(Math.toIntExact(exponent)); + } + + static int digits(long coefficient) { + assert coefficient >= 0; + int count = 1; + for (long c = coefficient; c >= 10; c /= 10) { + count++; + } + return count; + } + + static int digits(BigInteger value) { + BigInteger magnitude = value.abs(); + int bits = magnitude.bitLength(); + if (bits < Long.SIZE) { + return digits(magnitude.longValue()); + } + // 646456993 / 2^31 is a lower approximation of log10(2). For every int-sized + // bit length its error, plus the width of the bit-length interval, is less than one + // decimal digit. The magnitude therefore has either estimate or estimate + 1 digits. + int estimate = (int) (((bits - 1L) * 646456993L) >>> 31) + 1; + return magnitude.compareTo(pow10(estimate)) >= 0 ? estimate + 1 : estimate; + } + // Special values have an unused exponent of zero. A zero NaN coefficient means no // diagnostic payload; infinity always has a zero coefficient. record Value(boolean negative, BigInteger coefficient, long exponent, Kind kind) { @@ -169,7 +211,13 @@ static Value parse(String input) throws ConversionException { if (pos != text.length()) { throw new ConversionException(DecimalExceptionTypes.ConversionSyntax); } - return new Value(negative, coefficient.isEmpty() ? BigInteger.ZERO : new BigInteger(coefficient.toString()), 0, kind); + BigInteger integer; + if (coefficient.isEmpty()) { + integer = BigInteger.ZERO; + } else { + integer = new BigInteger(coefficient.toString()); + } + return new Value(negative, integer, 0, kind); } if (coefficient.isEmpty()) { throw new ConversionException(DecimalExceptionTypes.ConversionSyntax); @@ -208,7 +256,7 @@ static Value parse(String input) throws ConversionException { static Value finite(boolean negative, BigInteger coefficient, long exponent) throws ConversionException { // The adjusted exponent locates the most significant digit: exponent + digits - 1. // Both the stored exponent and the adjusted exponent must fit libmpdec's limits. - if (exponent < MIN_ETINY || exponent > MAX_EMAX || exponent + coefficient.toString().length() - 1 > MAX_EMAX) { + if (exponent < MIN_ETINY || exponent > MAX_EMAX || exponent + digits(coefficient) - 1 > MAX_EMAX) { throw new ConversionException(DecimalExceptionTypes.InvalidOperation); } return new Value(negative, coefficient, exponent, Kind.FINITE); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java index 7034f6d3f9..d84050a216 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java @@ -147,7 +147,12 @@ public String toScientificString(boolean capitals) { if (kind == Kind.INFINITY) { return sign + "Infinity"; } - String digits = isCompact() ? Long.toString(compactCoefficient) : largeCoefficient.toString(); + String digits; + if (isCompact()) { + digits = Long.toString(compactCoefficient); + } else { + digits = largeCoefficient.toString(); + } if (kind != Kind.FINITE) { return sign + (kind == Kind.NAN ? "NaN" : "sNaN") + (digits.equals("0") ? "" : digits); } @@ -167,7 +172,10 @@ public String toScientificString(boolean capitals) { return sign + digits; } // Scientific notation puts exactly one coefficient digit before the point. - String significand = digits.length() == 1 ? digits : digits.charAt(0) + "." + digits.substring(1); + String significand = digits; + if (digits.length() != 1) { + significand = digits.charAt(0) + "." + digits.substring(1); + } return sign + significand + (capitals ? "E" : "e") + (adjustedExponent >= 0 ? "+" : "") + adjustedExponent; } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContext.java index 22e716767b..dca8e0f506 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimalContext.java @@ -40,18 +40,17 @@ */ package com.oracle.graal.python.builtins.objects.decimal; -import static com.oracle.graal.python.builtins.modules.DecimalModuleBuiltins.T_ROUND_HALF_EVEN; +import static com.oracle.graal.python.builtins.objects.decimal.DecimalRounding.ROUND_HALF_EVEN; import com.oracle.graal.python.builtins.objects.object.PythonBuiltinObject; import com.oracle.truffle.api.object.Shape; -import com.oracle.truffle.api.strings.TruffleString; public final class PDecimalContext extends PythonBuiltinObject { private long precision = 28; private long emin = -999999; private long emax = 999999; - private TruffleString rounding = T_ROUND_HALF_EVEN; - private int capitals = 1; + private DecimalRounding rounding = ROUND_HALF_EVEN; + private boolean capitals = true; private int clamp; private int flags; private int traps = DecimalContextUtils.DEFAULT_TRAPS; @@ -86,19 +85,19 @@ public void setEmax(long value) { emax = value; } - public TruffleString getRounding() { + public DecimalRounding getRounding() { return rounding; } - public void setRounding(TruffleString value) { + public void setRounding(DecimalRounding value) { rounding = value; } - public int getCapitals() { + public boolean getCapitals() { return capitals; } - public void setCapitals(int value) { + public void setCapitals(boolean value) { capitals = value; } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java index b0012caa86..677f28797b 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java @@ -1625,6 +1625,9 @@ public abstract class ErrorMessages { "valid values for rounding are:\n [ROUND_CEILING, ROUND_FLOOR, ROUND_UP, ROUND_DOWN,\n ROUND_HALF_UP, ROUND_HALF_DOWN, ROUND_HALF_EVEN,\n ROUND_05UP]"); public static final TruffleString DECIMAL_INVALID_SIGNALS = tsLiteral( "valid values for signals are:\n [InvalidOperation, FloatOperation, DivisionByZero,\n Overflow, Underflow, Subnormal, Inexact, Rounded,\n Clamped]"); + public static final TruffleString DECIMAL_INFINITY_TO_INTEGER = tsLiteral("cannot convert Infinity to integer"); + public static final TruffleString DECIMAL_NAN_TO_INTEGER = tsLiteral("cannot convert NaN to integer"); + public static final TruffleString DECIMAL_SNAN_TO_FLOAT = tsLiteral("cannot convert signaling NaN to float"); public static final TruffleString DECIMAL_INVALID_LOCALCONTEXT_KEYWORD = tsLiteral("'%s' is an invalid keyword argument for this function"); // re module errors diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java index 38ad726c71..06494a5bcf 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/object/PFactory.java @@ -394,18 +394,34 @@ public static PTupleGetter createTupleGetter(Object cls, Shape shape, int index, return new PTupleGetter(cls, shape, index, doc); } + public static PDecimal createDecimal(PythonLanguage language, long value) { + return createDecimal(PythonBuiltinClassType.PDecimal, PythonBuiltinClassType.PDecimal.getInstanceShape(language), value); + } + public static PDecimal createDecimal(Object cls, Shape shape, long value) { return new PDecimal(cls, shape, value); } + public static PDecimal createDecimal(PythonLanguage language, boolean negative, long coefficient, long exponent, PDecimal.Kind kind) { + return createDecimal(PythonBuiltinClassType.PDecimal, PythonBuiltinClassType.PDecimal.getInstanceShape(language), negative, coefficient, exponent, kind); + } + public static PDecimal createDecimal(Object cls, Shape shape, boolean negative, long coefficient, long exponent, PDecimal.Kind kind) { return new PDecimal(cls, shape, negative, coefficient, exponent, kind); } + public static PDecimal createDecimal(PythonLanguage language, boolean negative, BigInteger coefficient, long exponent, PDecimal.Kind kind) { + return createDecimal(PythonBuiltinClassType.PDecimal, PythonBuiltinClassType.PDecimal.getInstanceShape(language), negative, coefficient, exponent, kind); + } + public static PDecimal createDecimal(Object cls, Shape shape, boolean negative, BigInteger coefficient, long exponent, PDecimal.Kind kind) { return new PDecimal(cls, shape, negative, coefficient, exponent, kind); } + public static PDecimal createDecimal(PythonLanguage language, PDecimal value) { + return createDecimal(PythonBuiltinClassType.PDecimal, PythonBuiltinClassType.PDecimal.getInstanceShape(language), value); + } + public static PDecimal createDecimal(Object cls, Shape shape, PDecimal value) { return new PDecimal(cls, shape, value); } From c6dbbd31111952c2d856b3c69703bf11f16252f4 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Fri, 9 Oct 2026 11:58:43 +0200 Subject: [PATCH 06/31] Implement more slots and methods --- .../modules/DecimalModuleBuiltins.java | 17 +- .../objects/decimal/DecimalArithmetic.java | 142 +++++++ .../objects/decimal/DecimalBuiltins.java | 396 ++++++++++++++---- .../objects/decimal/DecimalFormatting.java | 358 ++++++++++++++++ .../objects/decimal/DecimalModuleState.java | 10 + .../objects/decimal/DecimalNodes.java | 208 ++++++++- .../builtins/objects/decimal/PDecimal.java | 11 + .../graal/python/nodes/ErrorMessages.java | 9 + 8 files changed, 1071 insertions(+), 80 deletions(-) create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalFormatting.java diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java index ed503182fb..5bdd13097e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java @@ -71,6 +71,7 @@ import com.oracle.graal.python.builtins.objects.module.PythonModule; import com.oracle.graal.python.builtins.objects.type.PythonAbstractClass; import com.oracle.graal.python.builtins.objects.type.PythonClass; +import com.oracle.graal.python.lib.PyObjectGetAttr; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.call.CallNode; @@ -89,6 +90,12 @@ @CoreFunctions(defineModule = "_decimal") public final class DecimalModuleBuiltins extends PythonBuiltins { + private static final TruffleString T_NUMBERS = tsLiteral("numbers"); + private static final TruffleString T_NUMBER = tsLiteral("Number"); + private static final TruffleString T_RATIONAL = tsLiteral("Rational"); + private static final TruffleString T_REGISTER = tsLiteral("register"); + private static final TruffleString T_VERSION = tsLiteral("1.70"); + private static final TruffleString T_LIBMPDEC_VERSION = tsLiteral("2.5.1"); private static final TruffleString T_DECIMAL_TUPLE = tsLiteral("DecimalTuple"); private static final TruffleString T_DECIMAL_TUPLE_FIELDS = tsLiteral("sign digits exponent"); private static final TruffleString T_SIGNAL_DICT = tsLiteral("SignalDict"); @@ -124,8 +131,8 @@ public DecimalModuleBuiltins() { addBuiltinConstant("HAVE_CONTEXTVAR", true); // Even _pydecimal exports these constants, despite not being based on libmpdec, so we should do that too - addBuiltinConstant("__version__", tsLiteral("1.70")); - addBuiltinConstant("__libmpdec_version__", tsLiteral("2.5.1")); + addBuiltinConstant("__version__", T_VERSION); + addBuiltinConstant("__libmpdec_version__", T_LIBMPDEC_VERSION); } @Override @@ -172,6 +179,12 @@ public void postInitialize(Python3Core core) { module.setAttribute(T_DEFAULT_CONTEXT, state.getDefaultContext()); module.setAttribute(T_BASIC_CONTEXT, state.getBasicContext()); module.setAttribute(T_EXTENDED_CONTEXT, state.getExtendedContext()); + + PythonModule numbers = AbstractImportNode.importModule(T_NUMBERS); + state.setRationalType(numbers.getAttribute(T_RATIONAL)); + Object register = PyObjectGetAttr.executeUncached(numbers.getAttribute(T_NUMBER), T_REGISTER); + CallNode.executeUncached(register, PythonBuiltinClassType.PDecimal); + } @Builtin(name = "getcontext", minNumOfPositionalArgs = 0) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java index 9887b5c469..8ef92fd3ba 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java @@ -818,6 +818,148 @@ private static long logExponentBound(Value a) { return a.exponent() + digits(difference) - 1 - (adjusted == 0 ? 2 : 1); } + /** Context-free rescaling used by integer conversions and formatting. */ + @TruffleBoundary + static Value rescale(Value operand, long exponent, DecimalRounding rounding) { + BigInteger coefficient = operand.coefficient(); + long shift = operand.exponent() - exponent; + if (coefficient.signum() == 0) { + return finite(operand.negative(), coefficient, exponent); + } + if (shift >= 0) { + return finite(operand.negative(), coefficient.multiply(pow10(shift)), exponent); + } + BigInteger quotient; + BigInteger remainder; + BigInteger divisor; + if (-shift > digits(coefficient)) { + // Preserve the direction of rounding without allocating 10**(-shift). + quotient = BigInteger.ZERO; + remainder = BigInteger.ONE; + divisor = BigInteger.TEN; + } else { + divisor = pow10(-shift); + BigInteger[] parts = coefficient.divideAndRemainder(divisor); + quotient = parts[0]; + remainder = parts[1]; + } + DecimalArithmetic work = new DecimalArithmetic(1, 0, 0, rounding, 0); + if (work.increment(operand.negative(), quotient, remainder, divisor)) { + quotient = quotient.add(BigInteger.ONE); + } + return finite(operand.negative(), quotient, exponent); + } + + @TruffleBoundary + static BigInteger toInteger(PDecimal decimal, DecimalRounding rounding) { + if (decimal.getExponent() >= 0) { + return toInteger(decimal); + } + Value rounded = rescale(value(decimal), 0, rounding); + if (rounded.negative()) { + return rounded.coefficient().negate(); + } + return rounded.coefficient(); + } + + @TruffleBoundary + static Result quantize(PDecimal decimal, long exponent, PDecimalContext context, DecimalRounding rounding) { + DecimalArithmetic work = new DecimalArithmetic(context.getPrecision(), context.getEmin(), context.getEmax(), rounding, context.getClamp()); + Value operand = value(decimal); + Value result = work.checkNaNs(operand); + if (result == null) { + result = work.quantize(operand, exponent); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result quantize(PDecimal decimal, PDecimal quantum, PDecimalContext context, DecimalRounding rounding) { + DecimalArithmetic work = new DecimalArithmetic(context.getPrecision(), context.getEmin(), context.getEmax(), rounding, context.getClamp()); + Value operand = value(decimal); + Value exp = value(quantum); + Value result = work.checkNaNs(operand, exp); + if (result == null) { + if (operand.kind() == Kind.INFINITY && exp.kind() == Kind.INFINITY) { + result = operand; + } else if (exp.kind() == Kind.INFINITY) { + result = work.invalid(INVALID); + } else { + result = work.quantize(operand, exp.exponent()); + } + } + return new Result(result, work.status); + } + + private Value quantize(Value operand, long exponent) { + long etiny = emin - precision + 1; + if (operand.kind() == Kind.INFINITY || exponent < etiny || exponent > emax || + !isZero(operand) && (adjusted(operand) > emax || adjusted(operand) - exponent + 1 > precision)) { + return invalid(INVALID); + } + Value result = rescale(operand, exponent, rounding); + if (digits(result.coefficient()) > precision || adjusted(result) > emax) { + return invalid(INVALID); + } + if (!isZero(operand) && exponent > operand.exponent()) { + status |= ROUNDED; + if (compare(operand, result) != 0) { + status |= INEXACT; + } + } + if (!isZero(result) && adjusted(result) < emin) { + status |= SUBNORMAL; + } + return finish(result); + } + + /** Compare non-NaN values without constructing powers for widely separated exponents. */ + @TruffleBoundary + static int compare(Value a, Value b) { + boolean azero = isZero(a); + boolean bzero = isZero(b); + if (azero && bzero) { + return 0; + } + if (a.negative() != b.negative()) { + return a.negative() ? -1 : 1; + } + int magnitude; + if (a.kind() == Kind.INFINITY || b.kind() == Kind.INFINITY) { + if (a.kind() == b.kind()) { + magnitude = 0; + } else if (a.kind() == Kind.INFINITY) { + magnitude = 1; + } else { + magnitude = -1; + } + } else if (azero || bzero) { + magnitude = azero ? -1 : 1; + } else { + magnitude = Long.compare(adjusted(a), adjusted(b)); + if (magnitude == 0) { + String adigits = a.coefficient().toString(); + String bdigits = b.coefficient().toString(); + int length = Math.max(adigits.length(), bdigits.length()); + for (int i = 0; i < length; i++) { + char ac = '0'; + if (i < adigits.length()) { + ac = adigits.charAt(i); + } + char bc = '0'; + if (i < bdigits.length()) { + bc = bdigits.charAt(i); + } + if (ac != bc) { + magnitude = Character.compare(ac, bc); + break; + } + } + } + } + return a.negative() ? -magnitude : magnitude; + } + @TruffleBoundary static BigInteger toInteger(PDecimal decimal) { BigInteger coefficient = decimal.getCoefficient(); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java index 9b91b87e15..22fca955f8 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java @@ -40,12 +40,14 @@ */ package com.oracle.graal.python.builtins.objects.decimal; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.DeprecationWarning; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.MemoryError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.OverflowError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; +import static com.oracle.graal.python.util.PythonUtils.tsLiteral; import java.math.BigInteger; import java.util.List; @@ -58,9 +60,13 @@ import com.oracle.graal.python.builtins.CoreFunctions; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.builtins.modules.SysModuleBuiltins; +import com.oracle.graal.python.builtins.modules.WarningsModuleBuiltins.WarnNode; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.PNotImplemented; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; +import com.oracle.graal.python.builtins.objects.complex.ComplexBuiltins.ComplexValue; +import com.oracle.graal.python.builtins.objects.complex.ComplexBuiltins.ToComplexValueNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.AbsDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.AddDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.DivideDecimalNode; @@ -70,6 +76,7 @@ import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.MinusDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.MultiplyDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.PlusDecimalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.QuantizeDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.RaiseSignalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.RemainderDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.SubtractDecimalNode; @@ -80,18 +87,21 @@ import com.oracle.graal.python.builtins.objects.floats.PFloat; import com.oracle.graal.python.builtins.objects.function.PKeyword; import com.oracle.graal.python.builtins.objects.ints.PInt; +import com.oracle.graal.python.builtins.objects.object.ObjectBuiltins; import com.oracle.graal.python.builtins.objects.type.TpSlots; import com.oracle.graal.python.builtins.objects.type.TypeNodes; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotBinaryOp.BinaryOpBuiltinNode; -import com.oracle.graal.python.builtins.objects.type.slots.TpSlotGetAttr.GetAttrBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotHashFun.HashBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotInquiry.NbBoolBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotRichCompare.RichCmpBuiltinNode; +import com.oracle.graal.python.lib.PyComplexCheckNode; import com.oracle.graal.python.lib.PyFloatAsDoubleNode; import com.oracle.graal.python.lib.PyFloatCheckNode; import com.oracle.graal.python.lib.PyListCheckNode; import com.oracle.graal.python.lib.PyLongAsLongNode; import com.oracle.graal.python.lib.PyLongCheckNode; +import com.oracle.graal.python.lib.PyObjectGetAttr; +import com.oracle.graal.python.lib.PyObjectIsInstanceNode; import com.oracle.graal.python.lib.PyTupleCheckNode; import com.oracle.graal.python.lib.PyUnicodeCheckNode; import com.oracle.graal.python.lib.RichCmpOp; @@ -134,6 +144,8 @@ @CoreFunctions(extendClasses = PythonBuiltinClassType.PDecimal) public final class DecimalBuiltins extends PythonBuiltins { public static final TpSlots SLOTS = DecimalBuiltinsSlotsGen.SLOTS; + private static final TruffleString T_NUMERATOR = tsLiteral("numerator"); + private static final TruffleString T_DENOMINATOR = tsLiteral("denominator"); @Override protected List> getNodeFactories() { @@ -344,15 +356,40 @@ static Object doMock(VirtualFrame frame, Object self, Object other, Object conte } } - @Builtin(name = "quantize", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "exp", "rounding", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "quantize", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "exp", "rounding", "context"}) @GenerateNodeFactory public abstract static class QuantizeNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object exp, Object rounding, Object context, PKeyword[] kwargs, + static PDecimal quantize(VirtualFrame frame, PDecimal self, Object exp, Object rounding, Object context, @Bind Node inliningTarget, + @Cached GetContextNode getContext, + @Cached ToDecimalNode toDecimal, + @Cached QuantizeDecimalNode operation, + @Cached RaiseSignalNode raiseSignal, + @Cached GetClassNode getClass, + @Cached TypeNodes.GetNameNode getName, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.quantize. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + DecimalRounding actualRounding = actualContext.getRounding(); + if (!(rounding instanceof PNone)) { + actualRounding = DecimalContextUtils.validateRounding(inliningTarget, rounding); + } + PDecimal quantum = toDecimal.execute(inliningTarget, exp); + if (quantum == null) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.CONVERSION_FROM_S_TO_DECIMAL_NOT_SUPPORTED, + getName.execute(inliningTarget, getClass.execute(inliningTarget, exp))); + } + if (quantum.getKind() == Kind.FINITE) { + return operation.execute(frame, inliningTarget, self, quantum.getExponent(), actualContext, actualRounding); + } + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.quantize(self, quantum, actualContext, actualRounding); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } @@ -776,15 +813,34 @@ static Object doMock(VirtualFrame frame, Object self, } } + private static Value decimalValue(PDecimal self) { + return new Value(self.isNegative(), self.getCoefficient(), self.getExponent(), self.getKind()); + } + + private static Object toInteger(Node inliningTarget, PDecimal self, DecimalRounding rounding, PRaiseNode raiseNode) { + if (self.getKind() == Kind.INFINITY) { + throw raiseNode.raise(inliningTarget, OverflowError, ErrorMessages.DECIMAL_INFINITY_TO_INTEGER); + } + if (self.getKind() != Kind.FINITE) { + throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.DECIMAL_NAN_TO_INTEGER); + } + long compact = DecimalNodes.compactInteger(self, rounding); + if (compact != Long.MIN_VALUE) { + return compact; + } + try { + return PFactory.createInt(PythonLanguage.get(inliningTarget), DecimalArithmetic.toInteger(self, rounding)); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + } + @Builtin(name = "__copy__", minNumOfPositionalArgs = 1) @GenerateNodeFactory public abstract static class CopyNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.__copy__. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static PDecimal copy(PDecimal self) { + return self; } } @@ -792,11 +848,8 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class DeepcopyNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.__deepcopy__. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static PDecimal copy(PDecimal self, @SuppressWarnings("unused") Object memo) { + return self; } } @@ -804,23 +857,46 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class FormatNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static Object format(VirtualFrame frame, PDecimal self, Object specifier, Object override, @Bind Node inliningTarget, + @Cached PyUnicodeCheckNode unicodeCheck, + @Cached CastToJavaStringNode asString, + @Cached TruffleString.FromJavaStringNode fromJavaString, + @Cached WarnNode warnNode, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.__format__. - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (!unicodeCheck.execute(inliningTarget, specifier)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_FORMAT_STRING); + } + String format = asString.execute(specifier); + if (endsWithN(format)) { + warnNode.warnEx(frame, DeprecationWarning, ErrorMessages.DECIMAL_FORMAT_N_DEPRECATED, 1); + } + PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + try { + return fromJavaString.execute(DecimalFormatting.format(self, format, override, context), TS_ENCODING); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } } + + @TruffleBoundary + private static boolean endsWithN(String format) { + return format.endsWith("N"); + } + } @Builtin(name = "__reduce__", minNumOfPositionalArgs = 1) @GenerateNodeFactory public abstract static class ReduceNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static Object reduce(PDecimal self, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.__reduce__. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetClassNode getClass, + @Cached TpStrNode str) { + PythonLanguage language = PythonLanguage.get(inliningTarget); + Object args = PFactory.createTuple(language, new Object[]{str.execute(null, self)}); + return PFactory.createTuple(language, new Object[]{getClass.execute(inliningTarget, self), args}); } } @@ -828,11 +904,27 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class RoundNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static Object round(VirtualFrame frame, PDecimal self, Object ndigits, @Bind Node inliningTarget, + @Cached PyLongCheckNode longCheck, + @Cached PyLongAsLongNode asLong, + @Cached QuantizeDecimalNode operation, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.__round__. - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (ndigits == PNone.NO_VALUE) { + return toInteger(inliningTarget, self, DecimalRounding.ROUND_HALF_EVEN, raiseNode); + } + if (!longCheck.execute(inliningTarget, ndigits)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_ROUND_DIGITS); + } + long digits = asLong.execute(frame, inliningTarget, ndigits); + long exponent; + if (digits == Long.MIN_VALUE) { + exponent = Long.MAX_VALUE; + } else { + exponent = -digits; + } + PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + return operation.execute(frame, inliningTarget, self, exponent, context, context.getRounding()); } } @@ -840,11 +932,10 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class CeilNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static Object round(PDecimal self, @Bind Node inliningTarget, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.__ceil__. - throw raiseNode.raise(inliningTarget, NotImplementedError); + return toInteger(inliningTarget, self, DecimalRounding.ROUND_CEILING, raiseNode); } } @@ -852,11 +943,10 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class FloorNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static Object round(PDecimal self, @Bind Node inliningTarget, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.__floor__. - throw raiseNode.raise(inliningTarget, NotImplementedError); + return toInteger(inliningTarget, self, DecimalRounding.ROUND_FLOOR, raiseNode); } } @@ -864,11 +954,9 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class TruncNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.__trunc__. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static Object trunc(PDecimal self, + @Cached NbIntNode integer) { + return integer.execute(null, self); } } @@ -876,11 +964,10 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class ComplexNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static Object complex(PDecimal self, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.__complex__. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached NbFloatNode floating) { + return PFactory.createComplex(PythonLanguage.get(inliningTarget), (double) floating.execute(null, self), 0.0); } } @@ -888,11 +975,20 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class SizeofNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static long size(PDecimal self, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.__sizeof__. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetClassNode getClass, + @Cached TypeNodes.GetBasicSizeNode getBasicSize) { + // Report coefficient storage in addition to the object's basic size. + return getBasicSize.execute(inliningTarget, getClass.execute(inliningTarget, self)) + coefficientSize(self); + } + + @TruffleBoundary + private static long coefficientSize(PDecimal self) { + if (self.isCompact()) { + return Long.BYTES; + } + return (self.getCoefficient().bitLength() + 31L) / 32 * Integer.BYTES; } } @@ -900,11 +996,8 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class RealNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.real. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static PDecimal real(PDecimal self) { + return self; } } @@ -912,11 +1005,9 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class ImagNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.imag. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static PDecimal imag(@SuppressWarnings("unused") PDecimal self, + @Bind PythonLanguage language) { + return PFactory.createDecimal(language, 0); } } @@ -1216,11 +1307,11 @@ private static String reprString(PDecimal self, boolean capitals) { @GenerateNodeFactory public abstract static class TpStrNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static TruffleString str(PDecimal self, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.tp_str. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached TruffleString.FromJavaStringNode fromJavaString) { + boolean capitals = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget).getCapitals(); + return fromJavaString.execute(self.toScientificString(capitals), TS_ENCODING); } } @@ -1313,39 +1404,190 @@ static double floating(PDecimal self, } } - @Slot(value = SlotKind.tp_getattro, isComplex = true) - @GenerateNodeFactory - public abstract static class TpGetattroNode extends GetAttrBuiltinNode { - @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.tp_getattro. - throw raiseNode.raise(inliningTarget, NotImplementedError); - } - } - @Slot(value = SlotKind.tp_hash, isComplex = true) @GenerateNodeFactory public abstract static class TpHashNode extends HashBuiltinNode { + // Inverse of 10 modulo 2**61 - 1, as in CPython's _dec_hash. + private static final long INVERSE_TEN_HASH = 2075258708292324556L; + @Specialization - static long doMock(VirtualFrame frame, Object self, + static long hash(PDecimal self, @Bind Node inliningTarget, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.tp_hash. - throw raiseNode.raise(inliningTarget, NotImplementedError); + long hash = self.getHash(); + if (hash != -1) { + return hash; + } + if (self.getKind() == Kind.SNAN) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_SNAN_HASH); + } + if (self.getKind() == Kind.NAN) { + hash = ObjectBuiltins.HashNode.hash(self); + } else if (self.getKind() == Kind.INFINITY) { + hash = SysModuleBuiltins.HASH_INF; + if (self.isNegative()) { + hash = -hash; + } + } else if (self.isCompact()) { + hash = hashCompact(self.getCompactCoefficient(), self.getExponent()); + if (self.isNegative()) { + hash = -hash; + } + } else { + hash = hashFinite(self); + } + hash = hash == -1 ? -2 : hash; + self.setHash(hash); + return hash; + } + + private static long hashCompact(long coefficient, long exponent) { + long hash = coefficient % SysModuleBuiltins.HASH_MODULUS; + if (hash == 0 || exponent == 0) { + return hash; + } + long base = 10; + if (exponent < 0) { + base = INVERSE_TEN_HASH; + exponent = -exponent; + } + while (exponent != 0) { + if ((exponent & 1) != 0) { + hash = multiplyHashModulus(hash, base); + } + exponent >>>= 1; + if (exponent != 0) { + base = multiplyHashModulus(base, base); + } + } + return hash; + } + + private static long multiplyHashModulus(long a, long b) { + // Fold the unsigned 128-bit product at bit 61 using 2**61 == 1 (mod p). + assert SysModuleBuiltins.HASH_BITS == 61; + long low = a * b; + long high = Math.multiplyHigh(a, b); + long reduced = (low & SysModuleBuiltins.HASH_MODULUS) + ((high << 3) | (low >>> 61)); + reduced = (reduced & SysModuleBuiltins.HASH_MODULUS) + (reduced >>> 61); + if (reduced == SysModuleBuiltins.HASH_MODULUS) { + return 0; + } + return reduced; + } + + @TruffleBoundary + private static long hashFinite(PDecimal self) { + BigInteger modulus = BigInteger.valueOf(SysModuleBuiltins.HASH_MODULUS); + BigInteger base = BigInteger.TEN; + if (self.getExponent() < 0) { + base = base.modInverse(modulus); + } + BigInteger power = base.modPow(BigInteger.valueOf(self.getExponent()).abs(), modulus); + long hash = self.getCoefficient().mod(modulus).multiply(power).mod(modulus).longValue(); + if (self.isNegative()) { + hash = -hash; + } + return hash == -1 ? -2 : hash; } } @Slot(value = SlotKind.tp_richcompare, isComplex = true) + @ImportStatic(DecimalNodes.class) @GenerateNodeFactory public abstract static class TpRichcompareNode extends RichCmpBuiltinNode { - @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, RichCmpOp op, + @Specialization(guards = {"isCompactFinite(self)", "isCompactFinite(other)"}) + static boolean compactDecimal(PDecimal self, PDecimal other, RichCmpOp op) { + return op.compareResultToBool(DecimalNodes.compareCompact(self.isNegative(), self.getCompactCoefficient(), self.getExponent(), + other.isNegative(), other.getCompactCoefficient(), other.getExponent())); + } + + @Specialization(guards = "isCompactFinite(self)") + static boolean compactInt(PDecimal self, int other, RichCmpOp op) { + return compareInteger(self, other, op); + } + + @Specialization(guards = {"isCompactFinite(self)", "fitsCompactLong(other)"}) + static boolean compactLong(PDecimal self, long other, RichCmpOp op) { + return compareInteger(self, other, op); + } + + private static boolean compareInteger(PDecimal self, long other, RichCmpOp op) { + return op.compareResultToBool(DecimalNodes.compareCompact(self.isNegative(), self.getCompactCoefficient(), self.getExponent(), + other < 0, Math.abs(other), 0)); + } + + static boolean fitsCompactLong(long value) { + return value != Long.MIN_VALUE; + } + + @Fallback + static Object compare(VirtualFrame frame, PDecimal self, Object other, RichCmpOp op, @Bind Node inliningTarget, + @Cached ToDecimalNode toDecimal, + @Cached PyFloatCheckNode floatCheck, + @Cached PyFloatAsDoubleNode asDouble, + @Cached PyComplexCheckNode complexCheck, + @Cached ToComplexValueNode asComplex, + @Cached PyObjectIsInstanceNode isInstance, + @Cached PyObjectGetAttr getAttr, + @Cached CastToJavaBigIntegerNode asInteger, + @Cached PyLongCheckNode longCheck, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.tp_richcompare. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + Value a = decimalValue(self); + Value b; + PDecimal converted = toDecimal.execute(inliningTarget, other); + if (converted != null) { + b = decimalValue(converted); + } else if (floatCheck.execute(inliningTarget, other)) { + floatOperation(frame, context, op, raiseSignal); + b = DecimalUtils.fromFloat(asDouble.execute(frame, inliningTarget, other)); + } else if (op.isEqOrNe() && complexCheck.execute(inliningTarget, other)) { + ComplexValue complex = asComplex.execute(inliningTarget, other); + if (complex.getImag() != 0.0) { + return PNotImplemented.NOT_IMPLEMENTED; + } + floatOperation(frame, context, op, raiseSignal); + b = DecimalUtils.fromFloat(complex.getReal()); + } else if (isInstance.execute(frame, other, DecimalContextUtils.getState(inliningTarget).getRationalType())) { + Object numerator = getAttr.execute(frame, inliningTarget, other, T_NUMERATOR); + if (!longCheck.execute(inliningTarget, numerator)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_INTEGER_ARGUMENT); + } + b = DecimalUtils.fromInteger(asInteger.execute(inliningTarget, numerator)); + if (a.kind() == Kind.FINITE) { + Object denominator = getAttr.execute(frame, inliningTarget, other, T_DENOMINATOR); + if (!longCheck.execute(inliningTarget, denominator)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_INTEGER_ARGUMENT); + } + a = multiplyDenominator(a, asInteger.execute(inliningTarget, denominator)); + } + } else { + return PNotImplemented.NOT_IMPLEMENTED; + } + if (a.kind() == Kind.NAN || a.kind() == Kind.SNAN || b.kind() == Kind.NAN || b.kind() == Kind.SNAN) { + if (!op.isEqOrNe() || a.kind() == Kind.SNAN || b.kind() == Kind.SNAN) { + DecimalContextUtils.addStatus(frame, context, DecimalContextUtils.conditionMask(DecimalExceptionTypes.InvalidOperation), raiseSignal); + } + return op.isNe(); + } + return op.compareResultToBool(DecimalArithmetic.compare(a, b)); + } + + private static void floatOperation(VirtualFrame frame, PDecimalContext context, RichCmpOp op, RaiseSignalNode raiseSignal) { + int mask = DecimalContextUtils.mask(DecimalExceptionTypes.FloatOperation); + if (op.isEqOrNe()) { + context.setFlags(context.getFlags() | mask); + } else { + DecimalContextUtils.addStatus(frame, context, DecimalContextUtils.conditionMask(DecimalExceptionTypes.FloatOperation), raiseSignal); + } + } + + @TruffleBoundary + private static Value multiplyDenominator(Value value, BigInteger denominator) { + return new Value(value.negative() != (denominator.signum() < 0), value.coefficient().multiply(denominator.abs()), value.exponent(), Kind.FINITE); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalFormatting.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalFormatting.java new file mode 100644 index 0000000000..5dfba332d0 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalFormatting.java @@ -0,0 +1,358 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.decimal; + +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; +import static com.oracle.graal.python.runtime.locale.PythonLocale.LC_NUMERIC; +import static com.oracle.graal.python.util.PythonUtils.tsLiteral; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageGetItem; +import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.Value; +import com.oracle.graal.python.builtins.objects.decimal.PDecimal.Kind; +import com.oracle.graal.python.builtins.objects.dict.PDict; +import com.oracle.graal.python.lib.PyUnicodeCheckNode; +import com.oracle.graal.python.nodes.ErrorMessages; +import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.util.CastToJavaStringNode; +import com.oracle.graal.python.runtime.PythonContext; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.strings.TruffleString; + +/** Decimal formatting is quiet: only rounding and capitals are taken from the context. */ +final class DecimalFormatting { + private static final TruffleString T_DECIMAL_POINT = tsLiteral("decimal_point"); + private static final TruffleString T_THOUSANDS_SEP = tsLiteral("thousands_sep"); + private static final TruffleString T_GROUPING = tsLiteral("grouping"); + private static final Pattern FORMAT = Pattern.compile("(?sU)\\A(?:(?.)?(?[<>=^]))?(?[-+ ])?(?z)?(?\\#)?(?0)?" + + "(?(?!0)\\d+)?(?[,_])?(?:\\.(?\\d+))?(?[eEfFgGnN%])?\\z"); + + private DecimalFormatting() { + } + + @TruffleBoundary + static String format(PDecimal self, String format, Object override, PDecimalContext context) { + Matcher match = FORMAT.matcher(format); + if (!match.matches() || match.group("zero") != null && match.group("align") != null) { + throw PRaiseNode.getUncached().raise(null, ValueError, ErrorMessages.DECIMAL_FORMAT_INVALID); + } + String fill = groupOrDefault(match, "fill", " "); + char align = groupOrDefault(match, "align", ">").charAt(0); + char sign = groupOrDefault(match, "sign", "-").charAt(0); + boolean zeroPad = match.group("zero") != null; + boolean noNegativeZero = match.group("z") != null; + boolean alternate = match.group("alt") != null; + long width = number(match.group("width"), 0); + long precision = number(match.group("prec"), -1); + String defaultType = "g"; + if (context.getCapitals()) { + defaultType = "G"; + } + char type = groupOrDefault(match, "type", defaultType).charAt(0); + char lowerType = Character.toLowerCase(type); + // CPython falls back to _pydecimal for these newer syntax features. Its parser + // rejects leading precision zeros and does not consume the locale override. + boolean fallback = noNegativeZero || alternate || "_".equals(match.group("sep")) || + !asciiDigits(match.group("width")) || !asciiDigits(match.group("prec")); + String precisionString = match.group("prec"); + if (fallback && (type == 'N' || precisionString != null && precisionString.length() > 1 && precisionString.charAt(0) == '0')) { + throw PRaiseNode.getUncached().raise(null, ValueError, ErrorMessages.DECIMAL_FORMAT_INVALID); + } + if (precision == 0 && (lowerType == 'g' || lowerType == 'n')) { + precision = 1; + } + String dot = "."; + String separator = groupOrDefault(match, "sep", ""); + String grouping = "\003\000"; + if (lowerType == 'n') { + if (!separator.isEmpty()) { + throw PRaiseNode.getUncached().raise(null, ValueError, ErrorMessages.DECIMAL_FORMAT_INVALID); + } + Locale locale = PythonContext.get(null).getCurrentLocale().category(LC_NUMERIC); + NumberFormat numberFormat = NumberFormat.getInstance(locale); + DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale); + dot = String.valueOf(symbols.getDecimalSeparator()); + separator = String.valueOf(symbols.getGroupingSeparator()); + grouping = ""; + if (numberFormat instanceof DecimalFormat df && df.isGroupingUsed()) { + grouping = String.valueOf((char) df.getGroupingSize()) + "\000"; + } + type = type == 'N' ? 'G' : 'g'; + lowerType = 'g'; + } + if (!fallback && override != PNone.NO_VALUE) { + if (!(override instanceof PDict dict)) { + throw PRaiseNode.getUncached().raise(null, TypeError, ErrorMessages.DECIMAL_FORMAT_DICT); + } + dot = overrideString(dict, T_DECIMAL_POINT, dot); + separator = overrideString(dict, T_THOUSANDS_SEP, separator); + grouping = overrideString(dict, T_GROUPING, grouping); + if (dot.isEmpty() || dot.getBytes(StandardCharsets.UTF_8).length > 4 || separator.getBytes(StandardCharsets.UTF_8).length > 4 || + !isAscii(grouping)) { + throw PRaiseNode.getUncached().raise(null, ValueError, ErrorMessages.DECIMAL_FORMAT_OVERRIDE); + } + } + if (width > DecimalUtils.MAX_PREC || self.getKind() == Kind.FINITE && precision > DecimalUtils.MAX_PREC) { + throw PRaiseNode.getUncached().raise(null, ValueError, ErrorMessages.DECIMAL_FORMAT_LIMITS); + } + if (self.getKind() != Kind.FINITE) { + String body = self.toScientificString(true); + if (self.isNegative()) { + body = body.substring(1); + } + if (type == '%') { + body += '%'; + } + if (zeroPad) { + fill = " "; + align = '>'; + } + return align(sign(self.isNegative(), sign), body, fill, align, width); + } + long valueExponent = self.getExponent(); + if (type == '%') { + valueExponent += 2; + } + Value value = new Value(self.isNegative(), self.getCoefficient(), valueExponent, Kind.FINITE); + if (precision >= 0) { + if (lowerType == 'f' || type == '%') { + value = DecimalArithmetic.rescale(value, -precision, context.getRounding()); + } else if (lowerType == 'e' && value.coefficient().signum() != 0) { + value = round(value, precision + 1, context.getRounding()); + } else if (lowerType == 'g' && DecimalUtils.digits(value.coefficient()) > precision) { + value = round(value, precision, context.getRounding()); + } + } + boolean zero = value.coefficient().signum() == 0; + if (zero && value.exponent() > 0 && (lowerType == 'f' || type == '%')) { + value = new Value(value.negative(), BigInteger.ZERO, 0, Kind.FINITE); + } + String digits = value.coefficient().toString(); + long leftDigits = value.exponent() + digits.length(); + long point; + if (lowerType == 'e') { + point = 1; + if (zero && precision >= 0) { + point = 1 - precision; + } + } else if (lowerType == 'f' || type == '%') { + point = leftDigits; + } else { + point = 1; + if (value.exponent() <= 0 && leftDigits > -6) { + point = leftDigits; + } + } + String integer; + String fraction; + if (point <= 0) { + integer = "0"; + fraction = zeros(-point) + digits; + } else if (point >= digits.length()) { + integer = digits + zeros(point - digits.length()); + fraction = ""; + } else { + integer = digits.substring(0, (int) point); + fraction = digits.substring((int) point); + } + long exponent = leftDigits - point; + String suffix = ""; + if (!fraction.isEmpty() || alternate) { + suffix = dot + fraction; + } + if (exponent != 0 || lowerType == 'e') { + if (Character.isUpperCase(type)) { + suffix += "E"; + } else { + suffix += "e"; + } + if (exponent >= 0) { + suffix += "+"; + } + suffix += exponent; + } + if (type == '%') { + suffix += '%'; + } + String signString = sign(value.negative() && !(zero && noNegativeZero), sign); + long minWidth = 0; + if (zeroPad) { + minWidth = width - length(suffix) - signString.length(); + } + integer = group(integer, separator, grouping, minWidth); + return align(signString, integer + suffix, fill, align, width); + } + + private static Value round(Value value, long precision, DecimalRounding rounding) { + long exponent = value.exponent() + DecimalUtils.digits(value.coefficient()) - precision; + Value result = DecimalArithmetic.rescale(value, exponent, rounding); + if (DecimalUtils.digits(result.coefficient()) > precision) { + result = DecimalArithmetic.rescale(result, exponent + 1, rounding); + } + return result; + } + + private static String groupOrDefault(Matcher match, String group, String defaultValue) { + String result = match.group(group); + if (result == null) { + return defaultValue; + } + return result; + } + + private static boolean asciiDigits(String value) { + if (value != null) { + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c < '0' || c > '9') { + return false; + } + } + } + return true; + } + + private static boolean isAscii(String value) { + for (int i = 0; i < value.length(); i++) { + if (value.charAt(i) > 127) { + return false; + } + } + return true; + } + + private static long number(String number, long defaultValue) { + if (number == null) { + return defaultValue; + } + try { + return Long.parseLong(number); + } catch (NumberFormatException e) { + throw PRaiseNode.getUncached().raise(null, ValueError, ErrorMessages.DECIMAL_FORMAT_INVALID); + } + } + + private static String overrideString(PDict dict, TruffleString key, String defaultValue) { + Object value = HashingStorageGetItem.executeUncached(dict.getDictStorage(), key); + if (value == null) { + return defaultValue; + } + if (!PyUnicodeCheckNode.executeUncached(value)) { + throw PRaiseNode.getUncached().raise(null, TypeError, ErrorMessages.DECIMAL_FORMAT_STRING); + } + String result = CastToJavaStringNode.getUncached().execute(value); + // libmpdec treats the override values as NUL-terminated strings. + int nul = result.indexOf('\000'); + return nul < 0 ? result : result.substring(0, nul); + } + + private static String zeros(long count) { + return "0".repeat(Math.toIntExact(count)); + } + + private static String sign(boolean negative, char sign) { + if (negative) { + return "-"; + } + if (sign == '-') { + return ""; + } + return String.valueOf(sign); + } + + private static int length(String value) { + return value.codePointCount(0, value.length()); + } + + private static String align(String sign, String body, String fill, char align, long width) { + int padding = Math.toIntExact(Math.max(0, width - sign.length() - length(body))); + return switch (align) { + case '<' -> sign + body + fill.repeat(padding); + case '=' -> sign + fill.repeat(padding) + body; + case '^' -> fill.repeat(padding / 2) + sign + body + fill.repeat(padding - padding / 2); + default -> fill.repeat(padding) + sign + body; + }; + } + + private static String group(String digits, String separator, String grouping, long width) { + List groups = new ArrayList<>(); + int end = digits.length(); + int index = 0; + int previous = 0; + while (true) { + int size = previous; + if (index < grouping.length()) { + size = grouping.charAt(index++); + } + if (size == 0) { + size = previous; + } + if (size == 127 || size == 0) { + size = Math.toIntExact(Math.max(Math.max(end, width), 1)); + } + previous = size; + int count = (int) Math.min(Math.max(Math.max(end, width), 1), size); + int start = Math.max(0, end - count); + groups.add(zeros(count - (end - start)) + digits.substring(start, end)); + end = start; + width -= count; + if (end == 0 && width <= 0) { + break; + } + width -= length(separator); + } + Collections.reverse(groups); + return String.join(separator, groups); + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java index da9f66d697..6d1e1e75ac 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java @@ -60,6 +60,8 @@ public final class DecimalModuleState { private final PythonClass decimalTupleType; private final PythonClass signalDictType; + private Object rationalType; + private PDecimalContext defaultContext; private PDecimalContext basicContext; private PDecimalContext extendedContext; @@ -83,6 +85,14 @@ public PythonClass getSignalDictType() { return signalDictType; } + public Object getRationalType() { + return rationalType; + } + + public void setRationalType(Object rationalType) { + this.rationalType = rationalType; + } + public void initializeContexts(PythonLanguage language) { currentContext = PFactory.createContextVar(language, T_DECIMAL_CONTEXT, PContextVar.NO_DEFAULT); Object cls = PythonBuiltinClassType.PDecimalContext; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java index c1c16c7bee..3b673a7740 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java @@ -73,6 +73,13 @@ import com.oracle.truffle.api.profiles.InlinedConditionProfile; public final class DecimalNodes { + private static final int INVALID = DecimalContextUtils.conditionMask(DecimalExceptionTypes.InvalidOperation); + private static final int INEXACT = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Inexact); + private static final int ROUNDED = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Rounded); + private static final int SUBNORMAL = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Subnormal); + private static final int CLAMPED = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Clamped); + private static final long HALF_TEN_TO_NINETEEN = 5000000000000000000L; + private DecimalNodes() { } @@ -471,7 +478,7 @@ private static PDecimal finishResult(VirtualFrame frame, PythonLanguage language return create(language, result.value()); } - private static boolean isCompactFinite(PDecimal value) { + static boolean isCompactFinite(PDecimal value) { return value.getKind() == Kind.FINITE && value.isCompact(); } @@ -571,6 +578,205 @@ private static PDecimal compactMultiply(PythonLanguage language, PDecimal a, PDe return null; } + /** Apply quantize's precision, exponent limits and flags without BigInteger arithmetic. */ + private static PDecimal compactQuantize(VirtualFrame frame, PythonLanguage language, PDecimal self, long exponent, + PDecimalContext context, DecimalRounding rounding, RaiseSignalNode raiseSignal) { + if (!isCompactFinite(self)) { + return null; + } + long coefficient = self.getCompactCoefficient(); + long etiny = context.getEmin() - context.getPrecision() + 1; + long adjusted = self.getExponent() + DecimalArithmetic.digits(coefficient) - 1; + if (exponent < etiny || exponent > context.getEmax() || coefficient != 0 && + (adjusted > context.getEmax() || adjusted - exponent + 1 > context.getPrecision())) { + return invalidQuantize(frame, language, context, raiseSignal); + } + int status = 0; + long shift = self.getExponent() - exponent; + if (coefficient != 0) { + if (shift >= 0) { + coefficient = scaleCompactCoefficient(coefficient, shift); + if (coefficient < 0) { + return null; + } + } else { + long places = -shift; + long divisor = compactDivisor(places); + long remainder = coefficient; + long quotient = 0; + if (divisor != 0) { + quotient = coefficient / divisor; + remainder = coefficient % divisor; + } + if (incrementCompact(self.isNegative(), quotient, remainder, divisor, places, rounding)) { + quotient++; + } + coefficient = quotient; + status |= ROUNDED; + if (remainder != 0) { + status |= INEXACT; + } + } + } + int digits = DecimalArithmetic.digits(coefficient); + adjusted = exponent + digits - 1; + if (digits > context.getPrecision() || adjusted > context.getEmax()) { + return invalidQuantize(frame, language, context, raiseSignal); + } + if (coefficient != 0 && adjusted < context.getEmin()) { + status |= SUBNORMAL; + } + long etop = context.getEmax() - context.getPrecision() + 1; + if (context.getClamp() != 0 && exponent > etop) { + coefficient = scaleCompactCoefficient(coefficient, exponent - etop); + if (coefficient < 0) { + return null; + } + exponent = etop; + status |= CLAMPED; + } + DecimalContextUtils.addStatus(frame, context, status, raiseSignal); + return compact(language, self.isNegative(), coefficient, exponent); + } + + private static PDecimal invalidQuantize(VirtualFrame frame, PythonLanguage language, PDecimalContext context, RaiseSignalNode raiseSignal) { + DecimalContextUtils.addStatus(frame, context, INVALID, raiseSignal); + return PFactory.createDecimal(language, false, 0L, 0, Kind.NAN); + } + + @GenerateInline + @GenerateCached(false) + @GenerateUncached + public abstract static class QuantizeDecimalNode extends Node { + public abstract PDecimal execute(VirtualFrame frame, Node inliningTarget, PDecimal self, long exponent, PDecimalContext context, DecimalRounding rounding); + + @Specialization + static PDecimal quantize(VirtualFrame frame, Node inliningTarget, PDecimal self, long exponent, PDecimalContext context, DecimalRounding rounding, + @Cached InlinedBranchProfile nonCompactProfile, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode) { + PythonLanguage language = PythonLanguage.get(inliningTarget); + PDecimal fast = compactQuantize(frame, language, self, exponent, context, rounding, raiseSignal); + if (fast != null) { + return fast; + } + nonCompactProfile.enter(inliningTarget); + Result result; + try { + result = DecimalArithmetic.quantize(self, exponent, context, rounding); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, context, result.status(), raiseSignal); + return create(language, result.value()); + } + } + + /** A divisor of zero denotes 10**places outside the compact coefficient range. */ + private static long compactDivisor(long places) { + if (places > 18) { + return 0; + } + long divisor = 1; + for (int i = 0; i < places; i++) { + divisor *= 10; + } + return divisor; + } + + private static boolean incrementCompact(boolean negative, long quotient, long remainder, long divisor, long places, DecimalRounding rounding) { + if (remainder == 0) { + return false; + } + int half = -1; + if (divisor != 0) { + half = Long.compare(remainder, divisor / 2); + } else if (places == 19) { + half = Long.compare(remainder, HALF_TEN_TO_NINETEEN); + } + return switch (rounding) { + case ROUND_DOWN -> false; + case ROUND_UP -> true; + case ROUND_CEILING -> !negative; + case ROUND_FLOOR -> negative; + case ROUND_05UP -> quotient % 5 == 0; + case ROUND_HALF_UP -> half >= 0; + case ROUND_HALF_DOWN -> half > 0; + case ROUND_HALF_EVEN -> half > 0 || half == 0 && (quotient & 1) != 0; + }; + } + + /** Long.MIN_VALUE indicates that the integer result needs the BigInteger fallback. */ + static long compactInteger(PDecimal self, DecimalRounding rounding) { + if (!self.isCompact()) { + return Long.MIN_VALUE; + } + long coefficient = self.getCompactCoefficient(); + long exponent = self.getExponent(); + if (exponent >= 0) { + coefficient = scaleCompactCoefficient(coefficient, exponent); + if (coefficient < 0) { + return Long.MIN_VALUE; + } + } else { + long divisor = compactDivisor(-exponent); + long quotient = 0; + long remainder = coefficient; + if (divisor != 0) { + quotient = coefficient / divisor; + remainder = coefficient % divisor; + } + if (incrementCompact(self.isNegative(), quotient, remainder, divisor, -exponent, rounding)) { + quotient++; + } + coefficient = quotient; + } + if (self.isNegative()) { + return -coefficient; + } + return coefficient; + } + + /** Compare finite compact magnitudes by truncating, so scaling cannot overflow. */ + static int compareCompact(boolean negativeA, long a, long exponentA, boolean negativeB, long b, long exponentB) { + if (a == 0 && b == 0) { + return 0; + } + if (negativeA != negativeB) { + if (negativeA) { + return -1; + } + return 1; + } + int result; + if (a == 0 || b == 0) { + result = Long.compare(a, b); + } else { + long adjustedA = exponentA + DecimalArithmetic.digits(a) - 1; + long adjustedB = exponentB + DecimalArithmetic.digits(b) - 1; + result = Long.compare(adjustedA, adjustedB); + if (result == 0) { + if (exponentA >= exponentB) { + long divisor = compactDivisor(exponentA - exponentB); + result = Long.compare(a, b / divisor); + if (result == 0 && b % divisor != 0) { + result = -1; + } + } else { + long divisor = compactDivisor(exponentB - exponentA); + result = Long.compare(a / divisor, b); + if (result == 0 && a % divisor != 0) { + result = 1; + } + } + } + } + if (negativeA) { + return -result; + } + return result; + } + @GenerateInline(false) // Error path @GenerateUncached public abstract static class RaiseSignalNode extends Node { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java index d84050a216..d7dbab45b7 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java @@ -65,6 +65,7 @@ public enum Kind { private final Kind kind; private final long compactCoefficient; private final BigInteger largeCoefficient; + private long hash = -1; public PDecimal(Object cls, Shape instanceShape, long value) { super(cls, instanceShape); @@ -90,6 +91,7 @@ public PDecimal(Object cls, Shape instanceShape, boolean negative, long coeffici largeCoefficient = null; } + @TruffleBoundary public PDecimal(Object cls, Shape instanceShape, boolean negative, BigInteger coefficient, long exponent, Kind kind) { super(cls, instanceShape); assert coefficient.signum() >= 0; @@ -115,6 +117,15 @@ public PDecimal(Object cls, Shape instanceShape, PDecimal value) { largeCoefficient = value.largeCoefficient; } + public long getHash() { + return hash; + } + + public void setHash(long hash) { + assert hash != -1; + this.hash = hash; + } + public boolean isNegative() { return negative; } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java index 677f28797b..dccd5cf3f8 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java @@ -1628,6 +1628,15 @@ public abstract class ErrorMessages { public static final TruffleString DECIMAL_INFINITY_TO_INTEGER = tsLiteral("cannot convert Infinity to integer"); public static final TruffleString DECIMAL_NAN_TO_INTEGER = tsLiteral("cannot convert NaN to integer"); public static final TruffleString DECIMAL_SNAN_TO_FLOAT = tsLiteral("cannot convert signaling NaN to float"); + public static final TruffleString DECIMAL_SNAN_HASH = tsLiteral("Cannot hash a signaling NaN value"); + public static final TruffleString DECIMAL_INTEGER_ARGUMENT = tsLiteral("argument must be an integer"); + public static final TruffleString DECIMAL_FORMAT_N_DEPRECATED = tsLiteral("Format specifier 'N' is deprecated and slated for removal in Python 3.18"); + public static final TruffleString DECIMAL_ROUND_DIGITS = tsLiteral("optional arg must be an integer"); + public static final TruffleString DECIMAL_FORMAT_STRING = tsLiteral("format arg must be str"); + public static final TruffleString DECIMAL_FORMAT_DICT = tsLiteral("optional argument must be a dict"); + public static final TruffleString DECIMAL_FORMAT_OVERRIDE = tsLiteral("invalid override dict"); + public static final TruffleString DECIMAL_FORMAT_INVALID = tsLiteral("invalid format string"); + public static final TruffleString DECIMAL_FORMAT_LIMITS = tsLiteral("format specification exceeds internal limits of _decimal"); public static final TruffleString DECIMAL_INVALID_LOCALCONTEXT_KEYWORD = tsLiteral("'%s' is an invalid keyword argument for this function"); // re module errors From 0614887e7a8d26cce22abbd292760fd3eecdfe2e Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Fri, 9 Oct 2026 13:56:33 +0200 Subject: [PATCH 07/31] Implement remaining decimal methods --- .../objects/decimal/DecimalArithmetic.java | 766 +++++++++++++- .../objects/decimal/DecimalBuiltins.java | 935 +++++++++++++----- .../objects/decimal/DecimalNodes.java | 23 + .../objects/decimal/DecimalPowerMath.java | 26 + .../builtins/objects/decimal/PDecimal.java | 12 +- .../graal/python/nodes/ErrorMessages.java | 3 + 6 files changed, 1516 insertions(+), 249 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java index 8ef92fd3ba..11b3f44d48 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java @@ -401,6 +401,759 @@ static ResultWithRemainder divmod(PDecimal left, PDecimal right, PDecimalContext return new ResultWithRemainder(result, result, arithmetic.status); } + @TruffleBoundary + static Result normalize(PDecimal self, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value a = value(self); + Value result = work.checkNaNs(a); + if (result == null) { + result = work.normalize(a); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result toIntegralValue(PDecimal self, PDecimalContext context, DecimalRounding rounding) { + DecimalArithmetic work = new DecimalArithmetic(context.getPrecision(), context.getEmin(), context.getEmax(), rounding, context.getClamp()); + Value a = value(self); + Value result = work.checkNaNs(a); + if (result == null) { + result = work.integral(a, false); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result toIntegralExact(PDecimal self, PDecimalContext context, DecimalRounding rounding) { + DecimalArithmetic work = new DecimalArithmetic(context.getPrecision(), context.getEmin(), context.getEmax(), rounding, context.getClamp()); + Value a = value(self); + Value result = work.checkNaNs(a); + if (result == null) { + result = work.integral(a, true); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result exp(PDecimal self, PDecimalContext context) { + // These three transcendental operations always use half-even, including overflow. + DecimalArithmetic work = new DecimalArithmetic(context.getPrecision(), context.getEmin(), context.getEmax(), ROUND_HALF_EVEN, context.getClamp()); + Value a = value(self); + Value result = work.checkNaNs(a); + if (result == null) { + result = work.exp(a); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result ln(PDecimal self, PDecimalContext context) { + return logarithm(self, context, false); + } + + @TruffleBoundary + static Result log10(PDecimal self, PDecimalContext context) { + return logarithm(self, context, true); + } + + private static Result logarithm(PDecimal self, PDecimalContext context, boolean baseTen) { + DecimalArithmetic work = new DecimalArithmetic(context.getPrecision(), context.getEmin(), context.getEmax(), ROUND_HALF_EVEN, context.getClamp()); + Value a = value(self); + Value result = work.checkNaNs(a); + if (result == null) { + result = work.logarithm(a, baseTen); + } + return new Result(result, work.status); + } + + private Value exp(Value a) { + if (a.kind() == Kind.INFINITY) { + return a.negative() ? zero(false, 0) : infinity(false); + } + if (isZero(a)) { + return finite(false, BigInteger.ONE, 0); + } + status |= INEXACT | ROUNDED; + long adjusted = adjusted(a); + long etiny = emin - precision + 1; + long limit = a.negative() ? -etiny : emax; + if (adjusted > digits(3 * (limit + 1))) { + long exponent; + if (a.negative()) { + exponent = etiny - 1; + } else { + exponent = emax + 1; + } + return finish(finite(false, BigInteger.ONE, exponent)); + } + // libmpdec's first work context uses precision + 3 and returns exactly one + // for abs(a) <= 9 * 10^(-precision-4). In particular, it does not signal + // tininess for negative inputs here when Emin is zero. + if (compareMagnitude(a, finite(false, BigInteger.valueOf(9), -precision - 4)) <= 0) { + return finish(finite(false, pow10(precision - 1), 1 - precision)); + } + if (!a.negative() && adjusted < -precision) { + return finish(finite(false, pow10(precision).add(BigInteger.ONE), -precision)); + } + if (a.negative() && adjusted < -precision - 1) { + return finish(finite(false, pow10(precision + 1).subtract(BigInteger.ONE), -precision - 1)); + } + for (long extra = 3;; extra += 3) { + Value approximation = DecimalPowerMath.exponential(a, precision + extra); + Value result = finishTranscendentalInterval(approximation); + if (result != null) { + return result; + } + } + } + + private Value logarithm(Value a, boolean baseTen) { + if (isZero(a)) { + // Unlike logb, logarithms of either signed zero do not signal DivisionByZero. + return infinity(true); + } + if (a.negative()) { + return invalid(INVALID); + } + if (a.kind() == Kind.INFINITY) { + return infinity(false); + } + if (a.coefficient().equals(pow10(digits(a.coefficient()) - 1))) { + long exponent = adjusted(a); + if (baseTen) { + return finish(finite(exponent < 0, BigInteger.valueOf(exponent).abs(), 0)); + } + if (exponent == 0) { + return zero(false, 0); + } + } + status |= INEXACT | ROUNDED; + // The log10 bound is also a conservative bound for ln, since abs(ln(x)) + // exceeds abs(log10(x)). It retains significant digits for arguments near one. + long bound = logExponentBound(a); + for (long extra = 3;; extra += 3) { + long places = precision - bound + extra; + Value approximation; + if (baseTen) { + approximation = DecimalPowerMath.logarithm10(a.coefficient(), a.exponent(), places); + } else { + approximation = DecimalPowerMath.logarithm(a.coefficient(), a.exponent(), places); + } + Value result = finishTranscendentalInterval(approximation); + if (result != null) { + return result; + } + } + } + + /** Finalize only when both ends of the error interval have identical values and signals. */ + private Value finishTranscendentalInterval(Value approximation) { + DecimalArithmetic low = new DecimalArithmetic(precision, emin, emax, rounding, clamp); + DecimalArithmetic high = new DecimalArithmetic(precision, emin, emax, rounding, clamp); + low.status = high.status = INEXACT | ROUNDED; + Value lower = low.finish(finite(approximation.negative(), approximation.coefficient().subtract(BigInteger.ONE), approximation.exponent())); + Value upper = high.finish(finite(approximation.negative(), approximation.coefficient().add(BigInteger.ONE), approximation.exponent())); + if ((low.status & SUBNORMAL) != 0) { + low.status |= UNDERFLOW; + } + if ((high.status & SUBNORMAL) != 0) { + high.status |= UNDERFLOW; + } + if (lower.equals(upper) && low.status == high.status) { + status |= low.status; + return lower; + } + return null; + } + + @TruffleBoundary + static Result sqrt(PDecimal self, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value a = value(self); + Value result = work.checkNaNs(a); + if (result == null) { + result = work.sqrt(a); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result compare(PDecimal self, PDecimal other, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value a = value(self); + Value b = value(other); + Value result = work.checkNaNs(a, b); + if (result == null) { + result = DecimalUtils.fromInteger(BigInteger.valueOf(Integer.signum(compare(a, b)))); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result compareSignal(PDecimal self, PDecimal other, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value a = value(self); + Value b = value(other); + Value result = work.checkNaNs(a, b); + if (result != null) { + work.status |= INVALID; + } + if (result == null) { + result = DecimalUtils.fromInteger(BigInteger.valueOf(Integer.signum(compare(a, b)))); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result nextMinus(PDecimal self, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value a = value(self); + Value result = work.checkNaNs(a); + if (result == null) { + result = work.next(a, false); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result nextPlus(PDecimal self, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value a = value(self); + Value result = work.checkNaNs(a); + if (result == null) { + result = work.next(a, true); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result nextToward(PDecimal self, PDecimal other, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value a = value(self); + Value b = value(other); + Value result = work.checkNaNs(a, b); + if (result == null) { + result = work.nextToward(a, b); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result remainderNear(PDecimal self, PDecimal other, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value a = value(self); + Value b = value(other); + Value result = work.checkNaNs(a, b); + if (result == null) { + result = work.remainderNear(a, b); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result logb(PDecimal self, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value a = value(self); + Value result = work.checkNaNs(a); + if (result == null) { + result = work.logb(a); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result rotate(PDecimal self, PDecimal other, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value a = value(self); + Value b = value(other); + Value result = work.checkNaNs(a, b); + if (result == null) { + result = work.rotate(a, b); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result shift(PDecimal self, PDecimal other, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value a = value(self); + Value b = value(other); + Value result = work.checkNaNs(a, b); + if (result == null) { + result = work.shift(a, b); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result scaleb(PDecimal self, PDecimal other, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value a = value(self); + Value b = value(other); + Value result = work.checkNaNs(a, b); + if (result == null) { + result = work.scaleb(a, b); + } + return new Result(result, work.status); + } + + @TruffleBoundary + static Result compareTotal(PDecimal self, PDecimal other) { + Value result = DecimalUtils.fromInteger(BigInteger.valueOf(compareTotal(value(self), value(other), false))); + return new Result(result, 0); + } + + @TruffleBoundary + static Result compareTotalMag(PDecimal self, PDecimal other) { + Value result = DecimalUtils.fromInteger(BigInteger.valueOf(compareTotal(value(self), value(other), true))); + return new Result(result, 0); + } + + @TruffleBoundary + static Result max(PDecimal self, PDecimal other, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value result = work.extremum(value(self), value(other), false, true); + return new Result(result, work.status); + } + + @TruffleBoundary + static Result maxMag(PDecimal self, PDecimal other, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value result = work.extremum(value(self), value(other), true, true); + return new Result(result, work.status); + } + + @TruffleBoundary + static Result min(PDecimal self, PDecimal other, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value result = work.extremum(value(self), value(other), false, false); + return new Result(result, work.status); + } + + @TruffleBoundary + static Result minMag(PDecimal self, PDecimal other, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value result = work.extremum(value(self), value(other), true, false); + return new Result(result, work.status); + } + + @TruffleBoundary + static Result logicalInvert(PDecimal self, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value result = work.logicalInvert(value(self)); + return new Result(result, work.status); + } + + @TruffleBoundary + static Result logicalAnd(PDecimal self, PDecimal other, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value result = work.logicalAnd(value(self), value(other)); + return new Result(result, work.status); + } + + @TruffleBoundary + static Result logicalOr(PDecimal self, PDecimal other, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value result = work.logicalOr(value(self), value(other)); + return new Result(result, work.status); + } + + @TruffleBoundary + static Result logicalXor(PDecimal self, PDecimal other, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value result = work.logicalXor(value(self), value(other)); + return new Result(result, work.status); + } + + @TruffleBoundary + static Result fma(PDecimal self, PDecimal other, PDecimal third, PDecimalContext context) { + DecimalArithmetic work = new DecimalArithmetic(context); + Value result = work.fma(value(self), value(other), value(third)); + return new Result(result, work.status); + } + + private Value normalize(Value a) { + Value result = finish(a); + if (result.kind() != Kind.FINITE) { + return result; + } + if (isZero(result)) { + return zero(result.negative(), 0); + } + long exponent = result.exponent(); + long maximum = clamp == 0 ? emax : emax - precision + 1; + BigInteger coefficient = result.coefficient(); + while (exponent < maximum && coefficient.remainder(BigInteger.TEN).signum() == 0) { + coefficient = coefficient.divide(BigInteger.TEN); + exponent++; + } + return finite(result.negative(), coefficient, exponent); + } + + private Value integral(Value a, boolean exact) { + if (a.kind() != Kind.FINITE || a.exponent() >= 0) { + return a; + } + Value result = rescale(a, 0, rounding); + if (exact && !isZero(a)) { + status |= ROUNDED; + if (compare(a, result) != 0) { + status |= INEXACT; + } + } + return result; + } + + private static Value unsigned(Value a) { + return new Value(false, a.coefficient(), a.exponent(), a.kind()); + } + + private static int totalRank(Kind kind) { + return switch (kind) { + case FINITE -> 0; + case INFINITY -> 1; + case SNAN -> 2; + case NAN -> 3; + }; + } + + private static int compareTotal(Value left, Value right, boolean magnitude) { + Value a = magnitude ? unsigned(left) : left; + Value b = magnitude ? unsigned(right) : right; + if (a.negative() != b.negative()) { + return a.negative() ? -1 : 1; + } + int order = Integer.compare(totalRank(a.kind()), totalRank(b.kind())); + if (order == 0) { + if (a.kind() == Kind.NAN || a.kind() == Kind.SNAN) { + order = a.coefficient().compareTo(b.coefficient()); + } else { + order = compare(unsigned(a), unsigned(b)); + if (order == 0 && a.kind() == Kind.FINITE) { + order = Long.compare(a.exponent(), b.exponent()); + } + } + } + return Integer.signum(a.negative() ? -order : order); + } + + private Value extremum(Value a, Value b, boolean magnitude, boolean maximum) { + // min/max ignore a lone quiet NaN and return the rounded numeric operand. + if (a.kind() == Kind.NAN && b.kind() != Kind.NAN && b.kind() != Kind.SNAN) { + return finish(b); + } + if (b.kind() == Kind.NAN && a.kind() != Kind.NAN && a.kind() != Kind.SNAN) { + return finish(a); + } + Value nan = checkNaNs(a, b); + if (nan != null) { + return nan; + } + int order = compare(magnitude ? unsigned(a) : a, magnitude ? unsigned(b) : b); + if (order == 0) { + order = compareTotal(a, b, false); + } + return finish((order >= 0) == maximum ? a : b); + } + + private Value logb(Value a) { + if (a.kind() == Kind.INFINITY) { + return infinity(false); + } + if (isZero(a)) { + status |= DIVISION_ZERO; + return infinity(true); + } + return finish(DecimalUtils.fromInteger(BigInteger.valueOf(adjusted(a)))); + } + + private static boolean logicalOperand(Value a) { + if (a.kind() != Kind.FINITE || a.negative() || a.exponent() != 0) { + return false; + } + String text = a.coefficient().toString(); + for (int i = 0; i < text.length(); i++) { + if (text.charAt(i) != '0' && text.charAt(i) != '1') { + return false; + } + } + return true; + } + + private Value logicalInvert(Value a) { + // Logical operands have stricter validation than arithmetic operands, including NaNs. + if (!logicalOperand(a)) { + return invalid(INVALID); + } + String left = a.coefficient().toString(); + int length = Math.toIntExact(precision); + char[] result = new char[length]; + for (int i = 0; i < length; i++) { + int x = i < left.length() ? left.charAt(left.length() - 1 - i) - '0' : 0; + result[length - 1 - i] = (char) ('0' + (x ^ 1)); + } + return finite(false, new BigInteger(new String(result)), 0); + } + + private Value logicalAnd(Value a, Value b) { + // Logical operands have stricter validation than arithmetic operands, including NaNs. + if (!logicalOperand(a) || !logicalOperand(b)) { + return invalid(INVALID); + } + String left = a.coefficient().toString(); + String right = b.coefficient().toString(); + int length = Math.toIntExact(Math.min(precision, Math.max(left.length(), right.length()))); + char[] result = new char[length]; + for (int i = 0; i < length; i++) { + int x = i < left.length() ? left.charAt(left.length() - 1 - i) - '0' : 0; + int y = i < right.length() ? right.charAt(right.length() - 1 - i) - '0' : 0; + result[length - 1 - i] = (char) ('0' + (x & y)); + } + return finite(false, new BigInteger(new String(result)), 0); + } + + private Value logicalOr(Value a, Value b) { + // Logical operands have stricter validation than arithmetic operands, including NaNs. + if (!logicalOperand(a) || !logicalOperand(b)) { + return invalid(INVALID); + } + String left = a.coefficient().toString(); + String right = b.coefficient().toString(); + int length = Math.toIntExact(Math.min(precision, Math.max(left.length(), right.length()))); + char[] result = new char[length]; + for (int i = 0; i < length; i++) { + int x = i < left.length() ? left.charAt(left.length() - 1 - i) - '0' : 0; + int y = i < right.length() ? right.charAt(right.length() - 1 - i) - '0' : 0; + result[length - 1 - i] = (char) ('0' + (x | y)); + } + return finite(false, new BigInteger(new String(result)), 0); + } + + private Value logicalXor(Value a, Value b) { + // Logical operands have stricter validation than arithmetic operands, including NaNs. + if (!logicalOperand(a) || !logicalOperand(b)) { + return invalid(INVALID); + } + String left = a.coefficient().toString(); + String right = b.coefficient().toString(); + int length = Math.toIntExact(Math.min(precision, Math.max(left.length(), right.length()))); + char[] result = new char[length]; + for (int i = 0; i < length; i++) { + int x = i < left.length() ? left.charAt(left.length() - 1 - i) - '0' : 0; + int y = i < right.length() ? right.charAt(right.length() - 1 - i) - '0' : 0; + result[length - 1 - i] = (char) ('0' + (x ^ y)); + } + return finite(false, new BigInteger(new String(result)), 0); + } + + private Value shift(Value a, Value b) { + if (b.kind() != Kind.FINITE || b.exponent() != 0 || b.coefficient().compareTo(BigInteger.valueOf(precision)) > 0) { + return invalid(INVALID); + } + if (a.kind() == Kind.INFINITY) { + return a; + } + long count = b.coefficient().longValue() * (b.negative() ? -1 : 1); + BigInteger coefficient = a.coefficient(); + if (digits(coefficient) > precision) { + coefficient = coefficient.remainder(pow10(precision)); + } + if (count < 0) { + if (-count >= digits(coefficient)) { + coefficient = BigInteger.ZERO; + } else { + coefficient = coefficient.divide(pow10(-count)); + } + } else if (count > 0 && coefficient.signum() != 0) { + if (count == precision) { + coefficient = BigInteger.ZERO; + } else { + coefficient = coefficient.multiply(pow10(count)); + if (digits(coefficient) > precision) { + coefficient = coefficient.remainder(pow10(precision)); + } + } + } + return finite(a.negative(), coefficient, a.exponent()); + } + + private Value rotate(Value a, Value b) { + if (b.kind() != Kind.FINITE || b.exponent() != 0 || b.coefficient().compareTo(BigInteger.valueOf(precision)) > 0) { + return invalid(INVALID); + } + if (a.kind() == Kind.INFINITY) { + return a; + } + long count = b.coefficient().longValue() * (b.negative() ? -1 : 1); + BigInteger coefficient = a.coefficient(); + if (digits(coefficient) > precision) { + coefficient = coefficient.remainder(pow10(precision)); + } + if (count != 0 && Math.abs(count) != precision && coefficient.signum() != 0) { + long left = count > 0 ? count : precision + count; + long pivot = precision - left; + if (pivot >= digits(coefficient)) { + coefficient = coefficient.multiply(pow10(left)); + } else { + BigInteger[] parts = coefficient.divideAndRemainder(pow10(pivot)); + BigInteger lower; + if (parts[1].signum() == 0) { + lower = BigInteger.ZERO; + } else { + lower = parts[1].multiply(pow10(left)); + } + coefficient = lower.add(parts[0]); + } + } + return finite(a.negative(), coefficient, a.exponent()); + } + + private Value scaleb(Value a, Value b) { + if (b.kind() != Kind.FINITE || b.exponent() != 0 || b.coefficient().compareTo(BigInteger.valueOf(2 * (emax + precision))) > 0) { + return invalid(INVALID); + } + if (a.kind() == Kind.INFINITY) { + return a; + } + long count = b.coefficient().longValue() * (b.negative() ? -1 : 1); + return finish(finite(a.negative(), a.coefficient(), a.exponent() + count)); + } + + private Value next(Value a, boolean up) { + if (a.kind() == Kind.INFINITY) { + if (a.negative() != up) { + return a; + } + return finite(a.negative(), pow10(precision).subtract(BigInteger.ONE), emax - precision + 1); + } + DecimalArithmetic directed = new DecimalArithmetic(precision, emin, emax, + up ? DecimalRounding.ROUND_CEILING : ROUND_FLOOR, clamp); + Value rounded = directed.finish(a); + if (compare(rounded, a) != 0) { + return rounded; + } + // A tiny signed increment requests the neighboring value with directed rounding. + return directed.add(a, finite(!up, BigInteger.ONE, emin - precision)); + } + + private Value nextToward(Value a, Value b) { + int order = compare(a, b); + if (order == 0) { + return new Value(b.negative(), a.coefficient(), a.exponent(), a.kind()); + } + Value result = next(a, order < 0); + if (result.kind() == Kind.INFINITY && a.kind() != Kind.INFINITY) { + status |= OVERFLOW | INEXACT | ROUNDED; + } else if (result.kind() == Kind.FINITE && adjusted(result) < emin) { + status |= UNDERFLOW | SUBNORMAL | INEXACT | ROUNDED; + if (isZero(result)) { + status |= CLAMPED; + } + } + return result; + } + + private Value remainderNear(Value a, Value b) { + if (a.kind() == Kind.INFINITY) { + return invalid(INVALID); + } + if (isZero(b)) { + return invalid(isZero(a) ? DIVISION_UNDEFINED : INVALID); + } + if (b.kind() == Kind.INFINITY) { + return finish(a); + } + long exponent = Math.min(a.exponent(), b.exponent()); + if (isZero(a)) { + return finish(zero(a.negative(), exponent)); + } + long difference = adjusted(a) - adjusted(b); + if (difference > precision) { + return invalid(DIVISION_IMPOSSIBLE); + } + if (difference <= -2) { + // Retain a discarded zero to preserve Rounded without enormous coefficient shifts. + long target = Math.max(exponent, a.exponent() - Math.max(0, precision - digits(a.coefficient()) + 1)); + return finish(rescale(a, target, rounding)); + } + BigInteger numerator = a.coefficient().multiply(pow10(a.exponent() - exponent)); + BigInteger denominator = b.coefficient().multiply(pow10(b.exponent() - exponent)); + BigInteger[] parts = numerator.divideAndRemainder(denominator); + BigInteger quotient = parts[0]; + BigInteger remainder = parts[1]; + int half = remainder.shiftLeft(1).compareTo(denominator); + if (half > 0 || half == 0 && quotient.testBit(0)) { + quotient = quotient.add(BigInteger.ONE); + remainder = remainder.subtract(denominator); + } + if (digits(quotient) > precision) { + return invalid(DIVISION_IMPOSSIBLE); + } + return finish(finite(a.negative() ^ remainder.signum() < 0, remainder.abs(), exponent)); + } + + private Value fma(Value a, Value b, Value c) { + // The multiplication is exact. Its invalid operations take precedence over the addend. + if (a.kind() == Kind.SNAN || b.kind() == Kind.SNAN) { + return checkNaNs(a, b); + } + Value product; + if (a.kind() == Kind.NAN || b.kind() == Kind.NAN) { + product = a.kind() == Kind.NAN ? a : b; + } else if (a.kind() == Kind.INFINITY || b.kind() == Kind.INFINITY) { + if (isZero(a) || isZero(b)) { + return invalid(INVALID); + } + product = infinity(a.negative() ^ b.negative()); + } else { + product = finite(a.negative() ^ b.negative(), a.coefficient().multiply(b.coefficient()), a.exponent() + b.exponent()); + } + Value nan = checkNaNs(product, c); + return nan == null ? add(product, c) : nan; + } + + private Value sqrt(Value a) { + if (a.kind() == Kind.INFINITY && !a.negative()) { + return a; + } + // Square root always rounds half even, independently of the context rounding mode. + DecimalArithmetic work = new DecimalArithmetic(precision, emin, emax, ROUND_HALF_EVEN, clamp); + Value result; + if (isZero(a)) { + result = work.finish(zero(a.negative(), Math.floorDiv(a.exponent(), 2))); + } else if (a.negative()) { + return invalid(INVALID); + } else { + long ideal = Math.floorDiv(a.exponent(), 2); + BigInteger coefficient = a.coefficient(); + if ((a.exponent() & 1) != 0) { + coefficient = coefficient.multiply(BigInteger.TEN); + } + BigInteger[] exactRoot = coefficient.sqrtAndRemainder(); + if (exactRoot[1].signum() == 0) { + // Exact roots retain their preferred exponent without expanding to precision. + result = work.finish(finite(false, exactRoot[0], ideal)); + status |= work.status; + return result; + } + long shift = precision + 1 - (digits(coefficient) + 1L) / 2; + if (shift >= 0) { + coefficient = coefficient.multiply(pow10(2 * shift)); + } else { + coefficient = coefficient.divide(pow10(-2 * shift)); + } + BigInteger root = coefficient.sqrt(); + long exponent = ideal - shift; + if (root.remainder(FIVE).signum() == 0) { + // Keep a sticky digit so final context rounding cannot mistake a tie. + root = root.add(BigInteger.ONE); + } + result = work.finish(finite(false, root, exponent)); + } + status |= work.status; + return result; + } + private Value add(Value a, Value b) { if (a.kind() == Kind.INFINITY || b.kind() == Kind.INFINITY) { if (a.kind() == b.kind() && a.negative() != b.negative()) { @@ -702,7 +1455,13 @@ private Value power(Value a, Value b) { long etiny = emin - precision + 1; long limit = growing ? emax : -etiny; if (bound > digits(limit)) { - return finish(finite(negative, BigInteger.ONE, growing ? emax + 1 : etiny - 1)); + long exponent; + if (growing) { + exponent = emax + 1; + } else { + exponent = etiny - 1; + } + return finish(finite(negative, BigInteger.ONE, exponent)); } if (integerExponent) { return integerPower(a, b, negative); @@ -763,7 +1522,10 @@ private Value roundInterval(Value approximation) { /** Match libmpdec's integer-power working precision and rounding of intermediate values. */ private Value integerPower(Value base, Value exponent, boolean negative) { - long workPrecision = precision + digits(exponent.coefficient()) + exponent.exponent() + 2 + (exponent.negative() ? 1 : 0); + long workPrecision = precision + digits(exponent.coefficient()) + exponent.exponent() + 2; + if (exponent.negative()) { + workPrecision++; + } DecimalArithmetic work = new DecimalArithmetic(workPrecision, emin, emax, ROUND_HALF_EVEN, 0); Value positiveBase = finite(false, base.coefficient(), base.exponent()); if (exponent.negative()) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java index 22fca955f8..499aa4a176 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java @@ -42,7 +42,6 @@ import static com.oracle.graal.python.builtins.PythonBuiltinClassType.DeprecationWarning; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.MemoryError; -import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.OverflowError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; @@ -79,6 +78,7 @@ import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.QuantizeDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.RaiseSignalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.RemainderDecimalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.RequireDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.SubtractDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.ToDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.ConversionException; @@ -110,6 +110,7 @@ import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.builtins.ListNodes.GetListStorageNode; import com.oracle.graal.python.nodes.builtins.TupleNodes.GetTupleStorage; +import com.oracle.graal.python.nodes.call.CallNode; import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; import com.oracle.graal.python.nodes.function.PythonBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode; @@ -146,213 +147,393 @@ public final class DecimalBuiltins extends PythonBuiltins { public static final TpSlots SLOTS = DecimalBuiltinsSlotsGen.SLOTS; private static final TruffleString T_NUMERATOR = tsLiteral("numerator"); private static final TruffleString T_DENOMINATOR = tsLiteral("denominator"); + private static final TruffleString T_SNAN = tsLiteral("sNaN"); + private static final TruffleString T_NAN = tsLiteral("NaN"); + private static final TruffleString T_NEGATIVE_INFINITY = tsLiteral("-Infinity"); + private static final TruffleString T_POSITIVE_INFINITY = tsLiteral("+Infinity"); + private static final TruffleString T_NEGATIVE_ZERO = tsLiteral("-Zero"); + private static final TruffleString T_POSITIVE_ZERO = tsLiteral("+Zero"); + private static final TruffleString T_NEGATIVE_SUBNORMAL = tsLiteral("-Subnormal"); + private static final TruffleString T_POSITIVE_SUBNORMAL = tsLiteral("+Subnormal"); + private static final TruffleString T_NEGATIVE_NORMAL = tsLiteral("-Normal"); + private static final TruffleString T_POSITIVE_NORMAL = tsLiteral("+Normal"); @Override protected List> getNodeFactories() { return DecimalBuiltinsFactory.getFactories(); } - @Builtin(name = "exp", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "exp", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @GenerateNodeFactory public abstract static class ExpNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + static PDecimal exp(VirtualFrame frame, PDecimal self, Object context, @Bind Node inliningTarget, + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.exp. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.exp(self, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "ln", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "ln", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @GenerateNodeFactory public abstract static class LnNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + static PDecimal ln(VirtualFrame frame, PDecimal self, Object context, @Bind Node inliningTarget, + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.ln. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.ln(self, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "log10", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "log10", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @GenerateNodeFactory public abstract static class Log10Node extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + static PDecimal log10(VirtualFrame frame, PDecimal self, Object context, @Bind Node inliningTarget, + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.log10. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.log10(self, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "next_minus", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "next_minus", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @GenerateNodeFactory public abstract static class NextMinusNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + static PDecimal nextMinus(VirtualFrame frame, PDecimal self, Object context, @Bind Node inliningTarget, + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.next_minus. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.nextMinus(self, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "next_plus", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "next_plus", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @GenerateNodeFactory public abstract static class NextPlusNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + static PDecimal nextPlus(VirtualFrame frame, PDecimal self, Object context, @Bind Node inliningTarget, + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.next_plus. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.nextPlus(self, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "normalize", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "normalize", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @GenerateNodeFactory public abstract static class NormalizeNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + static PDecimal normalize(VirtualFrame frame, PDecimal self, Object context, @Bind Node inliningTarget, + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.normalize. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.normalize(self, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "to_integral", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "rounding", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "to_integral", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "rounding", "context"}) @GenerateNodeFactory public abstract static class ToIntegralNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object rounding, Object context, PKeyword[] kwargs, + static PDecimal toIntegralValue(VirtualFrame frame, PDecimal self, Object rounding, Object context, @Bind Node inliningTarget, + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.to_integral. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + DecimalRounding actualRounding = getRounding(rounding, inliningTarget, actualContext); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.toIntegralValue(self, actualContext, actualRounding); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "to_integral_exact", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "rounding", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "to_integral_exact", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "rounding", "context"}) @GenerateNodeFactory public abstract static class ToIntegralExactNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object rounding, Object context, PKeyword[] kwargs, + static PDecimal toIntegralExact(VirtualFrame frame, PDecimal self, Object rounding, Object context, @Bind Node inliningTarget, + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.to_integral_exact. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + DecimalRounding actualRounding = getRounding(rounding, inliningTarget, actualContext); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.toIntegralExact(self, actualContext, actualRounding); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "to_integral_value", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "rounding", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "to_integral_value", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "rounding", "context"}) @GenerateNodeFactory public abstract static class ToIntegralValueNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object rounding, Object context, PKeyword[] kwargs, + static PDecimal toIntegralValue(VirtualFrame frame, PDecimal self, Object rounding, Object context, @Bind Node inliningTarget, + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.to_integral_value. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + DecimalRounding actualRounding = getRounding(rounding, inliningTarget, actualContext); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.toIntegralValue(self, actualContext, actualRounding); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "sqrt", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "sqrt", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @GenerateNodeFactory public abstract static class SqrtNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + static PDecimal sqrt(VirtualFrame frame, PDecimal self, Object context, @Bind Node inliningTarget, + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.sqrt. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.sqrt(self, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "compare", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "compare", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class CompareNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal compare(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.compare. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.compare(self, operand, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "compare_signal", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "compare_signal", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class CompareSignalNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal compareSignal(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.compare_signal. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.compareSignal(self, operand, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "max", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "max", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class MaxNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal max(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.max. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.max(self, operand, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "max_mag", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "max_mag", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class MaxMagNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal maxMag(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.max_mag. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.maxMag(self, operand, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "min", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "min", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class MinNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal min(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.min. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.min(self, operand, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "min_mag", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "min_mag", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class MinMagNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal minMag(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.min_mag. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.minMag(self, operand, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "next_toward", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "next_toward", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class NextTowardNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal nextToward(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.next_toward. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.nextToward(self, operand, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } @@ -393,27 +574,50 @@ static PDecimal quantize(VirtualFrame frame, PDecimal self, Object exp, Object r } } - @Builtin(name = "remainder_near", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "remainder_near", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class RemainderNearNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal remainderNear(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.remainder_near. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.remainderNear(self, operand, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "fma", minNumOfPositionalArgs = 3, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "third", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "fma", minNumOfPositionalArgs = 3, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "third", "context"}) @GenerateNodeFactory public abstract static class FmaNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object third, Object context, PKeyword[] kwargs, + static PDecimal fma(VirtualFrame frame, PDecimal self, Object other, Object third, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.fma. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + PDecimal addend = convert.execute(inliningTarget, third); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.fma(self, operand, addend, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } @@ -421,11 +625,9 @@ static Object doMock(VirtualFrame frame, Object self, Object other, Object third @GenerateNodeFactory public abstract static class IsCanonicalNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.is_canonical. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static boolean check(PDecimal self) { + // In CPython, mpd_iscanonical always returns true + return true; } } @@ -433,11 +635,8 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class IsFiniteNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.is_finite. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static boolean check(PDecimal self) { + return self.getKind() == Kind.FINITE; } } @@ -445,11 +644,8 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class IsInfiniteNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.is_infinite. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static boolean check(PDecimal self) { + return self.getKind() == Kind.INFINITY; } } @@ -457,11 +653,8 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class IsNanNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.is_nan. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static boolean check(PDecimal self) { + return self.getKind() == Kind.NAN || self.getKind() == Kind.SNAN; } } @@ -469,11 +662,8 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class IsQnanNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.is_qnan. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static boolean check(PDecimal self) { + return self.getKind() == Kind.NAN; } } @@ -481,11 +671,8 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class IsSnanNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.is_snan. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static boolean check(PDecimal self) { + return self.getKind() == Kind.SNAN; } } @@ -493,11 +680,8 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class IsSignedNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.is_signed. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static boolean check(PDecimal self) { + return self.isNegative(); } } @@ -505,35 +689,32 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class IsZeroNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.is_zero. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static boolean check(PDecimal self) { + return isZero(self); } } - @Builtin(name = "is_normal", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "is_normal", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @GenerateNodeFactory public abstract static class IsNormalNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + static boolean check(PDecimal self, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.is_normal. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + return isNonzeroFinite(self) && adjustedExponent(self) >= actualContext.getEmin(); } } - @Builtin(name = "is_subnormal", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "is_subnormal", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @GenerateNodeFactory public abstract static class IsSubnormalNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + static boolean check(PDecimal self, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.is_subnormal. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + return isNonzeroFinite(self) && adjustedExponent(self) < actualContext.getEmin(); } } @@ -541,11 +722,8 @@ static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] @GenerateNodeFactory public abstract static class AdjustedNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.adjusted. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static long adjusted(PDecimal self) { + return adjustedExponent(self); } } @@ -553,11 +731,8 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class CanonicalNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.canonical. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static PDecimal identity(PDecimal self) { + return self; } } @@ -565,11 +740,8 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class ConjugateNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.conjugate. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static PDecimal identity(PDecimal self) { + return self; } } @@ -577,11 +749,9 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class RadixNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.radix. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static PDecimal radix(@SuppressWarnings("unused") PDecimal self, + @Bind PythonLanguage language) { + return PFactory.createDecimal(language, 10); } } @@ -589,11 +759,9 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class CopyAbsNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.copy_abs. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static PDecimal copy(PDecimal self, + @Bind PythonLanguage language) { + return copySign(language, self, false); } } @@ -601,179 +769,288 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class CopyNegateNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, - @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.copy_negate. - throw raiseNode.raise(inliningTarget, NotImplementedError); + static PDecimal copy(PDecimal self, + @Bind PythonLanguage language) { + return copySign(language, self, !self.isNegative()); } } - @Builtin(name = "logb", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "logb", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @GenerateNodeFactory public abstract static class LogbNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + static PDecimal logb(VirtualFrame frame, PDecimal self, Object context, @Bind Node inliningTarget, + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.logb. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.logb(self, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "logical_invert", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "logical_invert", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @GenerateNodeFactory public abstract static class LogicalInvertNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + static PDecimal logicalInvert(VirtualFrame frame, PDecimal self, Object context, @Bind Node inliningTarget, + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.logical_invert. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.logicalInvert(self, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "number_class", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "number_class", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @GenerateNodeFactory public abstract static class NumberClassNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + static TruffleString classify(PDecimal self, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.number_class. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + return numberClass(self, actualContext); } } - @Builtin(name = "to_eng_string", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "to_eng_string", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @GenerateNodeFactory public abstract static class ToEngStringNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object context, PKeyword[] kwargs, + static TruffleString format(PDecimal self, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.to_eng_string. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached TruffleString.FromJavaStringNode fromJavaString) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + return fromJavaString.execute(engineeringString(self, actualContext.getCapitals()), TS_ENCODING); } } - @Builtin(name = "compare_total", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "compare_total", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class CompareTotalNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal compareTotal(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.compare_total. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.compareTotal(self, operand); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "compare_total_mag", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "compare_total_mag", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class CompareTotalMagNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal compareTotalMag(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.compare_total_mag. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.compareTotalMag(self, operand); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "copy_sign", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "copy_sign", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class CopySignNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal apply(PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.copy_sign. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RequireDecimalNode convert) { + getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + return copySign(PythonLanguage.get(inliningTarget), self, operand.isNegative()); } } - @Builtin(name = "same_quantum", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "same_quantum", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class SameQuantumNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static boolean apply(PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.same_quantum. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RequireDecimalNode convert) { + getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + return sameQuantum(self, operand); } } - @Builtin(name = "logical_and", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "logical_and", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class LogicalAndNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal logicalAnd(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.logical_and. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.logicalAnd(self, operand, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "logical_or", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "logical_or", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class LogicalOrNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal logicalOr(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.logical_or. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.logicalOr(self, operand, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "logical_xor", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "logical_xor", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class LogicalXorNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal logicalXor(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.logical_xor. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.logicalXor(self, operand, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "rotate", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "rotate", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class RotateNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal rotate(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.rotate. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.rotate(self, operand, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "scaleb", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "scaleb", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class ScalebNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal scaleb(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.scaleb. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.scaleb(self, operand, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } - @Builtin(name = "shift", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}, takesVarKeywordArgs = true) + @Builtin(name = "shift", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @GenerateNodeFactory public abstract static class ShiftNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object other, Object context, PKeyword[] kwargs, + static PDecimal shift(VirtualFrame frame, PDecimal self, Object other, Object context, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.shift. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached GetContextNode getContext, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode, + @Cached RequireDecimalNode convert) { + PDecimalContext actualContext = getContext.execute(inliningTarget, context); + PDecimal operand = convert.execute(inliningTarget, other); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.shift(self, operand, actualContext); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); } } @@ -781,11 +1058,27 @@ static Object doMock(VirtualFrame frame, Object self, Object other, Object conte @GenerateNodeFactory public abstract static class FromFloatNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static Object convert(VirtualFrame frame, Object cls, Object value, @Bind Node inliningTarget, + @Cached PyLongCheckNode longCheck, + @Cached PyFloatCheckNode floatCheck, + @Cached RequireDecimalNode toDecimal, + @Cached PyFloatAsDoubleNode asDouble, + @Cached IsBuiltinClassExactProfile clsProfile, + @Cached CallNode callNode, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.from_float. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal result; + if (longCheck.execute(inliningTarget, value)) { + result = toDecimal.execute(inliningTarget, value); + } else if (floatCheck.execute(inliningTarget, value)) { + result = DecimalNodes.create(PythonLanguage.get(inliningTarget), DecimalUtils.fromFloat(asDouble.execute(frame, inliningTarget, value))); + } else { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_FROM_FLOAT_ARGUMENT); + } + if (clsProfile.profileClass(inliningTarget, cls, PythonBuiltinClassType.PDecimal)) { + return result; + } + return callNode.execute(frame, cls, result); } } @@ -793,11 +1086,11 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class AsTupleNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static Object tuple(VirtualFrame frame, PDecimal self, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.as_tuple. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached CallNode callNode) { + Object[] args = tupleArguments(PythonLanguage.get(inliningTarget), self); + return callNode.execute(frame, DecimalContextUtils.getState(inliningTarget).getDecimalTupleType(), args, PKeyword.EMPTY_KEYWORDS); } } @@ -805,12 +1098,164 @@ static Object doMock(VirtualFrame frame, Object self, @GenerateNodeFactory public abstract static class AsIntegerRatioNode extends PythonUnaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, + static Object ratio(PDecimal self, @Bind Node inliningTarget, @Cached PRaiseNode raiseNode) { - // TODO implement Decimal.as_integer_ratio. - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (self.getKind() == Kind.INFINITY) { + throw raiseNode.raise(inliningTarget, OverflowError, ErrorMessages.DECIMAL_INFINITY_TO_RATIO); + } + if (self.getKind() != Kind.FINITE) { + throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.DECIMAL_NAN_TO_RATIO); + } + try { + return integerRatio(PythonLanguage.get(inliningTarget), self); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + } + } + + private static DecimalRounding getRounding(Object rounding, Node inliningTarget, PDecimalContext actualContext) { + if (rounding instanceof PNone) { + return actualContext.getRounding(); + } else { + return DecimalContextUtils.validateRounding(inliningTarget, rounding); + } + } + + private static boolean isZero(PDecimal self) { + return self.getKind() == Kind.FINITE && self.isCompact() && self.getCompactCoefficient() == 0; + } + + private static boolean isNonzeroFinite(PDecimal self) { + return self.getKind() == Kind.FINITE && !isZero(self); + } + + private static long adjustedExponent(PDecimal self) { + if (self.getKind() != Kind.FINITE) { + return 0; + } + long exponent = self.getExponent(); + int digits; + if (self.isCompact()) { + digits = DecimalUtils.digits(self.getCompactCoefficient()); + } else { + digits = DecimalUtils.digits(self.getCoefficient()); + } + return exponent + digits - 1; + } + + private static PDecimal copySign(PythonLanguage language, PDecimal self, boolean negative) { + if (self.isCompact()) { + return PFactory.createDecimal(language, negative, self.getCompactCoefficient(), self.getExponent(), self.getKind()); + } + return PFactory.createDecimal(language, negative, self.getCoefficient(), self.getExponent(), self.getKind()); + } + + private static boolean sameQuantum(PDecimal self, PDecimal other) { + if (self.getKind() == Kind.FINITE && other.getKind() == Kind.FINITE) { + return self.getExponent() == other.getExponent(); + } + if (self.getKind() == Kind.INFINITY || other.getKind() == Kind.INFINITY) { + return self.getKind() == other.getKind(); + } + return self.getKind() != Kind.FINITE && other.getKind() != Kind.FINITE; + } + + private static TruffleString numberClass(PDecimal self, PDecimalContext context) { + if (self.getKind() == Kind.SNAN) { + return T_SNAN; + } + if (self.getKind() == Kind.NAN) { + return T_NAN; + } + if (self.getKind() == Kind.INFINITY) { + return self.isNegative() ? T_NEGATIVE_INFINITY : T_POSITIVE_INFINITY; + } + if (isZero(self)) { + return self.isNegative() ? T_NEGATIVE_ZERO : T_POSITIVE_ZERO; + } + if (adjustedExponent(self) < context.getEmin()) { + return self.isNegative() ? T_NEGATIVE_SUBNORMAL : T_POSITIVE_SUBNORMAL; + } + return self.isNegative() ? T_NEGATIVE_NORMAL : T_POSITIVE_NORMAL; + } + + @TruffleBoundary + private static String engineeringString(PDecimal self, boolean capitals) { + if (self.getKind() != Kind.FINITE || self.getExponent() <= 0 && adjustedExponent(self) >= -6) { + return self.toScientificString(capitals); + } + String digits = self.getCoefficient().toString(); + long left = self.getExponent() + digits.length(); + int point; + if (isZero(self)) { + point = Math.floorMod(left + 1, 3) - 1; + } else { + point = Math.floorMod(left - 1, 3) + 1; + } + String significand; + if (point <= 0) { + significand = "0." + "0".repeat(-point) + digits; + } else if (point >= digits.length()) { + significand = digits + "0".repeat(point - digits.length()); + } else { + significand = digits.substring(0, point) + "." + digits.substring(point); + } + long exponent = left - point; + String result = (self.isNegative() ? "-" : "") + significand; + if (exponent != 0) { + result += (capitals ? "E" : "e") + (exponent > 0 ? "+" : "") + exponent; + } + return result; + } + + @TruffleBoundary + private static Object[] tupleArguments(PythonLanguage language, PDecimal self) { + String text = self.getCoefficient().toString(); + Object exponent = self.getExponent(); + if (self.getKind() == Kind.INFINITY) { + text = "0"; + exponent = tsLiteral("F"); + } else if (self.getKind() != Kind.FINITE) { + if (text.equals("0")) { + text = ""; + } + exponent = self.getKind() == Kind.NAN ? tsLiteral("n") : tsLiteral("N"); } + int[] digits = new int[text.length()]; + for (int i = 0; i < text.length(); i++) { + digits[i] = text.charAt(i) - '0'; + } + return new Object[]{self.isNegative() ? 1 : 0, PFactory.createTuple(language, digits), exponent}; + } + + @TruffleBoundary + private static Object integerRatio(PythonLanguage language, PDecimal self) { + BigInteger numerator = self.getCoefficient(); + BigInteger denominator = BigInteger.ONE; + if (numerator.signum() != 0) { + if (self.getExponent() >= 0) { + numerator = numerator.multiply(DecimalUtils.pow10(self.getExponent())); + } else { + // Cancel powers of two and five before constructing the denominator. + long fives = -self.getExponent(); + BigInteger five = BigInteger.valueOf(5); + while (fives > 0 && numerator.remainder(five).signum() == 0) { + numerator = numerator.divide(five); + fives--; + } + long twos = -self.getExponent(); + int shift = (int) Math.min(numerator.getLowestSetBit(), twos); + numerator = numerator.shiftRight(shift); + twos -= shift; + denominator = five.pow(Math.toIntExact(fives)).shiftLeft(Math.toIntExact(twos)); + } + } + if (self.isNegative()) { + numerator = numerator.negate(); + } + return PFactory.createTuple(language, new Object[]{PFactory.createInt(language, numerator), PFactory.createInt(language, denominator)}); } private static Value decimalValue(PDecimal self) { @@ -1521,7 +1966,7 @@ static boolean fitsCompactLong(long value) { return value != Long.MIN_VALUE; } - @Fallback + @Specialization static Object compare(VirtualFrame frame, PDecimal self, Object other, RichCmpOp op, @Bind Node inliningTarget, @Cached ToDecimalNode toDecimal, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java index 3b673a7740..5691e652d0 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java @@ -53,10 +53,12 @@ import com.oracle.graal.python.builtins.objects.decimal.PDecimal.Kind; import com.oracle.graal.python.builtins.objects.ints.PInt; import com.oracle.graal.python.builtins.objects.list.PList; +import com.oracle.graal.python.builtins.objects.type.TypeNodes; import com.oracle.graal.python.lib.PyLongCheckNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.call.CallNode; +import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.nodes.util.CastToJavaBigIntegerNode; import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; @@ -161,6 +163,27 @@ static PDecimal create(PythonLanguage language, Value value) { return PFactory.createDecimal(language, value.negative(), value.coefficient(), value.exponent(), value.kind()); } + /** Decimal methods accept the same exact operands as slots, but reject unsupported types. */ + @GenerateInline + @GenerateCached(false) + public abstract static class RequireDecimalNode extends Node { + public abstract PDecimal execute(Node inliningTarget, Object value); + + @Specialization + static PDecimal convert(Node inliningTarget, Object value, + @Cached ToDecimalNode toDecimal, + @Cached GetClassNode getClass, + @Cached TypeNodes.GetNameNode getName, + @Cached PRaiseNode raiseNode) { + PDecimal result = toDecimal.execute(inliningTarget, value); + if (result == null) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.CONVERSION_FROM_S_TO_DECIMAL_NOT_SUPPORTED, + getName.execute(inliningTarget, getClass.execute(inliningTarget, value))); + } + return result; + } + } + private static PDecimal compact(PythonLanguage language, boolean negative, long coefficient, long exponent) { return PFactory.createDecimal(language, negative, coefficient, exponent, Kind.FINITE); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalPowerMath.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalPowerMath.java index 129a895ef8..ebd9b18ad9 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalPowerMath.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalPowerMath.java @@ -209,6 +209,32 @@ static Value logarithm(BigInteger coefficient, long exponent, long places) { return new Value(approximation.signum() < 0, approximation.abs(), -places, Kind.FINITE); } + /** Approximate 10^places * log10(c * 10^e), with absolute error at most one. */ + static Value logarithm10(BigInteger c, long e, long places) { + long p = places + 2; + long f = e + digits(c); + if (f >= 1) { + f--; + } + BigInteger approximation; + if (p > 0) { + BigInteger scale = pow10(p); + long shift = e + p - f; + BigInteger scaled; + if (shift >= 0) { + scaled = c.multiply(pow10(shift)); + } else { + scaled = nearestDiv(c, pow10(-shift)); + } + BigInteger logSignificand = nearestDiv(integerLog(scaled, scale, p + 1).multiply(scale), logTen(p)); + approximation = BigInteger.valueOf(f).multiply(scale).add(logSignificand); + } else { + approximation = nearestDiv(BigInteger.valueOf(f), pow10(-p)); + } + approximation = nearestDiv(approximation, HUNDRED); + return new Value(approximation.signum() < 0, approximation.abs(), -places, Kind.FINITE); + } + static Value exponential(Value argument, long precision) { BigInteger coefficient = argument.coefficient(); if (argument.negative()) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java index d7dbab45b7..fa3a93b1f9 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java @@ -165,7 +165,11 @@ public String toScientificString(boolean capitals) { digits = largeCoefficient.toString(); } if (kind != Kind.FINITE) { - return sign + (kind == Kind.NAN ? "NaN" : "sNaN") + (digits.equals("0") ? "" : digits); + String result = sign + (kind == Kind.NAN ? "NaN" : "sNaN"); + if (!digits.equals("0")) { + result += digits; + } + return result; } // Moving the decimal point after the first digit adds digits.length() - 1 // to the stored exponent, giving the exponent of the most significant digit. @@ -187,6 +191,10 @@ public String toScientificString(boolean capitals) { if (digits.length() != 1) { significand = digits.charAt(0) + "." + digits.substring(1); } - return sign + significand + (capitals ? "E" : "e") + (adjustedExponent >= 0 ? "+" : "") + adjustedExponent; + String result = sign + significand + (capitals ? "E" : "e"); + if (adjustedExponent >= 0) { + result += "+"; + } + return result + adjustedExponent; } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java index dccd5cf3f8..87d497c4c3 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java @@ -1630,6 +1630,9 @@ public abstract class ErrorMessages { public static final TruffleString DECIMAL_SNAN_TO_FLOAT = tsLiteral("cannot convert signaling NaN to float"); public static final TruffleString DECIMAL_SNAN_HASH = tsLiteral("Cannot hash a signaling NaN value"); public static final TruffleString DECIMAL_INTEGER_ARGUMENT = tsLiteral("argument must be an integer"); + public static final TruffleString DECIMAL_FROM_FLOAT_ARGUMENT = tsLiteral("argument must be int or float"); + public static final TruffleString DECIMAL_INFINITY_TO_RATIO = tsLiteral("cannot convert Infinity to integer ratio"); + public static final TruffleString DECIMAL_NAN_TO_RATIO = tsLiteral("cannot convert NaN to integer ratio"); public static final TruffleString DECIMAL_FORMAT_N_DEPRECATED = tsLiteral("Format specifier 'N' is deprecated and slated for removal in Python 3.18"); public static final TruffleString DECIMAL_ROUND_DIGITS = tsLiteral("optional arg must be an integer"); public static final TruffleString DECIMAL_FORMAT_STRING = tsLiteral("format arg must be str"); From 571a32352653f7a940e59a25fba223c92c0e1c4f Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Fri, 9 Oct 2026 16:21:15 +0200 Subject: [PATCH 08/31] Implement remaining decimal context methods --- .../objects/decimal/DecimalArithmetic.java | 15 + .../objects/decimal/DecimalBuiltins.java | 711 +++++++----------- .../decimal/DecimalContextBuiltins.java | 611 +++++++++------ .../objects/decimal/DecimalNodes.java | 373 ++++++++- .../objects/decimal/DecimalUtils.java | 1 + .../graal/python/nodes/ErrorMessages.java | 1 + 6 files changed, 1046 insertions(+), 666 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java index 11b3f44d48..34c7f1e804 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java @@ -288,6 +288,21 @@ private Value finish(Value operand) { return finite(operand.negative(), coefficient, exponent); } + /** Apply context bounds and rounding without the sign or sNaN rules of unary plus. */ + @TruffleBoundary + static Result applyContext(PDecimal self, PDecimalContext context) { + DecimalArithmetic arithmetic = new DecimalArithmetic(context); + Value operand = value(self); + Value result; + if (operand.kind() == Kind.NAN || operand.kind() == Kind.SNAN) { + Value nan = arithmetic.fixNaN(operand); + result = new Value(nan.negative(), nan.coefficient(), nan.exponent(), operand.kind()); + } else { + result = arithmetic.finish(operand); + } + return new Result(result, arithmetic.status); + } + @TruffleBoundary static Result plus(PDecimal self, PDecimalContext context) { return applySign(self, context, self.isNegative()); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java index 499aa4a176..9c814462b9 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java @@ -63,11 +63,11 @@ import com.oracle.graal.python.builtins.modules.WarningsModuleBuiltins.WarnNode; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.PNotImplemented; -import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.builtins.objects.complex.ComplexBuiltins.ComplexValue; import com.oracle.graal.python.builtins.objects.complex.ComplexBuiltins.ToComplexValueNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.AbsDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.AddDecimalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.CreateDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.DivideDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.DivideIntDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.DivmodDecimalNode; @@ -84,9 +84,7 @@ import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.ConversionException; import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.Value; import com.oracle.graal.python.builtins.objects.decimal.PDecimal.Kind; -import com.oracle.graal.python.builtins.objects.floats.PFloat; import com.oracle.graal.python.builtins.objects.function.PKeyword; -import com.oracle.graal.python.builtins.objects.ints.PInt; import com.oracle.graal.python.builtins.objects.object.ObjectBuiltins; import com.oracle.graal.python.builtins.objects.type.TpSlots; import com.oracle.graal.python.builtins.objects.type.TypeNodes; @@ -97,19 +95,14 @@ import com.oracle.graal.python.lib.PyComplexCheckNode; import com.oracle.graal.python.lib.PyFloatAsDoubleNode; import com.oracle.graal.python.lib.PyFloatCheckNode; -import com.oracle.graal.python.lib.PyListCheckNode; import com.oracle.graal.python.lib.PyLongAsLongNode; import com.oracle.graal.python.lib.PyLongCheckNode; import com.oracle.graal.python.lib.PyObjectGetAttr; import com.oracle.graal.python.lib.PyObjectIsInstanceNode; -import com.oracle.graal.python.lib.PyTupleCheckNode; import com.oracle.graal.python.lib.PyUnicodeCheckNode; import com.oracle.graal.python.lib.RichCmpOp; import com.oracle.graal.python.nodes.ErrorMessages; -import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PRaiseNode; -import com.oracle.graal.python.nodes.builtins.ListNodes.GetListStorageNode; -import com.oracle.graal.python.nodes.builtins.TupleNodes.GetTupleStorage; import com.oracle.graal.python.nodes.call.CallNode; import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; import com.oracle.graal.python.nodes.function.PythonBuiltinNode; @@ -120,19 +113,11 @@ import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.nodes.util.CastToJavaBigIntegerNode; import com.oracle.graal.python.nodes.util.CastToJavaStringNode; -import com.oracle.graal.python.runtime.ExecutionContext.BoundaryCallContext; -import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; import com.oracle.graal.python.runtime.object.PFactory; -import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.dsl.Cached.Shared; -import com.oracle.truffle.api.dsl.Fallback; -import com.oracle.truffle.api.dsl.GenerateCached; -import com.oracle.truffle.api.dsl.GenerateInline; import com.oracle.truffle.api.dsl.GenerateNodeFactory; -import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.NodeFactory; import com.oracle.truffle.api.dsl.Specialization; @@ -163,6 +148,11 @@ protected List> getNodeFa return DecimalBuiltinsFactory.getFactories(); } + static PDecimal finishResult(VirtualFrame frame, Node inliningTarget, PDecimalContext context, DecimalArithmetic.Result result, RaiseSignalNode raiseSignal) { + DecimalContextUtils.addStatus(frame, context, result.status(), raiseSignal); + return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + } + @Builtin(name = "exp", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @GenerateNodeFactory public abstract static class ExpNode extends PythonBuiltinNode { @@ -173,14 +163,17 @@ static PDecimal exp(VirtualFrame frame, PDecimal self, Object context, @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); + return doExp(frame, inliningTarget, self, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doExp(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.exp(self, actualContext); + result = DecimalArithmetic.exp(self, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -194,14 +187,17 @@ static PDecimal ln(VirtualFrame frame, PDecimal self, Object context, @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); + return doLn(frame, inliningTarget, self, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doLn(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.ln(self, actualContext); + result = DecimalArithmetic.ln(self, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -215,14 +211,17 @@ static PDecimal log10(VirtualFrame frame, PDecimal self, Object context, @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); + return doLog10(frame, inliningTarget, self, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doLog10(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.log10(self, actualContext); + result = DecimalArithmetic.log10(self, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -236,14 +235,17 @@ static PDecimal nextMinus(VirtualFrame frame, PDecimal self, Object context, @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); + return doNextMinus(frame, inliningTarget, self, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doNextMinus(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.nextMinus(self, actualContext); + result = DecimalArithmetic.nextMinus(self, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -257,14 +259,17 @@ static PDecimal nextPlus(VirtualFrame frame, PDecimal self, Object context, @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); + return doNextPlus(frame, inliningTarget, self, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doNextPlus(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.nextPlus(self, actualContext); + result = DecimalArithmetic.nextPlus(self, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -278,14 +283,17 @@ static PDecimal normalize(VirtualFrame frame, PDecimal self, Object context, @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); + return doNormalize(frame, inliningTarget, self, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doNormalize(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.normalize(self, actualContext); + result = DecimalArithmetic.normalize(self, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -300,14 +308,7 @@ static PDecimal toIntegralValue(VirtualFrame frame, PDecimal self, Object roundi @Cached PRaiseNode raiseNode) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); DecimalRounding actualRounding = getRounding(rounding, inliningTarget, actualContext); - DecimalArithmetic.Result result; - try { - result = DecimalArithmetic.toIntegralValue(self, actualContext, actualRounding); - } catch (ArithmeticException e) { - throw raiseNode.raise(inliningTarget, MemoryError); - } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return ToIntegralValueNode.doToIntegralValue(frame, inliningTarget, self, actualContext, actualRounding, raiseSignal, raiseNode); } } @@ -322,14 +323,18 @@ static PDecimal toIntegralExact(VirtualFrame frame, PDecimal self, Object roundi @Cached PRaiseNode raiseNode) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); DecimalRounding actualRounding = getRounding(rounding, inliningTarget, actualContext); + return doToIntegralExact(frame, inliningTarget, self, actualContext, actualRounding, raiseSignal, raiseNode); + } + + static PDecimal doToIntegralExact(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, DecimalRounding rounding, RaiseSignalNode raiseSignal, + PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.toIntegralExact(self, actualContext, actualRounding); + result = DecimalArithmetic.toIntegralExact(self, context, rounding); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -344,14 +349,18 @@ static PDecimal toIntegralValue(VirtualFrame frame, PDecimal self, Object roundi @Cached PRaiseNode raiseNode) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); DecimalRounding actualRounding = getRounding(rounding, inliningTarget, actualContext); + return doToIntegralValue(frame, inliningTarget, self, actualContext, actualRounding, raiseSignal, raiseNode); + } + + static PDecimal doToIntegralValue(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, DecimalRounding rounding, RaiseSignalNode raiseSignal, + PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.toIntegralValue(self, actualContext, actualRounding); + result = DecimalArithmetic.toIntegralValue(self, context, rounding); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -365,14 +374,17 @@ static PDecimal sqrt(VirtualFrame frame, PDecimal self, Object context, @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); + return doSqrt(frame, inliningTarget, self, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doSqrt(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.sqrt(self, actualContext); + result = DecimalArithmetic.sqrt(self, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -388,14 +400,17 @@ static PDecimal compare(VirtualFrame frame, PDecimal self, Object other, Object @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doCompare(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doCompare(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.compare(self, operand, actualContext); + result = DecimalArithmetic.compare(self, operand, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -411,14 +426,17 @@ static PDecimal compareSignal(VirtualFrame frame, PDecimal self, Object other, O @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doCompareSignal(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doCompareSignal(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.compareSignal(self, operand, actualContext); + result = DecimalArithmetic.compareSignal(self, operand, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -434,14 +452,17 @@ static PDecimal max(VirtualFrame frame, PDecimal self, Object other, Object cont @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doMax(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doMax(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.max(self, operand, actualContext); + result = DecimalArithmetic.max(self, operand, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -457,14 +478,17 @@ static PDecimal maxMag(VirtualFrame frame, PDecimal self, Object other, Object c @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doMaxMag(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doMaxMag(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.maxMag(self, operand, actualContext); + result = DecimalArithmetic.maxMag(self, operand, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -480,14 +504,17 @@ static PDecimal min(VirtualFrame frame, PDecimal self, Object other, Object cont @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doMin(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doMin(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.min(self, operand, actualContext); + result = DecimalArithmetic.min(self, operand, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -503,14 +530,17 @@ static PDecimal minMag(VirtualFrame frame, PDecimal self, Object other, Object c @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doMinMag(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doMinMag(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.minMag(self, operand, actualContext); + result = DecimalArithmetic.minMag(self, operand, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -526,14 +556,17 @@ static PDecimal nextToward(VirtualFrame frame, PDecimal self, Object other, Obje @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doNextToward(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doNextToward(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.nextToward(self, operand, actualContext); + result = DecimalArithmetic.nextToward(self, operand, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -560,17 +593,7 @@ static PDecimal quantize(VirtualFrame frame, PDecimal self, Object exp, Object r throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.CONVERSION_FROM_S_TO_DECIMAL_NOT_SUPPORTED, getName.execute(inliningTarget, getClass.execute(inliningTarget, exp))); } - if (quantum.getKind() == Kind.FINITE) { - return operation.execute(frame, inliningTarget, self, quantum.getExponent(), actualContext, actualRounding); - } - DecimalArithmetic.Result result; - try { - result = DecimalArithmetic.quantize(self, quantum, actualContext, actualRounding); - } catch (ArithmeticException e) { - throw raiseNode.raise(inliningTarget, MemoryError); - } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return QuantizeDecimalNode.doQuantize(frame, inliningTarget, self, quantum, actualContext, actualRounding, operation, raiseSignal, raiseNode); } } @@ -586,14 +609,17 @@ static PDecimal remainderNear(VirtualFrame frame, PDecimal self, Object other, O @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doRemainderNear(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doRemainderNear(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.remainderNear(self, operand, actualContext); + result = DecimalArithmetic.remainderNear(self, operand, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -610,14 +636,17 @@ static PDecimal fma(VirtualFrame frame, PDecimal self, Object other, Object thir PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); PDecimal addend = convert.execute(inliningTarget, third); + return doFma(frame, inliningTarget, self, operand, addend, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doFma(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimal addend, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.fma(self, operand, addend, actualContext); + result = DecimalArithmetic.fma(self, operand, addend, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -702,7 +731,11 @@ static boolean check(PDecimal self, Object context, @Bind Node inliningTarget, @Cached GetContextNode getContext) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); - return isNonzeroFinite(self) && adjustedExponent(self) >= actualContext.getEmin(); + return doCheck(self, actualContext); + } + + static boolean doCheck(PDecimal self, PDecimalContext context) { + return isNonzeroFinite(self) && adjustedExponent(self) >= context.getEmin(); } } @@ -714,7 +747,11 @@ static boolean check(PDecimal self, Object context, @Bind Node inliningTarget, @Cached GetContextNode getContext) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); - return isNonzeroFinite(self) && adjustedExponent(self) < actualContext.getEmin(); + return doCheck(self, actualContext); + } + + static boolean doCheck(PDecimal self, PDecimalContext context) { + return isNonzeroFinite(self) && adjustedExponent(self) < context.getEmin(); } } @@ -751,6 +788,10 @@ public abstract static class RadixNode extends PythonUnaryBuiltinNode { @Specialization static PDecimal radix(@SuppressWarnings("unused") PDecimal self, @Bind PythonLanguage language) { + return createRadix(language); + } + + static PDecimal createRadix(PythonLanguage language) { return PFactory.createDecimal(language, 10); } } @@ -785,14 +826,17 @@ static PDecimal logb(VirtualFrame frame, PDecimal self, Object context, @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); + return doLogb(frame, inliningTarget, self, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doLogb(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.logb(self, actualContext); + result = DecimalArithmetic.logb(self, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -806,14 +850,17 @@ static PDecimal logicalInvert(VirtualFrame frame, PDecimal self, Object context, @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); + return doLogicalInvert(frame, inliningTarget, self, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doLogicalInvert(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.logicalInvert(self, actualContext); + result = DecimalArithmetic.logicalInvert(self, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -827,6 +874,25 @@ static TruffleString classify(PDecimal self, Object context, PDecimalContext actualContext = getContext.execute(inliningTarget, context); return numberClass(self, actualContext); } + + static TruffleString numberClass(PDecimal self, PDecimalContext context) { + if (self.getKind() == Kind.SNAN) { + return T_SNAN; + } + if (self.getKind() == Kind.NAN) { + return T_NAN; + } + if (self.getKind() == Kind.INFINITY) { + return self.isNegative() ? T_NEGATIVE_INFINITY : T_POSITIVE_INFINITY; + } + if (isZero(self)) { + return self.isNegative() ? T_NEGATIVE_ZERO : T_POSITIVE_ZERO; + } + if (adjustedExponent(self) < context.getEmin()) { + return self.isNegative() ? T_NEGATIVE_SUBNORMAL : T_POSITIVE_SUBNORMAL; + } + return self.isNegative() ? T_NEGATIVE_NORMAL : T_POSITIVE_NORMAL; + } } @Builtin(name = "to_eng_string", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "context"}) @@ -838,7 +904,40 @@ static TruffleString format(PDecimal self, Object context, @Cached GetContextNode getContext, @Cached TruffleString.FromJavaStringNode fromJavaString) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); - return fromJavaString.execute(engineeringString(self, actualContext.getCapitals()), TS_ENCODING); + return doFormat(self, actualContext, fromJavaString); + } + + static TruffleString doFormat(PDecimal self, PDecimalContext context, TruffleString.FromJavaStringNode fromJavaString) { + return fromJavaString.execute(engineeringString(self, context.getCapitals()), TS_ENCODING); + } + + @TruffleBoundary + private static String engineeringString(PDecimal self, boolean capitals) { + if (self.getKind() != Kind.FINITE || self.getExponent() <= 0 && adjustedExponent(self) >= -6) { + return self.toScientificString(capitals); + } + String digits = self.getCoefficient().toString(); + long left = self.getExponent() + digits.length(); + int point; + if (isZero(self)) { + point = Math.floorMod(left + 1, 3) - 1; + } else { + point = Math.floorMod(left - 1, 3) + 1; + } + String significand; + if (point <= 0) { + significand = "0." + "0".repeat(-point) + digits; + } else if (point >= digits.length()) { + significand = digits + "0".repeat(point - digits.length()); + } else { + significand = digits.substring(0, point) + "." + digits.substring(point); + } + long exponent = left - point; + String result = (self.isNegative() ? "-" : "") + significand; + if (exponent != 0) { + result += (capitals ? "E" : "e") + (exponent > 0 ? "+" : "") + exponent; + } + return result; } } @@ -854,14 +953,17 @@ static PDecimal compareTotal(VirtualFrame frame, PDecimal self, Object other, Ob @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doCompareTotal(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doCompareTotal(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { result = DecimalArithmetic.compareTotal(self, operand); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -877,14 +979,17 @@ static PDecimal compareTotalMag(VirtualFrame frame, PDecimal self, Object other, @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doCompareTotalMag(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doCompareTotalMag(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { result = DecimalArithmetic.compareTotalMag(self, operand); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -898,7 +1003,11 @@ static PDecimal apply(PDecimal self, Object other, Object context, @Cached RequireDecimalNode convert) { getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); - return copySign(PythonLanguage.get(inliningTarget), self, operand.isNegative()); + return doCopySign(PythonLanguage.get(inliningTarget), self, operand); + } + + static PDecimal doCopySign(PythonLanguage language, PDecimal self, PDecimal other) { + return copySign(language, self, other.isNegative()); } } @@ -914,6 +1023,16 @@ static boolean apply(PDecimal self, Object other, Object context, PDecimal operand = convert.execute(inliningTarget, other); return sameQuantum(self, operand); } + + static boolean sameQuantum(PDecimal self, PDecimal other) { + if (self.getKind() == Kind.FINITE && other.getKind() == Kind.FINITE) { + return self.getExponent() == other.getExponent(); + } + if (self.getKind() == Kind.INFINITY || other.getKind() == Kind.INFINITY) { + return self.getKind() == other.getKind(); + } + return self.getKind() != Kind.FINITE && other.getKind() != Kind.FINITE; + } } @Builtin(name = "logical_and", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "other", "context"}) @@ -928,14 +1047,17 @@ static PDecimal logicalAnd(VirtualFrame frame, PDecimal self, Object other, Obje @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doLogicalAnd(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doLogicalAnd(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.logicalAnd(self, operand, actualContext); + result = DecimalArithmetic.logicalAnd(self, operand, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -951,14 +1073,17 @@ static PDecimal logicalOr(VirtualFrame frame, PDecimal self, Object other, Objec @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doLogicalOr(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doLogicalOr(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.logicalOr(self, operand, actualContext); + result = DecimalArithmetic.logicalOr(self, operand, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -974,14 +1099,17 @@ static PDecimal logicalXor(VirtualFrame frame, PDecimal self, Object other, Obje @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doLogicalXor(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doLogicalXor(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.logicalXor(self, operand, actualContext); + result = DecimalArithmetic.logicalXor(self, operand, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -997,14 +1125,17 @@ static PDecimal rotate(VirtualFrame frame, PDecimal self, Object other, Object c @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doRotate(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doRotate(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.rotate(self, operand, actualContext); + result = DecimalArithmetic.rotate(self, operand, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -1020,14 +1151,17 @@ static PDecimal scaleb(VirtualFrame frame, PDecimal self, Object other, Object c @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doScaleb(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doScaleb(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.scaleb(self, operand, actualContext); + result = DecimalArithmetic.scaleb(self, operand, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -1043,14 +1177,17 @@ static PDecimal shift(VirtualFrame frame, PDecimal self, Object other, Object co @Cached RequireDecimalNode convert) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); PDecimal operand = convert.execute(inliningTarget, other); + return doShift(frame, inliningTarget, self, operand, actualContext, raiseSignal, raiseNode); + } + + static PDecimal doShift(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal operand, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { - result = DecimalArithmetic.shift(self, operand, actualContext); + result = DecimalArithmetic.shift(self, operand, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, actualContext, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } @@ -1067,6 +1204,15 @@ static Object convert(VirtualFrame frame, Object cls, Object value, @Cached IsBuiltinClassExactProfile clsProfile, @Cached CallNode callNode, @Cached PRaiseNode raiseNode) { + PDecimal result = fromFloat(frame, inliningTarget, value, longCheck, floatCheck, toDecimal, asDouble, raiseNode); + if (clsProfile.profileClass(inliningTarget, cls, PythonBuiltinClassType.PDecimal)) { + return result; + } + return callNode.execute(frame, cls, result); + } + + static PDecimal fromFloat(VirtualFrame frame, Node inliningTarget, Object value, PyLongCheckNode longCheck, + PyFloatCheckNode floatCheck, RequireDecimalNode toDecimal, PyFloatAsDoubleNode asDouble, PRaiseNode raiseNode) { PDecimal result; if (longCheck.execute(inliningTarget, value)) { result = toDecimal.execute(inliningTarget, value); @@ -1075,10 +1221,7 @@ static Object convert(VirtualFrame frame, Object cls, Object value, } else { throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_FROM_FLOAT_ARGUMENT); } - if (clsProfile.profileClass(inliningTarget, cls, PythonBuiltinClassType.PDecimal)) { - return result; - } - return callNode.execute(frame, cls, result); + return result; } } @@ -1152,64 +1295,6 @@ private static PDecimal copySign(PythonLanguage language, PDecimal self, boolean return PFactory.createDecimal(language, negative, self.getCoefficient(), self.getExponent(), self.getKind()); } - private static boolean sameQuantum(PDecimal self, PDecimal other) { - if (self.getKind() == Kind.FINITE && other.getKind() == Kind.FINITE) { - return self.getExponent() == other.getExponent(); - } - if (self.getKind() == Kind.INFINITY || other.getKind() == Kind.INFINITY) { - return self.getKind() == other.getKind(); - } - return self.getKind() != Kind.FINITE && other.getKind() != Kind.FINITE; - } - - private static TruffleString numberClass(PDecimal self, PDecimalContext context) { - if (self.getKind() == Kind.SNAN) { - return T_SNAN; - } - if (self.getKind() == Kind.NAN) { - return T_NAN; - } - if (self.getKind() == Kind.INFINITY) { - return self.isNegative() ? T_NEGATIVE_INFINITY : T_POSITIVE_INFINITY; - } - if (isZero(self)) { - return self.isNegative() ? T_NEGATIVE_ZERO : T_POSITIVE_ZERO; - } - if (adjustedExponent(self) < context.getEmin()) { - return self.isNegative() ? T_NEGATIVE_SUBNORMAL : T_POSITIVE_SUBNORMAL; - } - return self.isNegative() ? T_NEGATIVE_NORMAL : T_POSITIVE_NORMAL; - } - - @TruffleBoundary - private static String engineeringString(PDecimal self, boolean capitals) { - if (self.getKind() != Kind.FINITE || self.getExponent() <= 0 && adjustedExponent(self) >= -6) { - return self.toScientificString(capitals); - } - String digits = self.getCoefficient().toString(); - long left = self.getExponent() + digits.length(); - int point; - if (isZero(self)) { - point = Math.floorMod(left + 1, 3) - 1; - } else { - point = Math.floorMod(left - 1, 3) + 1; - } - String significand; - if (point <= 0) { - significand = "0." + "0".repeat(-point) + digits; - } else if (point >= digits.length()) { - significand = digits + "0".repeat(point - digits.length()); - } else { - significand = digits.substring(0, point) + "." + digits.substring(point); - } - long exponent = left - point; - String result = (self.isNegative() ? "-" : "") + significand; - if (exponent != 0) { - result += (capitals ? "E" : "e") + (exponent > 0 ? "+" : "") + exponent; - } - return result; - } - @TruffleBoundary private static Object[] tupleArguments(PythonLanguage language, PDecimal self) { String text = self.getCoefficient().toString(); @@ -1463,7 +1548,7 @@ public abstract static class TpNewNode extends PythonTernaryBuiltinNode { @Specialization static PDecimal construct(VirtualFrame frame, Object cls, Object value, Object context, @Bind Node inliningTarget, - @Cached(inline = true) CreateDecimalNode createDecimal, + @Cached CreateDecimalNode createDecimal, @Cached RaiseSignalNode raiseSignal, @Cached GetContextNode getContext, @Cached InlinedBranchProfile errorProfile, @@ -1482,253 +1567,6 @@ static PDecimal construct(VirtualFrame frame, Object cls, Object value, Object c } } - @GenerateInline - @GenerateCached(false) - @GenerateUncached - @ImportStatic(PGuards.class) - abstract static class CreateDecimalNode extends Node { - abstract PDecimal execute(VirtualFrame frame, Node inliningTarget, Object cls, Object value) throws ConversionException; - - @Specialization(guards = "isNoValue(value)") - static PDecimal doDefault(@SuppressWarnings("unused") Node inliningTarget, Object cls, @SuppressWarnings("unused") PNone value, - @Shared @Cached TypeNodes.GetInstanceShape getShape) { - return PFactory.createDecimal(cls, getShape.execute(cls), 0L); - } - - @Specialization - static PDecimal doDecimal(Node inliningTarget, Object cls, PDecimal value, - @Shared @Cached GetClassNode getClass, - @Shared @Cached IsBuiltinClassExactProfile clsProfile, - @Shared @Cached IsBuiltinClassExactProfile valueProfile, - @Shared @Cached TypeNodes.GetInstanceShape getShape) { - if (clsProfile.profileClass(inliningTarget, cls, PythonBuiltinClassType.PDecimal) && - valueProfile.profileClass(inliningTarget, getClass.execute(inliningTarget, value), PythonBuiltinClassType.PDecimal)) { - return value; - } - return PFactory.createDecimal(cls, getShape.execute(cls), value); - } - - @Specialization(guards = "unicodeCheck.execute(inliningTarget, value)") - static PDecimal doString(Node inliningTarget, Object cls, Object value, - @SuppressWarnings("unused") @Shared @Cached PyUnicodeCheckNode unicodeCheck, - @Cached CastToJavaStringNode asString, - @Shared @Cached TypeNodes.GetInstanceShape getShape) throws ConversionException { - return create(cls, DecimalUtils.parse(asString.execute(value)), getShape); - } - - @Specialization - static PDecimal doInt(@SuppressWarnings("unused") Node inliningTarget, Object cls, int value, - @Shared @Cached TypeNodes.GetInstanceShape getShape) { - return PFactory.createDecimal(cls, getShape.execute(cls), value); - } - - @Specialization - static PDecimal doLong(@SuppressWarnings("unused") Node inliningTarget, Object cls, long value, - @Shared @Cached TypeNodes.GetInstanceShape getShape) { - return PFactory.createDecimal(cls, getShape.execute(cls), value); - } - - @Specialization - static PDecimal doPInt(@SuppressWarnings("unused") Node inliningTarget, Object cls, PInt value, - @Shared @Cached TypeNodes.GetInstanceShape getShape) { - return create(cls, DecimalUtils.fromInteger(value.getValue()), getShape); - } - - @Specialization(guards = "longCheck.execute(inliningTarget, value)") - static PDecimal doIntGeneric(Node inliningTarget, Object cls, Object value, - @SuppressWarnings("unused") @Shared @Cached PyLongCheckNode longCheck, - @Shared @Cached CastToJavaBigIntegerNode asBigInteger, - @Shared @Cached TypeNodes.GetInstanceShape getShape) { - return create(cls, DecimalUtils.fromInteger(asBigInteger.execute(inliningTarget, value)), getShape); - } - - @Specialization - static PDecimal doDouble(Node inliningTarget, Object cls, double value, - @Shared @Cached InlinedBranchProfile nonCompactProfile, - @Shared @Cached TypeNodes.GetInstanceShape getShape) { - return fromDouble(inliningTarget, cls, value, getShape, nonCompactProfile); - } - - @Specialization - static PDecimal doPFloat(Node inliningTarget, Object cls, PFloat value, - @Shared @Cached InlinedBranchProfile nonCompactProfile, - @Shared @Cached TypeNodes.GetInstanceShape getShape) { - return fromDouble(inliningTarget, cls, value.getValue(), getShape, nonCompactProfile); - } - - @Specialization(guards = "floatCheck.execute(inliningTarget, value)") - static PDecimal doFloatGeneric(VirtualFrame frame, Node inliningTarget, Object cls, Object value, - @SuppressWarnings("unused") @Shared @Cached PyFloatCheckNode floatCheck, - @Shared @Cached PyFloatAsDoubleNode asDouble, - @Shared @Cached InlinedBranchProfile nonCompactProfile, - @Shared @Cached TypeNodes.GetInstanceShape getShape) { - return fromDouble(inliningTarget, cls, asDouble.execute(frame, inliningTarget, value), getShape, nonCompactProfile); - } - - @Specialization(guards = "tupleCheck.execute(inliningTarget, value)") - static PDecimal doTuple(VirtualFrame frame, Node inliningTarget, Object cls, Object value, - @SuppressWarnings("unused") @Shared @Cached PyTupleCheckNode tupleCheck, - @Shared @Cached GetTupleStorage getTupleStorage, - @Shared @Cached("createFor($node)") BoundaryCallData boundaryCallData, - @Shared @Cached TypeNodes.GetInstanceShape getShape) throws ConversionException { - SequenceStorage storage = getTupleStorage.execute(inliningTarget, value); - Object state = BoundaryCallContext.enter(frame, boundaryCallData); - try { - return create(cls, fromSequence(storage), getShape); - } finally { - BoundaryCallContext.exit(frame, boundaryCallData, state); - } - } - - @Specialization(guards = "listCheck.execute(inliningTarget, value)") - static PDecimal doList(VirtualFrame frame, Node inliningTarget, Object cls, Object value, - @SuppressWarnings("unused") @Shared @Cached PyListCheckNode listCheck, - @Shared @Cached GetListStorageNode getListStorage, - @Shared @Cached("createFor($node)") BoundaryCallData boundaryCallData, - @Shared @Cached TypeNodes.GetInstanceShape getShape) throws ConversionException { - SequenceStorage storage = getListStorage.execute(inliningTarget, value); - Object state = BoundaryCallContext.enter(frame, boundaryCallData); - try { - return create(cls, fromSequence(storage), getShape); - } finally { - BoundaryCallContext.exit(frame, boundaryCallData, state); - } - } - - @Fallback - static PDecimal doUnsupported(Node inliningTarget, @SuppressWarnings("unused") Object cls, Object value, - @Shared @Cached GetClassNode getClass, - @Shared @Cached TypeNodes.GetNameNode getName, - @Shared @Cached PRaiseNode raiseNode) { - throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.CONVERSION_FROM_S_TO_DECIMAL_NOT_SUPPORTED, - getName.execute(inliningTarget, getClass.execute(inliningTarget, value))); - } - - private static PDecimal create(Object cls, Value value, TypeNodes.GetInstanceShape getShape) { - return PFactory.createDecimal(cls, getShape.execute(cls), value.negative(), value.coefficient(), value.exponent(), value.kind()); - } - - private static PDecimal fromDouble(Node inliningTarget, Object cls, double value, TypeNodes.GetInstanceShape getShape, InlinedBranchProfile nonCompactProfile) { - // IEEE 754 binary64 stores the sign in bit 63, an 11-bit biased exponent - // in bits 62..52, and a 52-bit fraction in bits 51..0. The masks select - long bits = Double.doubleToRawLongBits(value); - boolean negative = bits < 0; - int exponentBits = (int) ((bits >>> 52) & 0x7ff); - long coefficient = bits & 0x000fffffffffffffL; - // An all-ones exponent encodes infinity if the fraction is zero, else NaN. - if (exponentBits == 0x7ff) { - Kind kind = coefficient == 0 ? Kind.INFINITY : Kind.NAN; - return PFactory.createDecimal(cls, getShape.execute(cls), kind == Kind.INFINITY && negative, 0L, 0, kind); - } - if (exponentBits == 0 && coefficient == 0) { - return PFactory.createDecimal(cls, getShape.execute(cls), negative, 0L, 0, Kind.FINITE); - } - int binaryExponent; - if (exponentBits == 0) { - // Subnormals have no implicit leading 1. Their integer fraction is - // scaled by 2^(1 - 1023 - 52) = 2^-1074 (bias 1023, 52 fraction bits). - binaryExponent = -1074; - } else { - // Normal values have an implicit leading 1 at bit 52. Subtract the - // exponent bias (1023) and fraction width (52) to scale this integer. - coefficient |= 1L << 52; - binaryExponent = exponentBits - 1023 - 52; - } - // Reduce coefficient * 2^binaryExponent to an odd coefficient. - int trailingZeros = Long.numberOfTrailingZeros(coefficient); - coefficient >>>= trailingZeros; - binaryExponent += trailingZeros; - if (binaryExponent >= 0) { - // A compact positive coefficient has 63 usable bits. Check the shift - // count and remaining capacity before multiplying by 2^binaryExponent. - if (binaryExponent < Long.SIZE - 1 && coefficient <= (Long.MAX_VALUE >>> binaryExponent)) { - return PFactory.createDecimal(cls, getShape.execute(cls), negative, coefficient << binaryExponent, 0, Kind.FINITE); - } - } else { - // n / 2^k = (n * 5^k) * 10^-k because 2 * 5 = 10, with no decimal - // rounding. Long.MAX_VALUE / 5 is the largest coefficient that can - // survive the next multiplication by 5 without signed-long overflow. - for (int i = 0; i < -binaryExponent; i++) { - if (coefficient > Long.MAX_VALUE / 5) { - nonCompactProfile.enter(inliningTarget); - return create(cls, DecimalUtils.fromFloat(value), getShape); - } - coefficient *= 5; - } - return PFactory.createDecimal(cls, getShape.execute(cls), negative, coefficient, binaryExponent, Kind.FINITE); - } - nonCompactProfile.enter(inliningTarget); - return create(cls, DecimalUtils.fromFloat(value), getShape); - } - - @TruffleBoundary - private static Value fromSequence(SequenceStorage triple) throws ConversionException { - PRaiseNode raiseNode = PRaiseNode.getUncached(); - // Decimal triples are (sign, digits, exponent), at indices 0, 1, and 2. - if (triple.length() != 3) { - throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_SEQUENCE_LENGTH); - } - Object sign = SequenceStorageNodes.GetItemScalarNode.executeUncached(triple, 0); - if (!PyLongCheckNode.executeUncached(sign)) { - throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_SIGN); - } - long signValue = PyLongAsLongNode.executeUncached(sign); - // The tuple format uses 0 for positive and 1 for negative, including zero. - if (signValue != 0 && signValue != 1) { - throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_SIGN); - } - Object exp = SequenceStorageNodes.GetItemScalarNode.executeUncached(triple, 2); - Kind kind = Kind.FINITE; - long exponent = 0; - if (PyUnicodeCheckNode.executeUncached(exp)) { - kind = switch (CastToJavaStringNode.getUncached().execute(exp)) { - case "F" -> Kind.INFINITY; - case "n" -> Kind.NAN; - case "N" -> Kind.SNAN; - default -> throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_SPECIAL_EXPONENT); - }; - } else { - if (!PyLongCheckNode.executeUncached(exp)) { - throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_EXPONENT); - } - exponent = PyLongAsLongNode.executeUncached(exp); - } - Object digits = SequenceStorageNodes.GetItemScalarNode.executeUncached(triple, 1); - SequenceStorage storage; - if (PyTupleCheckNode.executeUncached(digits)) { - storage = GetTupleStorage.executeUncached(digits); - } else if (PyListCheckNode.executeUncached(digits)) { - storage = GetListStorageNode.executeUncached(digits); - } else { - throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_COEFFICIENT); - } - StringBuilder coefficient = new StringBuilder(storage.length()); - for (int i = 0; i < storage.length(); i++) { - Object digit = SequenceStorageNodes.GetItemScalarNode.executeUncached(storage, i); - if (!PyLongCheckNode.executeUncached(digit)) { - throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_COEFFICIENT); - } - long n = PyLongAsLongNode.executeUncached(digit); - // Each coefficient element is one base-10 digit, in the range 0..9. - if (n < 0 || n > 9) { - throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_COEFFICIENT); - } - if (kind != Kind.INFINITY) { - coefficient.append((char) ('0' + n)); - } - } - BigInteger integer; - if (coefficient.isEmpty()) { - integer = BigInteger.ZERO; - } else { - integer = new BigInteger(coefficient.toString()); - } - if (kind == Kind.FINITE) { - return DecimalUtils.finite(signValue != 0, integer, exponent); - } - return new Value(signValue != 0, integer, 0, kind); - } - } } @Slot(value = SlotKind.tp_repr, isComplex = true) @@ -1756,6 +1594,10 @@ static TruffleString str(PDecimal self, @Bind Node inliningTarget, @Cached TruffleString.FromJavaStringNode fromJavaString) { boolean capitals = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget).getCapitals(); + return doStr(self, capitals, fromJavaString); + } + + static TruffleString doStr(PDecimal self, boolean capitals, TruffleString.FromJavaStringNode fromJavaString) { return fromJavaString.execute(self.toScientificString(capitals), TS_ENCODING); } } @@ -2133,14 +1975,17 @@ static Object power(VirtualFrame frame, Object left, Object right, Object mod, return PNotImplemented.NOT_IMPLEMENTED; } PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + return doPower(frame, inliningTarget, a, b, modulo, context, raiseSignal, raiseNode); + } + + static PDecimal doPower(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimal modulo, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { DecimalArithmetic.Result result; try { result = DecimalArithmetic.power(a, b, modulo, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - DecimalContextUtils.addStatus(frame, context, result.status(), raiseSignal); - return DecimalNodes.create(PythonLanguage.get(inliningTarget), result.value()); + return finishResult(frame, inliningTarget, context, result, raiseSignal); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java index 22cf4da345..19b38b9c8d 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java @@ -41,7 +41,7 @@ package com.oracle.graal.python.builtins.objects.decimal; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.AttributeError; -import static com.oracle.graal.python.builtins.PythonBuiltinClassType.NotImplementedError; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.MemoryError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import static com.oracle.graal.python.builtins.objects.decimal.DecimalContextUtils.T_FLAGS; import static com.oracle.graal.python.builtins.objects.decimal.DecimalContextUtils.T_TRAPS; @@ -59,15 +59,23 @@ import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.PythonBuiltins; import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.RaiseSignalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.RequireDecimalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.ConversionException; +import com.oracle.graal.python.builtins.objects.decimal.PDecimal.Kind; import com.oracle.graal.python.builtins.objects.function.PKeyword; import com.oracle.graal.python.builtins.objects.object.ObjectBuiltins; import com.oracle.graal.python.builtins.objects.str.StringNodes.CastToTruffleStringChecked1Node; +import com.oracle.graal.python.builtins.objects.str.StringUtils; import com.oracle.graal.python.builtins.objects.type.TpSlots; import com.oracle.graal.python.builtins.objects.type.TypeNodes; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotGetAttr.GetAttrBuiltinNode; import com.oracle.graal.python.builtins.objects.type.slots.TpSlotSetAttr.SetAttrBuiltinNode; +import com.oracle.graal.python.lib.PyFloatAsDoubleNode; +import com.oracle.graal.python.lib.PyFloatCheckNode; import com.oracle.graal.python.lib.PyLongAsLongNode; import com.oracle.graal.python.lib.PyLongCheckNode; +import com.oracle.graal.python.lib.PyUnicodeCheckNode; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; @@ -78,6 +86,7 @@ import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonVarargsBuiltinNode; import com.oracle.graal.python.nodes.object.GetClassNode; +import com.oracle.graal.python.nodes.util.CastToTruffleStringNode; import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; @@ -86,6 +95,7 @@ import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.profiles.InlinedBranchProfile; import com.oracle.truffle.api.strings.TruffleString; @CoreFunctions(extendClasses = PythonBuiltinClassType.PDecimalContext) @@ -97,15 +107,30 @@ protected List> getNodeFa return DecimalContextBuiltinsFactory.getFactories(); } + private static boolean invalidContextString(TruffleString value, TruffleString.CodePointLengthNode codePointLengthNode, TruffleString.CodePointAtIndexUTF32Node codePointAtIndexNode) { + // Context conversion does not strip whitespace or underscores like Decimal does. + int length = codePointLengthNode.execute(value, TS_ENCODING); + for (int i = 0; i < length; i++) { + int codePoint = codePointAtIndexNode.execute(value, i); + if (codePoint == '_' || StringUtils.isUnicodeWhitespace(codePoint)) { + return true; + } + } + return false; + } + @Builtin(name = "abs", minNumOfPositionalArgs = 2) @GenerateNodeFactory public abstract static class AbsNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal abs(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached InlinedBranchProfile nonCompactProfile, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.abs. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalNodes.AbsDecimalNode.doAbs(frame, inliningTarget, operand, self, nonCompactProfile, raiseSignal, raiseNode); } } @@ -113,11 +138,13 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class ExpNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal exp(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.exp. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.ExpNode.doExp(frame, inliningTarget, operand, self, raiseSignal, raiseNode); } } @@ -125,11 +152,13 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class LnNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal ln(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.ln. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.LnNode.doLn(frame, inliningTarget, operand, self, raiseSignal, raiseNode); } } @@ -137,11 +166,13 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class Log10Node extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal log10(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.log10. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.Log10Node.doLog10(frame, inliningTarget, operand, self, raiseSignal, raiseNode); } } @@ -149,11 +180,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class MinusNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal minus(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached InlinedBranchProfile nonCompactProfile, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.minus. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalNodes.MinusDecimalNode.doMinus(frame, inliningTarget, operand, self, nonCompactProfile, raiseSignal, raiseNode); } } @@ -161,11 +195,13 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class NextMinusNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal nextMinus(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.next_minus. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.NextMinusNode.doNextMinus(frame, inliningTarget, operand, self, raiseSignal, raiseNode); } } @@ -173,11 +209,13 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class NextPlusNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal nextPlus(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.next_plus. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.NextPlusNode.doNextPlus(frame, inliningTarget, operand, self, raiseSignal, raiseNode); } } @@ -185,11 +223,13 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class NormalizeNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal normalize(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.normalize. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.NormalizeNode.doNormalize(frame, inliningTarget, operand, self, raiseSignal, raiseNode); } } @@ -197,11 +237,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class PlusNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal plus(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached InlinedBranchProfile nonCompactProfile, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.plus. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalNodes.PlusDecimalNode.doPlus(frame, inliningTarget, operand, self, nonCompactProfile, raiseSignal, raiseNode); } } @@ -209,11 +252,13 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class ToIntegralNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal toIntegralValue(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.to_integral. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.ToIntegralValueNode.doToIntegralValue(frame, inliningTarget, operand, self, self.getRounding(), raiseSignal, raiseNode); } } @@ -221,11 +266,13 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class ToIntegralExactNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal toIntegralExact(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.to_integral_exact. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.ToIntegralExactNode.doToIntegralExact(frame, inliningTarget, operand, self, self.getRounding(), raiseSignal, raiseNode); } } @@ -233,11 +280,13 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class ToIntegralValueNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal toIntegralValue(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.to_integral_value. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.ToIntegralValueNode.doToIntegralValue(frame, inliningTarget, operand, self, self.getRounding(), raiseSignal, raiseNode); } } @@ -245,11 +294,13 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class SqrtNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal sqrt(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.sqrt. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.SqrtNode.doSqrt(frame, inliningTarget, operand, self, raiseSignal, raiseNode); } } @@ -257,11 +308,15 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class AddNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal add(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached InlinedBranchProfile nonCompactProfile, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.add. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalNodes.AddDecimalNode.doAdd(frame, inliningTarget, operand, other, self, nonCompactProfile, raiseSignal, raiseNode); } } @@ -269,11 +324,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class CompareNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal compare(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.compare. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.CompareNode.doCompare(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -281,11 +339,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class CompareSignalNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal compareSignal(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.compare_signal. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.CompareSignalNode.doCompareSignal(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -293,11 +354,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class DivideNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal divide(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.divide. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalNodes.DivideDecimalNode.doDivide(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -305,11 +369,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class DivideIntNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal divideInt(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.divide_int. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalNodes.DivideIntDecimalNode.doDivideInt(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -317,11 +384,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class DivmodNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static Object divmod(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.divmod. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal left = convert.execute(inliningTarget, a); + PDecimal right = convert.execute(inliningTarget, b); + return DecimalNodes.DivmodDecimalNode.doDivmod(frame, inliningTarget, left, right, self, raiseSignal, raiseNode); } } @@ -329,11 +399,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class MaxNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal max(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.max. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.MaxNode.doMax(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -341,11 +414,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class MaxMagNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal maxMag(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.max_mag. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.MaxMagNode.doMaxMag(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -353,11 +429,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class MinNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal min(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.min. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.MinNode.doMin(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -365,11 +444,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class MinMagNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal minMag(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.min_mag. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.MinMagNode.doMinMag(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -377,11 +459,15 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class MultiplyNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal multiply(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached InlinedBranchProfile nonCompactProfile, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.multiply. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalNodes.MultiplyDecimalNode.doMultiply(frame, inliningTarget, operand, other, self, nonCompactProfile, raiseSignal, raiseNode); } } @@ -389,11 +475,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class NextTowardNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal nextToward(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.next_toward. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.NextTowardNode.doNextToward(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -401,11 +490,15 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class QuantizeNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal quantize(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached DecimalNodes.QuantizeDecimalNode operation, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.quantize. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalNodes.QuantizeDecimalNode.doQuantize(frame, inliningTarget, operand, other, self, self.getRounding(), operation, raiseSignal, raiseNode); } } @@ -413,11 +506,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class RemainderNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal remainder(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.remainder. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalNodes.RemainderDecimalNode.doRemainder(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -425,11 +521,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class RemainderNearNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal remainderNear(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.remainder_near. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.RemainderNearNode.doRemainderNear(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -437,23 +536,31 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class SubtractNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal subtract(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached InlinedBranchProfile nonCompactProfile, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.subtract. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalNodes.SubtractDecimalNode.doSubtract(frame, inliningTarget, operand, other, self, nonCompactProfile, raiseSignal, raiseNode); } } - @Builtin(name = "power", minNumOfPositionalArgs = 3, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "a", "b", "modulo"}, takesVarKeywordArgs = true) + @Builtin(name = "power", minNumOfPositionalArgs = 3, numOfPositionalOnlyArgs = 1, parameterNames = {"self", "a", "b", "modulo"}) @GenerateNodeFactory public abstract static class PowerNode extends PythonBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object a, Object b, Object modulo, PKeyword[] kwargs, + static PDecimal power(VirtualFrame frame, PDecimalContext self, Object a, Object b, Object modulo, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.power. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal base = convert.execute(inliningTarget, a); + PDecimal exponent = convert.execute(inliningTarget, b); + PDecimal modulus = modulo instanceof PNone ? null : convert.execute(inliningTarget, modulo); + return DecimalBuiltins.NbPowerNode.doPower(frame, inliningTarget, base, exponent, modulus, self, raiseSignal, raiseNode); } } @@ -461,11 +568,15 @@ static Object doMock(VirtualFrame frame, Object self, Object a, Object b, Object @GenerateNodeFactory public abstract static class FmaNode extends PythonQuaternaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, Object arg3, + static PDecimal fma(VirtualFrame frame, PDecimalContext self, Object a, Object b, Object c, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.fma. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + PDecimal third = convert.execute(inliningTarget, c); + return DecimalBuiltins.FmaNode.doFma(frame, inliningTarget, operand, other, third, self, raiseSignal, raiseNode); } } @@ -492,7 +603,7 @@ static long get(PDecimalContext self) { public abstract static class RadixNode extends PythonUnaryBuiltinNode { @Specialization static PDecimal radix(@SuppressWarnings("unused") PDecimalContext self, @Bind PythonLanguage language) { - return PFactory.createDecimal(language, 10L); + return DecimalBuiltins.RadixNode.createRadix(language); } } @@ -500,11 +611,13 @@ static PDecimal radix(@SuppressWarnings("unused") PDecimalContext self, @Bind Py @GenerateNodeFactory public abstract static class IsCanonicalNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static boolean canonical(@SuppressWarnings("unused") PDecimalContext self, Object a, @Bind Node inliningTarget, @Cached PRaiseNode raiseNode) { - // TODO implement Context.is_canonical. - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (!(a instanceof PDecimal operand)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_ARGUMENT); + } + return DecimalBuiltins.IsCanonicalNode.check(operand); } } @@ -512,11 +625,11 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class IsFiniteNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static boolean check(@SuppressWarnings("unused") PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.is_finite. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.IsFiniteNode.check(operand); } } @@ -524,11 +637,11 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class IsInfiniteNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static boolean check(@SuppressWarnings("unused") PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.is_infinite. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.IsInfiniteNode.check(operand); } } @@ -536,11 +649,11 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class IsNanNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static boolean check(@SuppressWarnings("unused") PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.is_nan. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.IsNanNode.check(operand); } } @@ -548,11 +661,11 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class IsNormalNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static boolean check(PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.is_normal. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.IsNormalNode.doCheck(operand, self); } } @@ -560,11 +673,11 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class IsQnanNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static boolean check(@SuppressWarnings("unused") PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.is_qnan. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.IsQnanNode.check(operand); } } @@ -572,11 +685,11 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class IsSignedNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static boolean check(@SuppressWarnings("unused") PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.is_signed. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.IsSignedNode.check(operand); } } @@ -584,11 +697,11 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class IsSnanNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static boolean check(@SuppressWarnings("unused") PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.is_snan. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.IsSnanNode.check(operand); } } @@ -596,11 +709,11 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class IsSubnormalNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static boolean check(PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.is_subnormal. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.IsSubnormalNode.doCheck(operand, self); } } @@ -608,11 +721,11 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class IsZeroNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static boolean check(@SuppressWarnings("unused") PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.is_zero. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.IsZeroNode.check(operand); } } @@ -620,11 +733,19 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class ApplyNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal applyContext(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context._apply. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.applyContext(operand, self); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + return DecimalBuiltins.finishResult(frame, inliningTarget, self, result, raiseSignal); } } @@ -632,11 +753,13 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class CanonicalNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal canonical(@SuppressWarnings("unused") PDecimalContext self, Object a, @Bind Node inliningTarget, @Cached PRaiseNode raiseNode) { - // TODO implement Context.canonical. - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (!(a instanceof PDecimal operand)) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_ARGUMENT); + } + return DecimalBuiltins.CanonicalNode.identity(operand); } } @@ -644,11 +767,11 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class CopyAbsNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal copy(@SuppressWarnings("unused") PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.copy_abs. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.CopyAbsNode.copy(operand, PythonLanguage.get(inliningTarget)); } } @@ -656,11 +779,11 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class CopyDecimalNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal copy(@SuppressWarnings("unused") PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.copy_decimal. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.CopyNode.copy(operand); } } @@ -668,11 +791,11 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class CopyNegateNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal copy(@SuppressWarnings("unused") PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.copy_negate. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.CopyNegateNode.copy(operand, PythonLanguage.get(inliningTarget)); } } @@ -680,11 +803,13 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class LogbNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal logb(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.logb. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.LogbNode.doLogb(frame, inliningTarget, operand, self, raiseSignal, raiseNode); } } @@ -692,11 +817,13 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class LogicalInvertNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal logicalInvert(VirtualFrame frame, PDecimalContext self, Object a, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.logical_invert. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.LogicalInvertNode.doLogicalInvert(frame, inliningTarget, operand, self, raiseSignal, raiseNode); } } @@ -704,11 +831,10 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class NumberClassNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static TruffleString classify(PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.number_class. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + return DecimalBuiltins.NumberClassNode.numberClass(convert.execute(inliningTarget, a), self); } } @@ -716,11 +842,12 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class ToSciStringNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static TruffleString format(PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.to_sci_string. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert, + @Cached TruffleString.FromJavaStringNode fromJavaString) { + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.TpStrNode.doStr(operand, self.getCapitals(), fromJavaString); } } @@ -728,11 +855,12 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class ToEngStringNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static TruffleString format(PDecimalContext self, Object a, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.to_eng_string. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert, + @Cached TruffleString.FromJavaStringNode fromJavaString) { + PDecimal operand = convert.execute(inliningTarget, a); + return DecimalBuiltins.ToEngStringNode.doFormat(operand, self, fromJavaString); } } @@ -740,11 +868,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class CompareTotalNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal compareTotal(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.compare_total. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.CompareTotalNode.doCompareTotal(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -752,11 +883,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class CompareTotalMagNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal compareTotalMag(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.compare_total_mag. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.CompareTotalMagNode.doCompareTotalMag(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -764,11 +898,12 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class CopySignNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal apply(@SuppressWarnings("unused") PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.copy_sign. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + PDecimal left = convert.execute(inliningTarget, a); + PDecimal right = convert.execute(inliningTarget, b); + return DecimalBuiltins.CopySignNode.doCopySign(PythonLanguage.get(inliningTarget), left, right); } } @@ -776,11 +911,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class LogicalAndNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal logicalAnd(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.logical_and. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.LogicalAndNode.doLogicalAnd(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -788,11 +926,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class LogicalOrNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal logicalOr(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.logical_or. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.LogicalOrNode.doLogicalOr(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -800,11 +941,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class LogicalXorNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal logicalXor(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.logical_xor. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.LogicalXorNode.doLogicalXor(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -812,11 +956,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class RotateNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal rotate(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.rotate. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.RotateNode.doRotate(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -824,11 +971,12 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class SameQuantumNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static boolean apply(@SuppressWarnings("unused") PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.same_quantum. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached RequireDecimalNode convert) { + PDecimal left = convert.execute(inliningTarget, a); + PDecimal right = convert.execute(inliningTarget, b); + return DecimalBuiltins.SameQuantumNode.sameQuantum(left, right); } } @@ -836,11 +984,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class ScalebNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal scaleb(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.scaleb. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.ScalebNode.doScaleb(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -848,11 +999,14 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, @GenerateNodeFactory public abstract static class ShiftNode extends PythonTernaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, Object arg2, + static PDecimal shift(VirtualFrame frame, PDecimalContext self, Object a, Object b, @Bind Node inliningTarget, + @Cached RequireDecimalNode convert, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.shift. - throw raiseNode.raise(inliningTarget, NotImplementedError); + PDecimal operand = convert.execute(inliningTarget, a); + PDecimal other = convert.execute(inliningTarget, b); + return DecimalBuiltins.ShiftNode.doShift(frame, inliningTarget, operand, other, self, raiseSignal, raiseNode); } } @@ -918,11 +1072,37 @@ static PDecimalContext copy(PDecimalContext self, @GenerateNodeFactory public abstract static class CreateDecimalNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal create(VirtualFrame frame, PDecimalContext self, Object value, @Bind Node inliningTarget, + @Cached DecimalNodes.CreateDecimalNode createDecimal, + @Cached PyUnicodeCheckNode unicodeCheck, + @Cached CastToTruffleStringNode asString, + @Cached TruffleString.CodePointLengthNode codePointLengthNode, + @Cached TruffleString.CodePointAtIndexUTF32Node codePointAtIndexNode, + @Cached PyFloatCheckNode floatCheck, + @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { - // TODO implement Context.create_decimal. - throw raiseNode.raise(inliningTarget, NotImplementedError); + if (floatCheck.execute(inliningTarget, value)) { + DecimalContextUtils.addStatus(frame, self, DecimalContextUtils.conditionMask(DecimalExceptionTypes.FloatOperation), raiseSignal); + } + DecimalArithmetic.Result result; + try { + if (unicodeCheck.execute(inliningTarget, value) && invalidContextString(asString.castKnownString(inliningTarget, value), codePointLengthNode, codePointAtIndexNode)) { + throw new ConversionException(DecimalExceptionTypes.ConversionSyntax); + } + PDecimal operand = createDecimal.execute(frame, inliningTarget, PythonBuiltinClassType.PDecimal, value); + if ((operand.getKind() == Kind.NAN || operand.getKind() == Kind.SNAN) && + operand.getCoefficient().signum() != 0 && DecimalUtils.digits(operand.getCoefficient()) > self.getPrecision() - self.getClamp()) { + throw new ConversionException(DecimalExceptionTypes.ConversionSyntax); + } + result = DecimalArithmetic.applyContext(operand, self); + } catch (ConversionException e) { + DecimalContextUtils.addStatus(frame, self, DecimalContextUtils.conditionMask(e.condition), raiseSignal); + return PFactory.createDecimal(PythonLanguage.get(inliningTarget), false, 0L, 0L, Kind.NAN); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + return DecimalBuiltins.finishResult(frame, inliningTarget, self, result, raiseSignal); } } @@ -930,11 +1110,22 @@ static Object doMock(VirtualFrame frame, Object self, Object arg1, @GenerateNodeFactory public abstract static class CreateDecimalFromFloatNode extends PythonBinaryBuiltinNode { @Specialization - static Object doMock(VirtualFrame frame, Object self, Object arg1, + static PDecimal create(VirtualFrame frame, PDecimalContext self, Object value, @Bind Node inliningTarget, - @Cached PRaiseNode raiseNode) { - // TODO implement Context.create_decimal_from_float. - throw raiseNode.raise(inliningTarget, NotImplementedError); + @Cached PyLongCheckNode longCheck, + @Cached PyFloatCheckNode floatCheck, + @Cached RequireDecimalNode convert, + @Cached PyFloatAsDoubleNode asDouble, + @Cached RaiseSignalNode raiseSignal, + @Cached PRaiseNode raiseNode) { + PDecimal operand = DecimalBuiltins.FromFloatNode.fromFloat(frame, inliningTarget, value, longCheck, floatCheck, convert, asDouble, raiseNode); + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.applyContext(operand, self); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + return DecimalBuiltins.finishResult(frame, inliningTarget, self, result, raiseSignal); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java index 5691e652d0..789df11901 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java @@ -42,32 +42,54 @@ import static com.oracle.graal.python.builtins.PythonBuiltinClassType.MemoryError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; + +import java.math.BigInteger; import com.oracle.graal.python.PythonLanguage; +import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.PNotImplemented; import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; +import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.builtins.objects.decimal.DecimalArithmetic.Result; -import com.oracle.graal.python.builtins.objects.decimal.DecimalArithmetic.ResultWithRemainder; +import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.ConversionException; import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.Value; import com.oracle.graal.python.builtins.objects.decimal.PDecimal.Kind; +import com.oracle.graal.python.builtins.objects.floats.PFloat; import com.oracle.graal.python.builtins.objects.ints.PInt; import com.oracle.graal.python.builtins.objects.list.PList; import com.oracle.graal.python.builtins.objects.type.TypeNodes; +import com.oracle.graal.python.lib.PyFloatAsDoubleNode; +import com.oracle.graal.python.lib.PyFloatCheckNode; +import com.oracle.graal.python.lib.PyListCheckNode; +import com.oracle.graal.python.lib.PyLongAsLongNode; import com.oracle.graal.python.lib.PyLongCheckNode; +import com.oracle.graal.python.lib.PyTupleCheckNode; +import com.oracle.graal.python.lib.PyUnicodeCheckNode; import com.oracle.graal.python.nodes.ErrorMessages; +import com.oracle.graal.python.nodes.PGuards; import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.builtins.ListNodes.GetListStorageNode; +import com.oracle.graal.python.nodes.builtins.TupleNodes.GetTupleStorage; import com.oracle.graal.python.nodes.call.CallNode; +import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinClassExactProfile; import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.nodes.util.CastToJavaBigIntegerNode; +import com.oracle.graal.python.nodes.util.CastToJavaStringNode; +import com.oracle.graal.python.runtime.ExecutionContext.BoundaryCallContext; +import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.GenerateCached; import com.oracle.truffle.api.dsl.GenerateInline; import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; @@ -184,6 +206,254 @@ static PDecimal convert(Node inliningTarget, Object value, } } + @GenerateInline + @GenerateCached(false) + @GenerateUncached + @ImportStatic(PGuards.class) + public abstract static class CreateDecimalNode extends Node { + public abstract PDecimal execute(VirtualFrame frame, Node inliningTarget, Object cls, Object value) throws ConversionException; + + @Specialization(guards = "isNoValue(value)") + static PDecimal doDefault(@SuppressWarnings("unused") Node inliningTarget, Object cls, @SuppressWarnings("unused") PNone value, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return PFactory.createDecimal(cls, getShape.execute(cls), 0L); + } + + @Specialization + static PDecimal doDecimal(Node inliningTarget, Object cls, PDecimal value, + @Shared @Cached GetClassNode getClass, + @Shared @Cached IsBuiltinClassExactProfile clsProfile, + @Shared @Cached IsBuiltinClassExactProfile valueProfile, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + if (clsProfile.profileClass(inliningTarget, cls, PythonBuiltinClassType.PDecimal) && + valueProfile.profileClass(inliningTarget, getClass.execute(inliningTarget, value), PythonBuiltinClassType.PDecimal)) { + return value; + } + return PFactory.createDecimal(cls, getShape.execute(cls), value); + } + + @Specialization(guards = "unicodeCheck.execute(inliningTarget, value)") + static PDecimal doString(Node inliningTarget, Object cls, Object value, + @SuppressWarnings("unused") @Shared @Cached PyUnicodeCheckNode unicodeCheck, + @Cached CastToJavaStringNode asString, + @Shared @Cached TypeNodes.GetInstanceShape getShape) throws ConversionException { + return createDecimal(cls, DecimalUtils.parse(asString.execute(value)), getShape); + } + + @Specialization + static PDecimal doInt(@SuppressWarnings("unused") Node inliningTarget, Object cls, int value, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return PFactory.createDecimal(cls, getShape.execute(cls), value); + } + + @Specialization + static PDecimal doLong(@SuppressWarnings("unused") Node inliningTarget, Object cls, long value, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return PFactory.createDecimal(cls, getShape.execute(cls), value); + } + + @Specialization + static PDecimal doPInt(@SuppressWarnings("unused") Node inliningTarget, Object cls, PInt value, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return createDecimal(cls, DecimalUtils.fromInteger(value.getValue()), getShape); + } + + @Specialization(guards = "longCheck.execute(inliningTarget, value)") + static PDecimal doIntGeneric(Node inliningTarget, Object cls, Object value, + @SuppressWarnings("unused") @Shared @Cached PyLongCheckNode longCheck, + @Shared @Cached CastToJavaBigIntegerNode asBigInteger, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return createDecimal(cls, DecimalUtils.fromInteger(asBigInteger.execute(inliningTarget, value)), getShape); + } + + @Specialization + static PDecimal doDouble(Node inliningTarget, Object cls, double value, + @Shared @Cached InlinedBranchProfile nonCompactProfile, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return fromDouble(inliningTarget, cls, value, getShape, nonCompactProfile); + } + + @Specialization + static PDecimal doPFloat(Node inliningTarget, Object cls, PFloat value, + @Shared @Cached InlinedBranchProfile nonCompactProfile, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return fromDouble(inliningTarget, cls, value.getValue(), getShape, nonCompactProfile); + } + + @Specialization(guards = "floatCheck.execute(inliningTarget, value)") + static PDecimal doFloatGeneric(VirtualFrame frame, Node inliningTarget, Object cls, Object value, + @SuppressWarnings("unused") @Shared @Cached PyFloatCheckNode floatCheck, + @Shared @Cached PyFloatAsDoubleNode asDouble, + @Shared @Cached InlinedBranchProfile nonCompactProfile, + @Shared @Cached TypeNodes.GetInstanceShape getShape) { + return fromDouble(inliningTarget, cls, asDouble.execute(frame, inliningTarget, value), getShape, nonCompactProfile); + } + + @Specialization(guards = "tupleCheck.execute(inliningTarget, value)") + static PDecimal doTuple(VirtualFrame frame, Node inliningTarget, Object cls, Object value, + @SuppressWarnings("unused") @Shared @Cached PyTupleCheckNode tupleCheck, + @Shared @Cached GetTupleStorage getTupleStorage, + @Shared @Cached("createFor($node)") BoundaryCallData boundaryCallData, + @Shared @Cached TypeNodes.GetInstanceShape getShape) throws ConversionException { + SequenceStorage storage = getTupleStorage.execute(inliningTarget, value); + Object state = BoundaryCallContext.enter(frame, boundaryCallData); + try { + return createDecimal(cls, fromSequence(storage), getShape); + } finally { + BoundaryCallContext.exit(frame, boundaryCallData, state); + } + } + + @Specialization(guards = "listCheck.execute(inliningTarget, value)") + static PDecimal doList(VirtualFrame frame, Node inliningTarget, Object cls, Object value, + @SuppressWarnings("unused") @Shared @Cached PyListCheckNode listCheck, + @Shared @Cached GetListStorageNode getListStorage, + @Shared @Cached("createFor($node)") BoundaryCallData boundaryCallData, + @Shared @Cached TypeNodes.GetInstanceShape getShape) throws ConversionException { + SequenceStorage storage = getListStorage.execute(inliningTarget, value); + Object state = BoundaryCallContext.enter(frame, boundaryCallData); + try { + return createDecimal(cls, fromSequence(storage), getShape); + } finally { + BoundaryCallContext.exit(frame, boundaryCallData, state); + } + } + + @Fallback + static PDecimal doUnsupported(Node inliningTarget, @SuppressWarnings("unused") Object cls, Object value, + @Shared @Cached GetClassNode getClass, + @Shared @Cached TypeNodes.GetNameNode getName, + @Shared @Cached PRaiseNode raiseNode) { + throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.CONVERSION_FROM_S_TO_DECIMAL_NOT_SUPPORTED, + getName.execute(inliningTarget, getClass.execute(inliningTarget, value))); + } + + private static PDecimal createDecimal(Object cls, Value value, TypeNodes.GetInstanceShape getShape) { + return PFactory.createDecimal(cls, getShape.execute(cls), value.negative(), value.coefficient(), value.exponent(), value.kind()); + } + + private static PDecimal fromDouble(Node inliningTarget, Object cls, double value, TypeNodes.GetInstanceShape getShape, InlinedBranchProfile nonCompactProfile) { + // IEEE 754 binary64 stores the sign in bit 63, an 11-bit biased exponent + // in bits 62..52, and a 52-bit fraction in bits 51..0. The masks select + long bits = Double.doubleToRawLongBits(value); + boolean negative = bits < 0; + int exponentBits = (int) ((bits >>> 52) & 0x7ff); + long coefficient = bits & 0x000fffffffffffffL; + // An all-ones exponent encodes infinity if the fraction is zero, else NaN. + if (exponentBits == 0x7ff) { + Kind kind = coefficient == 0 ? Kind.INFINITY : Kind.NAN; + return PFactory.createDecimal(cls, getShape.execute(cls), kind == Kind.INFINITY && negative, 0L, 0, kind); + } + if (exponentBits == 0 && coefficient == 0) { + return PFactory.createDecimal(cls, getShape.execute(cls), negative, 0L, 0, Kind.FINITE); + } + int binaryExponent; + if (exponentBits == 0) { + // Subnormals have no implicit leading 1. Their integer fraction is + // scaled by 2^(1 - 1023 - 52) = 2^-1074 (bias 1023, 52 fraction bits). + binaryExponent = -1074; + } else { + // Normal values have an implicit leading 1 at bit 52. Subtract the + // exponent bias (1023) and fraction width (52) to scale this integer. + coefficient |= 1L << 52; + binaryExponent = exponentBits - 1023 - 52; + } + // Reduce coefficient * 2^binaryExponent to an odd coefficient. + int trailingZeros = Long.numberOfTrailingZeros(coefficient); + coefficient >>>= trailingZeros; + binaryExponent += trailingZeros; + if (binaryExponent >= 0) { + // A compact positive coefficient has 63 usable bits. Check the shift + // count and remaining capacity before multiplying by 2^binaryExponent. + if (binaryExponent < Long.SIZE - 1 && coefficient <= (Long.MAX_VALUE >>> binaryExponent)) { + return PFactory.createDecimal(cls, getShape.execute(cls), negative, coefficient << binaryExponent, 0, Kind.FINITE); + } + } else { + // n / 2^k = (n * 5^k) * 10^-k because 2 * 5 = 10, with no decimal + // rounding. Long.MAX_VALUE / 5 is the largest coefficient that can + // survive the next multiplication by 5 without signed-long overflow. + for (int i = 0; i < -binaryExponent; i++) { + if (coefficient > Long.MAX_VALUE / 5) { + nonCompactProfile.enter(inliningTarget); + return createDecimal(cls, DecimalUtils.fromFloat(value), getShape); + } + coefficient *= 5; + } + return PFactory.createDecimal(cls, getShape.execute(cls), negative, coefficient, binaryExponent, Kind.FINITE); + } + nonCompactProfile.enter(inliningTarget); + return createDecimal(cls, DecimalUtils.fromFloat(value), getShape); + } + + @TruffleBoundary + private static Value fromSequence(SequenceStorage triple) throws ConversionException { + PRaiseNode raiseNode = PRaiseNode.getUncached(); + // Decimal triples are (sign, digits, exponent), at indices 0, 1, and 2. + if (triple.length() != 3) { + throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_SEQUENCE_LENGTH); + } + Object sign = SequenceStorageNodes.GetItemScalarNode.executeUncached(triple, 0); + if (!PyLongCheckNode.executeUncached(sign)) { + throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_SIGN); + } + long signValue = PyLongAsLongNode.executeUncached(sign); + // The tuple format uses 0 for positive and 1 for negative, including zero. + if (signValue != 0 && signValue != 1) { + throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_SIGN); + } + Object exp = SequenceStorageNodes.GetItemScalarNode.executeUncached(triple, 2); + Kind kind = Kind.FINITE; + long exponent = 0; + if (PyUnicodeCheckNode.executeUncached(exp)) { + kind = switch (CastToJavaStringNode.getUncached().execute(exp)) { + case "F" -> Kind.INFINITY; + case "n" -> Kind.NAN; + case "N" -> Kind.SNAN; + default -> throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_SPECIAL_EXPONENT); + }; + } else { + if (!PyLongCheckNode.executeUncached(exp)) { + throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_EXPONENT); + } + exponent = PyLongAsLongNode.executeUncached(exp); + } + Object digits = SequenceStorageNodes.GetItemScalarNode.executeUncached(triple, 1); + SequenceStorage storage; + if (PyTupleCheckNode.executeUncached(digits)) { + storage = GetTupleStorage.executeUncached(digits); + } else if (PyListCheckNode.executeUncached(digits)) { + storage = GetListStorageNode.executeUncached(digits); + } else { + throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_COEFFICIENT); + } + StringBuilder coefficient = new StringBuilder(storage.length()); + for (int i = 0; i < storage.length(); i++) { + Object digit = SequenceStorageNodes.GetItemScalarNode.executeUncached(storage, i); + if (!PyLongCheckNode.executeUncached(digit)) { + throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_COEFFICIENT); + } + long n = PyLongAsLongNode.executeUncached(digit); + // Each coefficient element is one base-10 digit, in the range 0..9. + if (n < 0 || n > 9) { + throw raiseNode.raise(null, ValueError, ErrorMessages.DECIMAL_COEFFICIENT); + } + if (kind != Kind.INFINITY) { + coefficient.append((char) ('0' + n)); + } + } + BigInteger integer; + if (coefficient.isEmpty()) { + integer = BigInteger.ZERO; + } else { + integer = new BigInteger(coefficient.toString()); + } + if (kind == Kind.FINITE) { + return DecimalUtils.finite(signValue != 0, integer, exponent); + } + return new Value(signValue != 0, integer, 0, kind); + } + } + private static PDecimal compact(PythonLanguage language, boolean negative, long coefficient, long exponent) { return PFactory.createDecimal(language, negative, coefficient, exponent, Kind.FINITE); } @@ -199,20 +469,25 @@ static PDecimal plus(VirtualFrame frame, Node inliningTarget, PDecimal self, @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + return doPlus(frame, inliningTarget, self, context, nonCompactProfile, raiseSignal, raiseNode); + } + + static PDecimal doPlus(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, InlinedBranchProfile nonCompactProfile, RaiseSignalNode raiseSignal, + PRaiseNode raiseNode) { PythonLanguage language = PythonLanguage.get(inliningTarget); PDecimal fast = compactUnary(language, self, context, self.isNegative()); if (fast != null) { return fast; } nonCompactProfile.enter(inliningTarget); - Result result; + DecimalArithmetic.Result result; try { result = DecimalArithmetic.plus(self, context); } catch (ArithmeticException e) { // BigInteger cannot allocate coefficients beyond its int-sized representation. throw raiseNode.raise(inliningTarget, MemoryError); } - return finishResult(frame, language, context, result, raiseSignal); + return finishResult(frame, PythonLanguage.get(inliningTarget), context, result, raiseSignal); } } @@ -227,20 +502,25 @@ static PDecimal minus(VirtualFrame frame, Node inliningTarget, PDecimal self, @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + return doMinus(frame, inliningTarget, self, context, nonCompactProfile, raiseSignal, raiseNode); + } + + static PDecimal doMinus(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, InlinedBranchProfile nonCompactProfile, RaiseSignalNode raiseSignal, + PRaiseNode raiseNode) { PythonLanguage language = PythonLanguage.get(inliningTarget); PDecimal fast = compactUnary(language, self, context, !self.isNegative()); if (fast != null) { return fast; } nonCompactProfile.enter(inliningTarget); - Result result; + DecimalArithmetic.Result result; try { result = DecimalArithmetic.minus(self, context); } catch (ArithmeticException e) { // BigInteger cannot allocate coefficients beyond its int-sized representation. throw raiseNode.raise(inliningTarget, MemoryError); } - return finishResult(frame, language, context, result, raiseSignal); + return finishResult(frame, PythonLanguage.get(inliningTarget), context, result, raiseSignal); } } @@ -255,20 +535,25 @@ static PDecimal abs(VirtualFrame frame, Node inliningTarget, PDecimal self, @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + return doAbs(frame, inliningTarget, self, context, nonCompactProfile, raiseSignal, raiseNode); + } + + static PDecimal doAbs(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, InlinedBranchProfile nonCompactProfile, RaiseSignalNode raiseSignal, + PRaiseNode raiseNode) { PythonLanguage language = PythonLanguage.get(inliningTarget); PDecimal fast = compactUnary(language, self, context, false); if (fast != null) { return fast; } nonCompactProfile.enter(inliningTarget); - Result result; + DecimalArithmetic.Result result; try { result = DecimalArithmetic.abs(self, context); } catch (ArithmeticException e) { // BigInteger cannot allocate coefficients beyond its int-sized representation. throw raiseNode.raise(inliningTarget, MemoryError); } - return finishResult(frame, language, context, result, raiseSignal); + return finishResult(frame, PythonLanguage.get(inliningTarget), context, result, raiseSignal); } } @@ -291,19 +576,24 @@ static Object add(VirtualFrame frame, Node inliningTarget, Object left, Object r return PNotImplemented.NOT_IMPLEMENTED; } PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + return doAdd(frame, inliningTarget, a, b, context, nonCompactProfile, raiseSignal, raiseNode); + } + + static PDecimal doAdd(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimalContext context, InlinedBranchProfile nonCompactProfile, RaiseSignalNode raiseSignal, + PRaiseNode raiseNode) { PythonLanguage language = PythonLanguage.get(inliningTarget); PDecimal fast = compactAdd(language, a, b, context, b.isNegative()); if (fast != null) { return fast; } nonCompactProfile.enter(inliningTarget); - Result result; + DecimalArithmetic.Result result; try { result = DecimalArithmetic.add(a, b, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - return finishResult(frame, language, context, result, raiseSignal); + return finishResult(frame, PythonLanguage.get(inliningTarget), context, result, raiseSignal); } } @@ -326,19 +616,24 @@ static Object subtract(VirtualFrame frame, Node inliningTarget, Object left, Obj return PNotImplemented.NOT_IMPLEMENTED; } PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + return doSubtract(frame, inliningTarget, a, b, context, nonCompactProfile, raiseSignal, raiseNode); + } + + static PDecimal doSubtract(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimalContext context, InlinedBranchProfile nonCompactProfile, RaiseSignalNode raiseSignal, + PRaiseNode raiseNode) { PythonLanguage language = PythonLanguage.get(inliningTarget); PDecimal fast = compactAdd(language, a, b, context, !b.isNegative()); if (fast != null) { return fast; } nonCompactProfile.enter(inliningTarget); - Result result; + DecimalArithmetic.Result result; try { result = DecimalArithmetic.subtract(a, b, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - return finishResult(frame, language, context, result, raiseSignal); + return finishResult(frame, PythonLanguage.get(inliningTarget), context, result, raiseSignal); } } @@ -361,19 +656,24 @@ static Object multiply(VirtualFrame frame, Node inliningTarget, Object left, Obj return PNotImplemented.NOT_IMPLEMENTED; } PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + return doMultiply(frame, inliningTarget, a, b, context, nonCompactProfile, raiseSignal, raiseNode); + } + + static PDecimal doMultiply(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimalContext context, InlinedBranchProfile nonCompactProfile, RaiseSignalNode raiseSignal, + PRaiseNode raiseNode) { PythonLanguage language = PythonLanguage.get(inliningTarget); PDecimal fast = compactMultiply(language, a, b, context); if (fast != null) { return fast; } nonCompactProfile.enter(inliningTarget); - Result result; + DecimalArithmetic.Result result; try { result = DecimalArithmetic.multiply(a, b, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - return finishResult(frame, language, context, result, raiseSignal); + return finishResult(frame, PythonLanguage.get(inliningTarget), context, result, raiseSignal); } } @@ -395,14 +695,17 @@ static Object divide(VirtualFrame frame, Node inliningTarget, Object left, Objec return PNotImplemented.NOT_IMPLEMENTED; } PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); - Result result; + return doDivide(frame, inliningTarget, a, b, context, raiseSignal, raiseNode); + } + + static PDecimal doDivide(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { + DecimalArithmetic.Result result; try { result = DecimalArithmetic.divide(a, b, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - PythonLanguage language = PythonLanguage.get(inliningTarget); - return finishResult(frame, language, context, result, raiseSignal); + return finishResult(frame, PythonLanguage.get(inliningTarget), context, result, raiseSignal); } } @@ -424,14 +727,17 @@ static Object divideInt(VirtualFrame frame, Node inliningTarget, Object left, Ob return PNotImplemented.NOT_IMPLEMENTED; } PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); - Result result; + return doDivideInt(frame, inliningTarget, a, b, context, raiseSignal, raiseNode); + } + + static PDecimal doDivideInt(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { + DecimalArithmetic.Result result; try { result = DecimalArithmetic.divideInt(a, b, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - PythonLanguage language = PythonLanguage.get(inliningTarget); - return finishResult(frame, language, context, result, raiseSignal); + return finishResult(frame, PythonLanguage.get(inliningTarget), context, result, raiseSignal); } } @@ -453,14 +759,17 @@ static Object remainder(VirtualFrame frame, Node inliningTarget, Object left, Ob return PNotImplemented.NOT_IMPLEMENTED; } PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); - Result result; + return doRemainder(frame, inliningTarget, a, b, context, raiseSignal, raiseNode); + } + + static PDecimal doRemainder(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { + DecimalArithmetic.Result result; try { result = DecimalArithmetic.remainder(a, b, context); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); } - PythonLanguage language = PythonLanguage.get(inliningTarget); - return finishResult(frame, language, context, result, raiseSignal); + return finishResult(frame, PythonLanguage.get(inliningTarget), context, result, raiseSignal); } } @@ -482,7 +791,11 @@ static Object divmod(VirtualFrame frame, Node inliningTarget, Object left, Objec return PNotImplemented.NOT_IMPLEMENTED; } PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); - ResultWithRemainder result; + return doDivmod(frame, inliningTarget, a, b, context, raiseSignal, raiseNode); + } + + static Object doDivmod(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { + DecimalArithmetic.ResultWithRemainder result; try { result = DecimalArithmetic.divmod(a, b, context); } catch (ArithmeticException e) { @@ -693,6 +1006,20 @@ static PDecimal quantize(VirtualFrame frame, Node inliningTarget, PDecimal self, DecimalContextUtils.addStatus(frame, context, result.status(), raiseSignal); return create(language, result.value()); } + + static PDecimal doQuantize(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal quantum, PDecimalContext context, DecimalRounding rounding, QuantizeDecimalNode operation, + RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { + if (quantum.getKind() == Kind.FINITE) { + return operation.execute(frame, inliningTarget, self, quantum.getExponent(), context, rounding); + } + DecimalArithmetic.Result result; + try { + result = DecimalArithmetic.quantize(self, quantum, context, rounding); + } catch (ArithmeticException e) { + throw raiseNode.raise(inliningTarget, MemoryError); + } + return finishResult(frame, PythonLanguage.get(inliningTarget), context, result, raiseSignal); + } } /** A divisor of zero denotes 10**places outside the compact coefficient range. */ diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java index 82faef90b8..634a70a2d2 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java @@ -94,6 +94,7 @@ static int digits(long coefficient) { return count; } + @TruffleBoundary static int digits(BigInteger value) { BigInteger magnitude = value.abs(); int bits = magnitude.bitLength(); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java index 87d497c4c3..410f9a9f28 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java @@ -1630,6 +1630,7 @@ public abstract class ErrorMessages { public static final TruffleString DECIMAL_SNAN_TO_FLOAT = tsLiteral("cannot convert signaling NaN to float"); public static final TruffleString DECIMAL_SNAN_HASH = tsLiteral("Cannot hash a signaling NaN value"); public static final TruffleString DECIMAL_INTEGER_ARGUMENT = tsLiteral("argument must be an integer"); + public static final TruffleString DECIMAL_ARGUMENT = tsLiteral("argument must be a Decimal"); public static final TruffleString DECIMAL_FROM_FLOAT_ARGUMENT = tsLiteral("argument must be int or float"); public static final TruffleString DECIMAL_INFINITY_TO_RATIO = tsLiteral("cannot convert Infinity to integer ratio"); public static final TruffleString DECIMAL_NAN_TO_RATIO = tsLiteral("cannot convert NaN to integer ratio"); From f7719b2488cfaa2a10e83a56a1422e05d1c2046c Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Mon, 12 Oct 2026 10:23:49 +0200 Subject: [PATCH 09/31] Add fast path for creating decimals from strings --- .../objects/decimal/DecimalNodes.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java index 789df11901..2481f3b7b5 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java @@ -43,6 +43,7 @@ import static com.oracle.graal.python.builtins.PythonBuiltinClassType.MemoryError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; +import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; import java.math.BigInteger; @@ -95,6 +96,7 @@ import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.profiles.InlinedBranchProfile; import com.oracle.truffle.api.profiles.InlinedConditionProfile; +import com.oracle.truffle.api.strings.TruffleString; public final class DecimalNodes { private static final int INVALID = DecimalContextUtils.conditionMask(DecimalExceptionTypes.InvalidOperation); @@ -232,6 +234,18 @@ static PDecimal doDecimal(Node inliningTarget, Object cls, PDecimal value, return PFactory.createDecimal(cls, getShape.execute(cls), value); } + @Specialization + static PDecimal doTruffleString(@SuppressWarnings("unused") Node inliningTarget, Object cls, TruffleString value, + @Cached TruffleString.CodePointAtIndexUTF32Node read, + @Cached TruffleString.ToJavaStringNode toJavaString, + @Shared @Cached TypeNodes.GetInstanceShape getShape) throws ConversionException { + PDecimal result = fromCompactString(cls, value, read, getShape); + if (result != null) { + return result; + } + return createDecimal(cls, DecimalUtils.parse(toJavaString.execute(value)), getShape); + } + @Specialization(guards = "unicodeCheck.execute(inliningTarget, value)") static PDecimal doString(Node inliningTarget, Object cls, Object value, @SuppressWarnings("unused") @Shared @Cached PyUnicodeCheckNode unicodeCheck, @@ -332,6 +346,75 @@ private static PDecimal createDecimal(Object cls, Value value, TypeNodes.GetInst return PFactory.createDecimal(cls, getShape.execute(cls), value.negative(), value.coefficient(), value.exponent(), value.kind()); } + private static PDecimal fromCompactString(Object cls, TruffleString text, TruffleString.CodePointAtIndexUTF32Node read, TypeNodes.GetInstanceShape getShape) { + int length = text.byteLength(TS_ENCODING) / Integer.BYTES; + // At most 18 characters guarantee a coefficient below 10^18 and an explicit + // exponent below 10^16 (the coefficient and 'e' take at least two characters). + // Neither long overflow nor libmpdec's exponent limits are possible here. + if (length == 0 || length > 18) { + return null; + } + int pos = 0; + int ch = read.execute(text, pos); + boolean negative = ch == '-'; + if (negative || ch == '+') { + pos++; + } + long coefficient = 0; + int digits = 0; + int fractionalDigits = 0; + boolean point = false; + while (pos < length) { + ch = read.execute(text, pos); + if (ch >= '0' && ch <= '9') { + coefficient = coefficient * 10 + ch - '0'; + digits++; + if (point) { + fractionalDigits++; + } + } else if (ch == '.' && !point) { + point = true; + } else { + break; + } + pos++; + } + if (digits == 0) { + return null; + } + long exponent = 0; + if (pos < length && (ch == 'e' || ch == 'E')) { + pos++; + boolean negativeExponent = false; + if (pos < length) { + ch = read.execute(text, pos); + negativeExponent = ch == '-'; + if (negativeExponent || ch == '+') { + pos++; + } + } + int exponentStart = pos; + while (pos < length) { + ch = read.execute(text, pos); + if (ch < '0' || ch > '9') { + return null; + } + exponent = exponent * 10 + ch - '0'; + pos++; + } + if (pos == exponentStart) { + return null; + } + if (negativeExponent) { + exponent = -exponent; + } + } + if (pos != length) { + return null; + } + return PFactory.createDecimal(cls, getShape.execute(cls), negative, coefficient, exponent - fractionalDigits, Kind.FINITE); + } + private static PDecimal fromDouble(Node inliningTarget, Object cls, double value, TypeNodes.GetInstanceShape getShape, InlinedBranchProfile nonCompactProfile) { // IEEE 754 binary64 stores the sign in bit 63, an 11-bit biased exponent // in bits 62..52, and a 52-bit fraction in bits 51..0. The masks select From 4188a4baed56151005bc182a913e0fda3836c246 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Mon, 12 Oct 2026 16:14:20 +0200 Subject: [PATCH 10/31] Reduce graph size --- .../modules/DecimalModuleBuiltins.java | 11 +-- .../objects/contextvars/PContextVar.java | 7 +- .../objects/decimal/DecimalBuiltins.java | 36 ++++++---- .../decimal/DecimalContextBuiltins.java | 5 +- .../objects/decimal/DecimalModuleState.java | 8 ++- .../objects/decimal/DecimalNodes.java | 72 +++++++++++++------ .../graal/python/runtime/PythonContext.java | 6 ++ 7 files changed, 100 insertions(+), 45 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java index 5bdd13097e..44c364996f 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java @@ -63,6 +63,7 @@ import com.oracle.graal.python.builtins.objects.decimal.DecimalContextUtils; import com.oracle.graal.python.builtins.objects.decimal.DecimalExceptionTypes; import com.oracle.graal.python.builtins.objects.decimal.DecimalModuleState; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.GetCurrentContextNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalRounding; import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils; import com.oracle.graal.python.builtins.objects.decimal.PDecimalContext; @@ -191,8 +192,9 @@ public void postInitialize(Python3Core core) { @GenerateNodeFactory public abstract static class GetcontextNode extends PythonBuiltinNode { @Specialization - static PDecimalContext get(@Bind Node inliningTarget) { - return DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + static PDecimalContext get(@Bind Node inliningTarget, + @Cached GetCurrentContextNode getCurrentContext) { + return getCurrentContext.execute(inliningTarget); } } @@ -219,12 +221,13 @@ static PDecimalContextManager local(Object ctx, Object prec, Object rounding, Ob PKeyword[] kwargs, @Bind Node inliningTarget, @Bind PythonLanguage language, - @Cached PRaiseNode raiseNode) { + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { if (kwargs.length != 0) { throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_INVALID_LOCALCONTEXT_KEYWORD, kwargs[0].getName()); } DecimalModuleState state = DecimalContextUtils.getState(inliningTarget); - PDecimalContext outer = state.getCurrentContext(inliningTarget); + PDecimalContext outer = getCurrentContext.execute(inliningTarget); PDecimalContext source = outer; if (!(ctx instanceof PNone)) { if (!(ctx instanceof PDecimalContext decimalContext)) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/PContextVar.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/PContextVar.java index f71d00ff74..6ab3626262 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/PContextVar.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/PContextVar.java @@ -44,6 +44,7 @@ import com.oracle.graal.python.runtime.PythonContext; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.object.Shape; +import com.oracle.truffle.api.profiles.InlinedBranchProfile; import com.oracle.truffle.api.strings.TruffleString; public final class PContextVar extends PythonBuiltinObject { @@ -73,7 +74,11 @@ public Object getDefault() { } public Object getValue(Node node, PythonContext.PythonThreadState state) { - return state.getContextVarsContext(node).contextVarValues.lookup(this, getHash()); + return getValue(node, state, InlinedBranchProfile.getUncached()); + } + + public Object getValue(Node node, PythonContext.PythonThreadState state, InlinedBranchProfile initializeProfile) { + return state.getContextVarsContext(node, initializeProfile).contextVarValues.lookup(this, getHash()); } public void setValue(Node node, PythonContext.PythonThreadState state, Object value) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java index 9c814462b9..9889da8c27 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java @@ -72,6 +72,7 @@ import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.DivideIntDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.DivmodDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.GetContextNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.GetCurrentContextNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.MinusDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.MultiplyDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.PlusDecimalNode; @@ -582,7 +583,8 @@ static PDecimal quantize(VirtualFrame frame, PDecimal self, Object exp, Object r @Cached RaiseSignalNode raiseSignal, @Cached GetClassNode getClass, @Cached TypeNodes.GetNameNode getName, - @Cached PRaiseNode raiseNode) { + @Cached PRaiseNode raiseNode, + @Cached InlinedBranchProfile nonFiniteQuantumProfile) { PDecimalContext actualContext = getContext.execute(inliningTarget, context); DecimalRounding actualRounding = actualContext.getRounding(); if (!(rounding instanceof PNone)) { @@ -593,7 +595,7 @@ static PDecimal quantize(VirtualFrame frame, PDecimal self, Object exp, Object r throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.CONVERSION_FROM_S_TO_DECIMAL_NOT_SUPPORTED, getName.execute(inliningTarget, getClass.execute(inliningTarget, exp))); } - return QuantizeDecimalNode.doQuantize(frame, inliningTarget, self, quantum, actualContext, actualRounding, operation, raiseSignal, raiseNode); + return QuantizeDecimalNode.doQuantize(frame, inliningTarget, self, quantum, actualContext, actualRounding, operation, raiseSignal, raiseNode, nonFiniteQuantumProfile); } } @@ -1393,7 +1395,8 @@ static Object format(VirtualFrame frame, PDecimal self, Object specifier, Object @Cached CastToJavaStringNode asString, @Cached TruffleString.FromJavaStringNode fromJavaString, @Cached WarnNode warnNode, - @Cached PRaiseNode raiseNode) { + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { if (!unicodeCheck.execute(inliningTarget, specifier)) { throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_FORMAT_STRING); } @@ -1401,7 +1404,7 @@ static Object format(VirtualFrame frame, PDecimal self, Object specifier, Object if (endsWithN(format)) { warnNode.warnEx(frame, DeprecationWarning, ErrorMessages.DECIMAL_FORMAT_N_DEPRECATED, 1); } - PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PDecimalContext context = getCurrentContext.execute(inliningTarget); try { return fromJavaString.execute(DecimalFormatting.format(self, format, override, context), TS_ENCODING); } catch (ArithmeticException e) { @@ -1439,7 +1442,8 @@ static Object round(VirtualFrame frame, PDecimal self, Object ndigits, @Cached PyLongCheckNode longCheck, @Cached PyLongAsLongNode asLong, @Cached QuantizeDecimalNode operation, - @Cached PRaiseNode raiseNode) { + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { if (ndigits == PNone.NO_VALUE) { return toInteger(inliningTarget, self, DecimalRounding.ROUND_HALF_EVEN, raiseNode); } @@ -1453,7 +1457,7 @@ static Object round(VirtualFrame frame, PDecimal self, Object ndigits, } else { exponent = -digits; } - PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PDecimalContext context = getCurrentContext.execute(inliningTarget); return operation.execute(frame, inliningTarget, self, exponent, context, context.getRounding()); } } @@ -1575,8 +1579,9 @@ public abstract static class TpReprNode extends PythonUnaryBuiltinNode { @Specialization static TruffleString repr(PDecimal self, @Bind Node inliningTarget, - @Cached TruffleString.FromJavaStringNode fromJavaString) { - boolean capitals = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget).getCapitals(); + @Cached TruffleString.FromJavaStringNode fromJavaString, + @Cached GetCurrentContextNode getCurrentContext) { + boolean capitals = getCurrentContext.execute(inliningTarget).getCapitals(); return fromJavaString.execute(reprString(self, capitals), TS_ENCODING); } @@ -1592,8 +1597,9 @@ public abstract static class TpStrNode extends PythonUnaryBuiltinNode { @Specialization static TruffleString str(PDecimal self, @Bind Node inliningTarget, - @Cached TruffleString.FromJavaStringNode fromJavaString) { - boolean capitals = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget).getCapitals(); + @Cached TruffleString.FromJavaStringNode fromJavaString, + @Cached GetCurrentContextNode getCurrentContext) { + boolean capitals = getCurrentContext.execute(inliningTarget).getCapitals(); return doStr(self, capitals, fromJavaString); } @@ -1821,8 +1827,9 @@ static Object compare(VirtualFrame frame, PDecimal self, Object other, RichCmpOp @Cached CastToJavaBigIntegerNode asInteger, @Cached PyLongCheckNode longCheck, @Cached RaiseSignalNode raiseSignal, - @Cached PRaiseNode raiseNode) { - PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { + PDecimalContext context = getCurrentContext.execute(inliningTarget); Value a = decimalValue(self); Value b; PDecimal converted = toDecimal.execute(inliningTarget, other); @@ -1966,7 +1973,8 @@ static Object power(VirtualFrame frame, Object left, Object right, Object mod, @Cached ToDecimalNode toModulo, @Cached InlinedConditionProfile notImplementedProfile, @Cached RaiseSignalNode raiseSignal, - @Cached PRaiseNode raiseNode) { + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { PDecimal a = toLeft.execute(inliningTarget, left); PDecimal b = toRight.execute(inliningTarget, right); boolean hasModulo = !(mod instanceof PNone); @@ -1974,7 +1982,7 @@ static Object power(VirtualFrame frame, Object left, Object right, Object mod, if (notImplementedProfile.profile(inliningTarget, a == null || b == null || hasModulo && modulo == null)) { return PNotImplemented.NOT_IMPLEMENTED; } - PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PDecimalContext context = getCurrentContext.execute(inliningTarget); return doPower(frame, inliningTarget, a, b, modulo, context, raiseSignal, raiseNode); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java index 19b38b9c8d..3b26297ee5 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java @@ -495,10 +495,11 @@ static PDecimal quantize(VirtualFrame frame, PDecimalContext self, Object a, Obj @Cached RequireDecimalNode convert, @Cached DecimalNodes.QuantizeDecimalNode operation, @Cached RaiseSignalNode raiseSignal, - @Cached PRaiseNode raiseNode) { + @Cached PRaiseNode raiseNode, + @Cached InlinedBranchProfile nonFiniteQuantumProfile) { PDecimal operand = convert.execute(inliningTarget, a); PDecimal other = convert.execute(inliningTarget, b); - return DecimalNodes.QuantizeDecimalNode.doQuantize(frame, inliningTarget, operand, other, self, self.getRounding(), operation, raiseSignal, raiseNode); + return DecimalNodes.QuantizeDecimalNode.doQuantize(frame, inliningTarget, operand, other, self, self.getRounding(), operation, raiseSignal, raiseNode, nonFiniteQuantumProfile); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java index 6d1e1e75ac..6c05b6a5fa 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java @@ -51,6 +51,7 @@ import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.object.Shape; +import com.oracle.truffle.api.profiles.InlinedBranchProfile; import com.oracle.truffle.api.strings.TruffleString; public final class DecimalModuleState { @@ -138,11 +139,16 @@ public PDecimalContext copyContext(PythonLanguage language, PDecimalContext orig } public PDecimalContext getCurrentContext(Node node) { + return getCurrentContext(node, InlinedBranchProfile.getUncached(), InlinedBranchProfile.getUncached()); + } + + public PDecimalContext getCurrentContext(Node node, InlinedBranchProfile initializeContextVarsProfile, InlinedBranchProfile initializeDecimalContextProfile) { PythonContext context = PythonContext.get(node); PythonLanguage language = context.getLanguage(node); PythonContext.PythonThreadState threadState = context.getThreadState(language); - Object value = currentContext.getValue(node, threadState); + Object value = currentContext.getValue(node, threadState, initializeContextVarsProfile); if (value == null) { + initializeDecimalContextProfile.enter(node); PDecimalContext initial = copyContext(language, defaultContext); initial.setFlags(0); currentContext.setValue(node, threadState, initial); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java index 2481f3b7b5..d2e7fa0370 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java @@ -109,6 +109,20 @@ public final class DecimalNodes { private DecimalNodes() { } + @GenerateInline + @GenerateCached(false) + @GenerateUncached + public abstract static class GetCurrentContextNode extends Node { + public abstract PDecimalContext execute(Node inliningTarget); + + @Specialization + static PDecimalContext get(Node inliningTarget, + @Cached InlinedBranchProfile initializeContextVarsProfile, + @Cached InlinedBranchProfile initializeDecimalContextProfile) { + return DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget, initializeContextVarsProfile, initializeDecimalContextProfile); + } + } + /** Resolve an optional context argument, matching CPython's CONTEXT_CHECK_VA. */ @GenerateInline @GenerateCached(false) @@ -122,8 +136,9 @@ static PDecimalContext doContext(@SuppressWarnings("unused") Node inliningTarget } @Specialization - static PDecimalContext doDefault(Node inliningTarget, @SuppressWarnings("unused") PNone context) { - return DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + static PDecimalContext doDefault(Node inliningTarget, @SuppressWarnings("unused") PNone context, + @Cached GetCurrentContextNode getCurrentContext) { + return getCurrentContext.execute(inliningTarget); } @Fallback @@ -550,8 +565,9 @@ public abstract static class PlusDecimalNode extends Node { static PDecimal plus(VirtualFrame frame, Node inliningTarget, PDecimal self, @Cached InlinedBranchProfile nonCompactProfile, @Cached RaiseSignalNode raiseSignal, - @Cached PRaiseNode raiseNode) { - PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { + PDecimalContext context = getCurrentContext.execute(inliningTarget); return doPlus(frame, inliningTarget, self, context, nonCompactProfile, raiseSignal, raiseNode); } @@ -583,8 +599,9 @@ public abstract static class MinusDecimalNode extends Node { static PDecimal minus(VirtualFrame frame, Node inliningTarget, PDecimal self, @Cached InlinedBranchProfile nonCompactProfile, @Cached RaiseSignalNode raiseSignal, - @Cached PRaiseNode raiseNode) { - PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { + PDecimalContext context = getCurrentContext.execute(inliningTarget); return doMinus(frame, inliningTarget, self, context, nonCompactProfile, raiseSignal, raiseNode); } @@ -616,8 +633,9 @@ public abstract static class AbsDecimalNode extends Node { static PDecimal abs(VirtualFrame frame, Node inliningTarget, PDecimal self, @Cached InlinedBranchProfile nonCompactProfile, @Cached RaiseSignalNode raiseSignal, - @Cached PRaiseNode raiseNode) { - PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { + PDecimalContext context = getCurrentContext.execute(inliningTarget); return doAbs(frame, inliningTarget, self, context, nonCompactProfile, raiseSignal, raiseNode); } @@ -652,13 +670,14 @@ static Object add(VirtualFrame frame, Node inliningTarget, Object left, Object r @Cached InlinedConditionProfile notImplementedProfile, @Cached InlinedBranchProfile nonCompactProfile, @Cached RaiseSignalNode raiseSignal, - @Cached PRaiseNode raiseNode) { + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { PDecimal a = toLeft.execute(inliningTarget, left); PDecimal b = toRight.execute(inliningTarget, right); if (notImplementedProfile.profile(inliningTarget, a == null || b == null)) { return PNotImplemented.NOT_IMPLEMENTED; } - PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PDecimalContext context = getCurrentContext.execute(inliningTarget); return doAdd(frame, inliningTarget, a, b, context, nonCompactProfile, raiseSignal, raiseNode); } @@ -692,13 +711,14 @@ static Object subtract(VirtualFrame frame, Node inliningTarget, Object left, Obj @Cached InlinedConditionProfile notImplementedProfile, @Cached InlinedBranchProfile nonCompactProfile, @Cached RaiseSignalNode raiseSignal, - @Cached PRaiseNode raiseNode) { + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { PDecimal a = toLeft.execute(inliningTarget, left); PDecimal b = toRight.execute(inliningTarget, right); if (notImplementedProfile.profile(inliningTarget, a == null || b == null)) { return PNotImplemented.NOT_IMPLEMENTED; } - PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PDecimalContext context = getCurrentContext.execute(inliningTarget); return doSubtract(frame, inliningTarget, a, b, context, nonCompactProfile, raiseSignal, raiseNode); } @@ -732,13 +752,14 @@ static Object multiply(VirtualFrame frame, Node inliningTarget, Object left, Obj @Cached InlinedConditionProfile notImplementedProfile, @Cached InlinedBranchProfile nonCompactProfile, @Cached RaiseSignalNode raiseSignal, - @Cached PRaiseNode raiseNode) { + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { PDecimal a = toLeft.execute(inliningTarget, left); PDecimal b = toRight.execute(inliningTarget, right); if (notImplementedProfile.profile(inliningTarget, a == null || b == null)) { return PNotImplemented.NOT_IMPLEMENTED; } - PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PDecimalContext context = getCurrentContext.execute(inliningTarget); return doMultiply(frame, inliningTarget, a, b, context, nonCompactProfile, raiseSignal, raiseNode); } @@ -771,13 +792,14 @@ static Object divide(VirtualFrame frame, Node inliningTarget, Object left, Objec @Cached ToDecimalNode toRight, @Cached InlinedConditionProfile notImplementedProfile, @Cached RaiseSignalNode raiseSignal, - @Cached PRaiseNode raiseNode) { + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { PDecimal a = toLeft.execute(inliningTarget, left); PDecimal b = toRight.execute(inliningTarget, right); if (notImplementedProfile.profile(inliningTarget, a == null || b == null)) { return PNotImplemented.NOT_IMPLEMENTED; } - PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PDecimalContext context = getCurrentContext.execute(inliningTarget); return doDivide(frame, inliningTarget, a, b, context, raiseSignal, raiseNode); } @@ -803,13 +825,14 @@ static Object divideInt(VirtualFrame frame, Node inliningTarget, Object left, Ob @Cached ToDecimalNode toRight, @Cached InlinedConditionProfile notImplementedProfile, @Cached RaiseSignalNode raiseSignal, - @Cached PRaiseNode raiseNode) { + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { PDecimal a = toLeft.execute(inliningTarget, left); PDecimal b = toRight.execute(inliningTarget, right); if (notImplementedProfile.profile(inliningTarget, a == null || b == null)) { return PNotImplemented.NOT_IMPLEMENTED; } - PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PDecimalContext context = getCurrentContext.execute(inliningTarget); return doDivideInt(frame, inliningTarget, a, b, context, raiseSignal, raiseNode); } @@ -835,13 +858,14 @@ static Object remainder(VirtualFrame frame, Node inliningTarget, Object left, Ob @Cached ToDecimalNode toRight, @Cached InlinedConditionProfile notImplementedProfile, @Cached RaiseSignalNode raiseSignal, - @Cached PRaiseNode raiseNode) { + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { PDecimal a = toLeft.execute(inliningTarget, left); PDecimal b = toRight.execute(inliningTarget, right); if (notImplementedProfile.profile(inliningTarget, a == null || b == null)) { return PNotImplemented.NOT_IMPLEMENTED; } - PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PDecimalContext context = getCurrentContext.execute(inliningTarget); return doRemainder(frame, inliningTarget, a, b, context, raiseSignal, raiseNode); } @@ -867,13 +891,14 @@ static Object divmod(VirtualFrame frame, Node inliningTarget, Object left, Objec @Cached ToDecimalNode toRight, @Cached InlinedConditionProfile notImplementedProfile, @Cached RaiseSignalNode raiseSignal, - @Cached PRaiseNode raiseNode) { + @Cached PRaiseNode raiseNode, + @Cached GetCurrentContextNode getCurrentContext) { PDecimal a = toLeft.execute(inliningTarget, left); PDecimal b = toRight.execute(inliningTarget, right); if (notImplementedProfile.profile(inliningTarget, a == null || b == null)) { return PNotImplemented.NOT_IMPLEMENTED; } - PDecimalContext context = DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget); + PDecimalContext context = getCurrentContext.execute(inliningTarget); return doDivmod(frame, inliningTarget, a, b, context, raiseSignal, raiseNode); } @@ -1091,10 +1116,11 @@ static PDecimal quantize(VirtualFrame frame, Node inliningTarget, PDecimal self, } static PDecimal doQuantize(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimal quantum, PDecimalContext context, DecimalRounding rounding, QuantizeDecimalNode operation, - RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { + RaiseSignalNode raiseSignal, PRaiseNode raiseNode, InlinedBranchProfile nonFiniteQuantumProfile) { if (quantum.getKind() == Kind.FINITE) { return operation.execute(frame, inliningTarget, self, quantum.getExponent(), context, rounding); } + nonFiniteQuantumProfile.enter(inliningTarget); DecimalArithmetic.Result result; try { result = DecimalArithmetic.quantize(self, quantum, context, rounding); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java index a78029cddb..ffd1d87a3f 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java @@ -190,6 +190,7 @@ import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; +import com.oracle.truffle.api.profiles.InlinedBranchProfile; import com.oracle.truffle.api.strings.TruffleString; import com.oracle.truffle.api.strings.TruffleString.Encoding; import com.oracle.truffle.api.utilities.CyclicAssumption; @@ -509,7 +510,12 @@ public void resetNativePointerAfterDetach() { } public PContextVarsContext getContextVarsContext(Node node) { + return getContextVarsContext(node, InlinedBranchProfile.getUncached()); + } + + public PContextVarsContext getContextVarsContext(Node node, InlinedBranchProfile initializeProfile) { if (contextVarsContext == null) { + initializeProfile.enter(node); contextVarsContext = PFactory.createContextVarsContext(PythonLanguage.get(node)); } return contextVarsContext; From b6495379a51502c20b5b3ca7b1b86ebe6e4263a3 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Tue, 13 Oct 2026 11:25:07 +0200 Subject: [PATCH 11/31] Precompute powers of ten and limits --- .../objects/decimal/DecimalNodes.java | 16 +++------- .../objects/decimal/DecimalUtils.java | 31 ++++++++++++++++--- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java index d2e7fa0370..7815f22db3 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java @@ -946,13 +946,11 @@ static long scaleCompactCoefficient(long coefficient, long shift) { if (shift > 18) { return -1; } - for (int i = 0; i < shift; i++) { - if (coefficient > Long.MAX_VALUE / 10) { - return -1; - } - coefficient *= 10; + int index = (int) shift; + if (coefficient > DecimalUtils.LONG_SCALE_LIMITS[index]) { + return -1; } - return coefficient; + return coefficient * DecimalUtils.LONG_POWERS_OF_TEN[index]; } private static PDecimal compactAdd(PythonLanguage language, PDecimal a, PDecimal b, PDecimalContext context, boolean rightNegative) { @@ -1136,11 +1134,7 @@ private static long compactDivisor(long places) { if (places > 18) { return 0; } - long divisor = 1; - for (int i = 0; i < places; i++) { - divisor *= 10; - } - return divisor; + return DecimalUtils.LONG_POWERS_OF_TEN[(int) places]; } private static boolean incrementCompact(boolean negative, long quotient, long remainder, long divisor, long places, DecimalRounding rounding) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java index 634a70a2d2..7b2bfb4862 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java @@ -47,6 +47,7 @@ import com.oracle.graal.python.builtins.objects.decimal.PDecimal.Kind; import com.oracle.graal.python.builtins.objects.str.StringUtils; +import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; /** Exact conversions, independent of the arithmetic context's precision and rounding. */ @@ -65,6 +66,26 @@ public final class DecimalUtils { // Bound retained storage independently of the user's precision and exponent limits. private static final BigInteger[] SMALL_POWERS_OF_TEN = createSmallPowersOfTen(); + @CompilationFinal(dimensions = 1) static final long[] LONG_POWERS_OF_TEN = createLongPowersOfTen(); + @CompilationFinal(dimensions = 1) static final long[] LONG_SCALE_LIMITS = createLongScaleLimits(); + + private static long[] createLongPowersOfTen() { + long[] powers = new long[19]; + powers[0] = 1; + for (int i = 1; i < powers.length; i++) { + powers[i] = powers[i - 1] * 10; + } + return powers; + } + + private static long[] createLongScaleLimits() { + long[] limits = new long[LONG_POWERS_OF_TEN.length]; + for (int i = 0; i < limits.length; i++) { + limits[i] = Long.MAX_VALUE / LONG_POWERS_OF_TEN[i]; + } + return limits; + } + private DecimalUtils() { } @@ -87,11 +108,13 @@ static BigInteger pow10(long exponent) { static int digits(long coefficient) { assert coefficient >= 0; - int count = 1; - for (long c = coefficient; c >= 10; c /= 10) { - count++; + if (coefficient < 10) { + return 1; } - return count; + // 1233 / 4096 approximates log10(2). For a signed long, the bit-length + // estimate is either the digit count or one less; one comparison corrects it. + int estimate = ((Long.SIZE - Long.numberOfLeadingZeros(coefficient)) * 1233) >>> 12; + return coefficient >= LONG_POWERS_OF_TEN[estimate] ? estimate + 1 : estimate; } @TruffleBoundary From 1be904e50c166add5713cd3b159aa7c9f29041ec Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Tue, 13 Oct 2026 11:27:25 +0200 Subject: [PATCH 12/31] Add fast path for Decimal to string --- .../objects/decimal/DecimalBuiltins.java | 8 +- .../decimal/DecimalContextBuiltins.java | 4 +- .../objects/decimal/DecimalNodes.java | 78 +++++++++++++++++-- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java index 9889da8c27..08da1d6211 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java @@ -68,6 +68,7 @@ import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.AbsDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.AddDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.CreateDecimalNode; +import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.DecimalToStringNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.DivideDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.DivideIntDecimalNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalNodes.DivmodDecimalNode; @@ -1597,15 +1598,12 @@ public abstract static class TpStrNode extends PythonUnaryBuiltinNode { @Specialization static TruffleString str(PDecimal self, @Bind Node inliningTarget, - @Cached TruffleString.FromJavaStringNode fromJavaString, + @Cached DecimalToStringNode toString, @Cached GetCurrentContextNode getCurrentContext) { boolean capitals = getCurrentContext.execute(inliningTarget).getCapitals(); - return doStr(self, capitals, fromJavaString); + return toString.execute(inliningTarget, self, capitals); } - static TruffleString doStr(PDecimal self, boolean capitals, TruffleString.FromJavaStringNode fromJavaString) { - return fromJavaString.execute(self.toScientificString(capitals), TS_ENCODING); - } } @Slot(value = SlotKind.nb_negative, isComplex = true) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java index 3b26297ee5..3f77413f09 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java @@ -846,9 +846,9 @@ public abstract static class ToSciStringNode extends PythonBinaryBuiltinNode { static TruffleString format(PDecimalContext self, Object a, @Bind Node inliningTarget, @Cached RequireDecimalNode convert, - @Cached TruffleString.FromJavaStringNode fromJavaString) { + @Cached DecimalNodes.DecimalToStringNode toString) { PDecimal operand = convert.execute(inliningTarget, a); - return DecimalBuiltins.TpStrNode.doStr(operand, self.getCapitals(), fromJavaString); + return toString.execute(inliningTarget, operand, self.getCapitals()); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java index 7815f22db3..d6c7dacd33 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java @@ -96,7 +96,10 @@ import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.profiles.InlinedBranchProfile; import com.oracle.truffle.api.profiles.InlinedConditionProfile; +import com.oracle.truffle.api.profiles.InlinedLoopConditionProfile; import com.oracle.truffle.api.strings.TruffleString; +import com.oracle.truffle.api.strings.TruffleStringBuilder; +import com.oracle.truffle.api.strings.TruffleStringBuilderUTF32; public final class DecimalNodes { private static final int INVALID = DecimalContextUtils.conditionMask(DecimalExceptionTypes.InvalidOperation); @@ -148,6 +151,68 @@ static PDecimalContext doInvalid(Node inliningTarget, @SuppressWarnings("unused" } } + @GenerateInline + @GenerateCached(false) + @GenerateUncached + public abstract static class DecimalToStringNode extends Node { + public abstract TruffleString execute(Node inliningTarget, PDecimal self, boolean capitals); + + @Specialization + static TruffleString format(Node inliningTarget, PDecimal self, boolean capitals, + @Cached InlinedConditionProfile fixedProfile, + @Cached TruffleString.FromLongNode fromLong, + @Cached TruffleStringBuilder.AppendCodePointNode appendCodePoint, + @Cached TruffleStringBuilder.AppendSubstringByteIndexNode appendSubstring, + @Cached TruffleStringBuilder.AppendLongNumberNode appendLong, + @Cached TruffleStringBuilder.ToStringNode toString, + @Cached TruffleString.FromJavaStringNode fromJavaStringNode) { + // The node specialization profiles + if (self.isCompact() && self.getKind() == Kind.FINITE) { + TruffleString digits = fromLong.execute(self.getCompactCoefficient(), TS_ENCODING, false); + int length = digits.byteLength(TS_ENCODING) / Integer.BYTES; + long exponent = self.getExponent(); + long adjustedExponent = exponent + length - 1; + // Compact coefficients have at most 19 digits. Both fixed and scientific + // notation fit in 48 characters, even with an exponent of magnitude 10^18. + TruffleStringBuilderUTF32 builder = TruffleStringBuilder.createUTF32(48); + if (self.isNegative()) { + appendCodePoint.execute(builder, '-'); + } + if (fixedProfile.profile(inliningTarget, exponent <= 0 && adjustedExponent >= -6)) { + int point = (int) (length + exponent); + if (point <= 0) { + appendCodePoint.execute(builder, '0'); + appendCodePoint.execute(builder, '.'); + if (point < 0) { + appendCodePoint.execute(builder, '0', -point, false); + } + appendSubstring.execute(builder, digits, 0, length * Integer.BYTES); + } else if (point < length) { + appendSubstring.execute(builder, digits, 0, point * Integer.BYTES); + appendCodePoint.execute(builder, '.'); + appendSubstring.execute(builder, digits, point * Integer.BYTES, (length - point) * Integer.BYTES); + } else { + appendSubstring.execute(builder, digits, 0, length * Integer.BYTES); + } + } else { + appendSubstring.execute(builder, digits, 0, Integer.BYTES); + if (length > 1) { + appendCodePoint.execute(builder, '.'); + appendSubstring.execute(builder, digits, Integer.BYTES, (length - 1) * Integer.BYTES); + } + appendCodePoint.execute(builder, capitals ? 'E' : 'e'); + if (adjustedExponent >= 0) { + appendCodePoint.execute(builder, '+'); + } + appendLong.execute(builder, adjustedExponent); + } + return toString.execute(builder); + } else { + return fromJavaStringNode.execute(self.toScientificString(capitals), TS_ENCODING); + } + } + } + /** Numeric slots accept Decimal and int operands, without invoking conversion protocols. */ @GenerateInline @GenerateCached(false) @@ -250,11 +315,13 @@ static PDecimal doDecimal(Node inliningTarget, Object cls, PDecimal value, } @Specialization - static PDecimal doTruffleString(@SuppressWarnings("unused") Node inliningTarget, Object cls, TruffleString value, + static PDecimal doTruffleString(Node inliningTarget, Object cls, TruffleString value, @Cached TruffleString.CodePointAtIndexUTF32Node read, @Cached TruffleString.ToJavaStringNode toJavaString, + @Cached InlinedLoopConditionProfile coefficientLoopProfile, + @Cached InlinedLoopConditionProfile exponentLoopProfile, @Shared @Cached TypeNodes.GetInstanceShape getShape) throws ConversionException { - PDecimal result = fromCompactString(cls, value, read, getShape); + PDecimal result = fromCompactString(inliningTarget, cls, value, read, getShape, coefficientLoopProfile, exponentLoopProfile); if (result != null) { return result; } @@ -361,7 +428,8 @@ private static PDecimal createDecimal(Object cls, Value value, TypeNodes.GetInst return PFactory.createDecimal(cls, getShape.execute(cls), value.negative(), value.coefficient(), value.exponent(), value.kind()); } - private static PDecimal fromCompactString(Object cls, TruffleString text, TruffleString.CodePointAtIndexUTF32Node read, TypeNodes.GetInstanceShape getShape) { + private static PDecimal fromCompactString(Node inliningTarget, Object cls, TruffleString text, TruffleString.CodePointAtIndexUTF32Node read, TypeNodes.GetInstanceShape getShape, + InlinedLoopConditionProfile coefficientLoopProfile, InlinedLoopConditionProfile exponentLoopProfile) { int length = text.byteLength(TS_ENCODING) / Integer.BYTES; // At most 18 characters guarantee a coefficient below 10^18 and an explicit // exponent below 10^16 (the coefficient and 'e' take at least two characters). @@ -379,7 +447,7 @@ private static PDecimal fromCompactString(Object cls, TruffleString text, Truffl int digits = 0; int fractionalDigits = 0; boolean point = false; - while (pos < length) { + while (coefficientLoopProfile.profile(inliningTarget, pos < length)) { ch = read.execute(text, pos); if (ch >= '0' && ch <= '9') { coefficient = coefficient * 10 + ch - '0'; @@ -409,7 +477,7 @@ private static PDecimal fromCompactString(Object cls, TruffleString text, Truffl } } int exponentStart = pos; - while (pos < length) { + while (exponentLoopProfile.profile(inliningTarget, pos < length)) { ch = read.execute(text, pos); if (ch < '0' || ch > '9') { return null; From baa0d6b73423a2a4766b550509c5ee9142ca6c4c Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Tue, 13 Oct 2026 11:36:05 +0200 Subject: [PATCH 13/31] Avoid string concatenations --- .../objects/decimal/DecimalBuiltins.java | 20 +++++++---- .../objects/decimal/DecimalFormatting.java | 33 +++++++++---------- .../builtins/objects/decimal/PDecimal.java | 30 +++++++++-------- 3 files changed, 45 insertions(+), 38 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java index 08da1d6211..d3c7a0cbe4 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java @@ -927,20 +927,26 @@ private static String engineeringString(PDecimal self, boolean capitals) { } else { point = Math.floorMod(left - 1, 3) + 1; } - String significand; + StringBuilder result = new StringBuilder(); + if (self.isNegative()) { + result.append('-'); + } if (point <= 0) { - significand = "0." + "0".repeat(-point) + digits; + result.append("0.").append("0".repeat(-point)).append(digits); } else if (point >= digits.length()) { - significand = digits + "0".repeat(point - digits.length()); + result.append(digits).append("0".repeat(point - digits.length())); } else { - significand = digits.substring(0, point) + "." + digits.substring(point); + result.append(digits, 0, point).append('.').append(digits, point, digits.length()); } long exponent = left - point; - String result = (self.isNegative() ? "-" : "") + significand; if (exponent != 0) { - result += (capitals ? "E" : "e") + (exponent > 0 ? "+" : "") + exponent; + result.append(capitals ? 'E' : 'e'); + if (exponent > 0) { + result.append('+'); + } + result.append(exponent); } - return result; + return result.toString(); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalFormatting.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalFormatting.java index 5dfba332d0..b4e081eb9c 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalFormatting.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalFormatting.java @@ -126,7 +126,7 @@ static String format(PDecimal self, String format, Object override, PDecimalCont separator = String.valueOf(symbols.getGroupingSeparator()); grouping = ""; if (numberFormat instanceof DecimalFormat df && df.isGroupingUsed()) { - grouping = String.valueOf((char) df.getGroupingSize()) + "\000"; + grouping = String.valueOf((char) df.getGroupingSize()) + '\000'; } type = type == 'N' ? 'G' : 'g'; lowerType = 'g'; @@ -152,7 +152,7 @@ static String format(PDecimal self, String format, Object override, PDecimalCont body = body.substring(1); } if (type == '%') { - body += '%'; + body = body + '%'; } if (zeroPad) { fill = " "; @@ -207,28 +207,24 @@ static String format(PDecimal self, String format, Object override, PDecimalCont fraction = digits.substring((int) point); } long exponent = leftDigits - point; - String suffix = ""; + StringBuilder suffix = new StringBuilder(); if (!fraction.isEmpty() || alternate) { - suffix = dot + fraction; + suffix.append(dot).append(fraction); } if (exponent != 0 || lowerType == 'e') { - if (Character.isUpperCase(type)) { - suffix += "E"; - } else { - suffix += "e"; - } + suffix.append(Character.isUpperCase(type) ? 'E' : 'e'); if (exponent >= 0) { - suffix += "+"; + suffix.append('+'); } - suffix += exponent; + suffix.append(exponent); } if (type == '%') { - suffix += '%'; + suffix.append('%'); } String signString = sign(value.negative() && !(zero && noNegativeZero), sign); long minWidth = 0; if (zeroPad) { - minWidth = width - length(suffix) - signString.length(); + minWidth = width - suffix.codePointCount(0, suffix.length()) - signString.length(); } integer = group(integer, separator, grouping, minWidth); return align(signString, integer + suffix, fill, align, width); @@ -317,11 +313,12 @@ private static int length(String value) { private static String align(String sign, String body, String fill, char align, long width) { int padding = Math.toIntExact(Math.max(0, width - sign.length() - length(body))); + StringBuilder result = new StringBuilder(); return switch (align) { - case '<' -> sign + body + fill.repeat(padding); - case '=' -> sign + fill.repeat(padding) + body; - case '^' -> fill.repeat(padding / 2) + sign + body + fill.repeat(padding - padding / 2); - default -> fill.repeat(padding) + sign + body; + case '<' -> result.append(sign).append(body).append(fill.repeat(padding)).toString(); + case '=' -> result.append(sign).append(fill.repeat(padding)).append(body).toString(); + case '^' -> result.append(fill.repeat(padding / 2)).append(sign).append(body).append(fill.repeat(padding - padding / 2)).toString(); + default -> result.append(fill.repeat(padding)).append(sign).append(body).toString(); }; } @@ -344,7 +341,7 @@ private static String group(String digits, String separator, String grouping, lo previous = size; int count = (int) Math.min(Math.max(Math.max(end, width), 1), size); int start = Math.max(0, end - count); - groups.add(zeros(count - (end - start)) + digits.substring(start, end)); + groups.add(new StringBuilder().append(zeros(count - (end - start))).append(digits, start, end).toString()); end = start; width -= count; if (end == 0 && width <= 0) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java index fa3a93b1f9..73465211d1 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/PDecimal.java @@ -154,9 +154,12 @@ public BigInteger getCoefficient() { @TruffleBoundary public String toScientificString(boolean capitals) { - String sign = negative ? "-" : ""; + StringBuilder result = new StringBuilder(); + if (negative) { + result.append('-'); + } if (kind == Kind.INFINITY) { - return sign + "Infinity"; + return result.append("Infinity").toString(); } String digits; if (isCompact()) { @@ -165,11 +168,11 @@ public String toScientificString(boolean capitals) { digits = largeCoefficient.toString(); } if (kind != Kind.FINITE) { - String result = sign + (kind == Kind.NAN ? "NaN" : "sNaN"); + result.append(kind == Kind.NAN ? "NaN" : "sNaN"); if (!digits.equals("0")) { - result += digits; + result.append(digits); } - return result; + return result.toString(); } // Moving the decimal point after the first digit adds digits.length() - 1 // to the stored exponent, giving the exponent of the most significant digit. @@ -180,21 +183,22 @@ public String toScientificString(boolean capitals) { if (exponent <= 0 && adjustedExponent >= -6) { int point = (int) (digits.length() + exponent); if (point <= 0) { - return sign + "0." + "0".repeat(-point) + digits; + return result.append("0.").append("0".repeat(-point)).append(digits).toString(); } else if (point < digits.length()) { - return sign + digits.substring(0, point) + "." + digits.substring(point); + return result.append(digits, 0, point).append('.').append(digits, point, digits.length()).toString(); } - return sign + digits; + return result.append(digits).toString(); } // Scientific notation puts exactly one coefficient digit before the point. - String significand = digits; if (digits.length() != 1) { - significand = digits.charAt(0) + "." + digits.substring(1); + result.append(digits.charAt(0)).append('.').append(digits, 1, digits.length()); + } else { + result.append(digits); } - String result = sign + significand + (capitals ? "E" : "e"); + result.append(capitals ? 'E' : 'e'); if (adjustedExponent >= 0) { - result += "+"; + result.append('+'); } - return result + adjustedExponent; + return result.append(adjustedExponent).toString(); } } From 9ee4a616888fa70a52923671a33913bc49f57ad8 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Tue, 13 Oct 2026 22:30:17 +0200 Subject: [PATCH 14/31] Cache decimal module state on the context --- .../modules/DecimalModuleBuiltins.java | 7 +- .../objects/decimal/DecimalContextUtils.java | 3 +- .../objects/decimal/DecimalModuleState.java | 79 ++++++++++--------- .../graal/python/runtime/PythonContext.java | 14 ++++ 4 files changed, 60 insertions(+), 43 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java index 44c364996f..a940ec23c1 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java @@ -174,15 +174,14 @@ public void postInitialize(Python3Core core) { PythonClass signalDict = PFactory.createPythonClassAndFixupSlots(null, language, T_SIGNAL_DICT, mixin, new PythonAbstractClass[]{mixin, mapping}); signalDict.setAttribute(T___MODULE__, T_DECIMAL); - DecimalModuleState state = new DecimalModuleState(exceptionTypes, decimalTuple, signalDict); + PythonModule numbers = AbstractImportNode.importModule(T_NUMBERS); + DecimalModuleState state = new DecimalModuleState(language, exceptionTypes, decimalTuple, signalDict, numbers.getAttribute(T_RATIONAL)); module.setModuleState(state); - state.initializeContexts(language); + core.getContext().setDecimalModuleState(state); module.setAttribute(T_DEFAULT_CONTEXT, state.getDefaultContext()); module.setAttribute(T_BASIC_CONTEXT, state.getBasicContext()); module.setAttribute(T_EXTENDED_CONTEXT, state.getExtendedContext()); - PythonModule numbers = AbstractImportNode.importModule(T_NUMBERS); - state.setRationalType(numbers.getAttribute(T_RATIONAL)); Object register = PyObjectGetAttr.executeUncached(numbers.getAttribute(T_NUMBER), T_REGISTER); CallNode.executeUncached(register, PythonBuiltinClassType.PDecimal); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextUtils.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextUtils.java index 69a955b921..e4b1e5cbae 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextUtils.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextUtils.java @@ -43,7 +43,6 @@ import static com.oracle.graal.python.builtins.PythonBuiltinClassType.KeyError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; -import static com.oracle.graal.python.nodes.BuiltinNames.T__DECIMAL; import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; import static com.oracle.graal.python.util.PythonUtils.tsLiteral; @@ -116,7 +115,7 @@ public static int mask(DecimalExceptionTypes signal) { } public static DecimalModuleState getState(Node node) { - return PythonContext.get(node).lookupBuiltinModule(T__DECIMAL).getModuleState(DecimalModuleState.class); + return PythonContext.get(node).getDecimalModuleState(); } public static int signalMask(Node node, PRaiseNode raiseNode, Object key) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java index 6c05b6a5fa..6294a59ec6 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java @@ -49,6 +49,8 @@ import com.oracle.graal.python.builtins.objects.type.PythonClass; import com.oracle.graal.python.runtime.PythonContext; import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.object.Shape; import com.oracle.truffle.api.profiles.InlinedBranchProfile; @@ -61,17 +63,29 @@ public final class DecimalModuleState { private final PythonClass decimalTupleType; private final PythonClass signalDictType; - private Object rationalType; + private final Object rationalType; - private PDecimalContext defaultContext; - private PDecimalContext basicContext; - private PDecimalContext extendedContext; - private PContextVar currentContext; + private final PDecimalContext defaultContext; + private final PDecimalContext basicContext; + private final PDecimalContext extendedContext; + private final PContextVar currentContext; - public DecimalModuleState(PythonClass[] exceptionTypes, PythonClass decimalTupleType, PythonClass signalDictType) { + public DecimalModuleState(PythonLanguage language, PythonClass[] exceptionTypes, PythonClass decimalTupleType, PythonClass signalDictType, Object rationalType) { this.exceptionTypes = exceptionTypes; this.decimalTupleType = decimalTupleType; this.signalDictType = signalDictType; + this.rationalType = rationalType; + currentContext = PFactory.createContextVar(language, T_DECIMAL_CONTEXT, PContextVar.NO_DEFAULT); + Object cls = PythonBuiltinClassType.PDecimalContext; + Shape shape = PythonBuiltinClassType.PDecimalContext.getInstanceShape(language); + defaultContext = createContext(cls, shape); + basicContext = newContext(cls, shape); + basicContext.setPrecision(9); + basicContext.setRounding(ROUND_HALF_UP); + basicContext.setTraps(DecimalContextUtils.DEFAULT_TRAPS | DecimalContextUtils.mask(DecimalExceptionTypes.Underflow) | DecimalContextUtils.mask(DecimalExceptionTypes.Clamped)); + extendedContext = newContext(cls, shape); + extendedContext.setPrecision(9); + extendedContext.setTraps(0); } public PythonClass getExceptionType(DecimalExceptionTypes type) { @@ -90,24 +104,6 @@ public Object getRationalType() { return rationalType; } - public void setRationalType(Object rationalType) { - this.rationalType = rationalType; - } - - public void initializeContexts(PythonLanguage language) { - currentContext = PFactory.createContextVar(language, T_DECIMAL_CONTEXT, PContextVar.NO_DEFAULT); - Object cls = PythonBuiltinClassType.PDecimalContext; - Shape shape = PythonBuiltinClassType.PDecimalContext.getInstanceShape(language); - defaultContext = newContext(cls, shape); - basicContext = newContext(cls, shape); - basicContext.setPrecision(9); - basicContext.setRounding(ROUND_HALF_UP); - basicContext.setTraps(DecimalContextUtils.DEFAULT_TRAPS | DecimalContextUtils.mask(DecimalExceptionTypes.Underflow) | DecimalContextUtils.mask(DecimalExceptionTypes.Clamped)); - extendedContext = newContext(cls, shape); - extendedContext.setPrecision(9); - extendedContext.setTraps(0); - } - public PDecimalContext getDefaultContext() { return defaultContext; } @@ -121,12 +117,15 @@ public PDecimalContext getExtendedContext() { } public PDecimalContext newContext(Object cls, Shape shape) { + PDecimalContext result = createContext(cls, shape); + result.copyFrom(defaultContext); + // Context.__new__ inherits arithmetic settings and signals, but not capitals. + result.setCapitals(true); + return result; + } + + private PDecimalContext createContext(Object cls, Shape shape) { PDecimalContext result = PFactory.createDecimalContext(cls, shape); - if (defaultContext != null) { - result.copyFrom(defaultContext); - // Context.__new__ inherits arithmetic settings and signals, but not capitals. - result.setCapitals(true); - } result.setSignalDicts(PFactory.createDecimalSignalDict(signalDictType, signalDictType.getInstanceShape(), result, false), PFactory.createDecimalSignalDict(signalDictType, signalDictType.getInstanceShape(), result, true)); return result; @@ -139,24 +138,30 @@ public PDecimalContext copyContext(PythonLanguage language, PDecimalContext orig } public PDecimalContext getCurrentContext(Node node) { - return getCurrentContext(node, InlinedBranchProfile.getUncached(), InlinedBranchProfile.getUncached()); + return getCurrentContext(node, InlinedBranchProfile.getUncached(), InlinedBranchProfile.getUncached(), InlinedBranchProfile.getUncached()); } - public PDecimalContext getCurrentContext(Node node, InlinedBranchProfile initializeContextVarsProfile, InlinedBranchProfile initializeDecimalContextProfile) { + public PDecimalContext getCurrentContext(Node node, InlinedBranchProfile initializeContextVarsProfile, InlinedBranchProfile initializeDecimalContextProfile, + InlinedBranchProfile contextVarCacheMissProfile) { PythonContext context = PythonContext.get(node); PythonLanguage language = context.getLanguage(node); PythonContext.PythonThreadState threadState = context.getThreadState(language); - Object value = currentContext.getValue(node, threadState, initializeContextVarsProfile); - if (value == null) { + Object value = currentContext.getValue(node, threadState, initializeContextVarsProfile, contextVarCacheMissProfile); + if (CompilerDirectives.injectBranchProbability(CompilerDirectives.SLOWPATH_PROBABILITY, value == null)) { initializeDecimalContextProfile.enter(node); - PDecimalContext initial = copyContext(language, defaultContext); - initial.setFlags(0); - currentContext.setValue(node, threadState, initial); - return initial; + return initializeCurrentContext(node, language, threadState); } return (PDecimalContext) value; } + @TruffleBoundary + private PDecimalContext initializeCurrentContext(Node node, PythonLanguage language, PythonContext.PythonThreadState threadState) { + PDecimalContext initial = copyContext(language, defaultContext); + initial.setFlags(0); + currentContext.setValue(node, threadState, initial); + return initial; + } + public void setCurrentContext(Node node, PDecimalContext value) { PythonContext context = PythonContext.get(node); PythonLanguage language = context.getLanguage(node); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java index ffd1d87a3f..98601f8943 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java @@ -118,6 +118,7 @@ import com.oracle.graal.python.builtins.objects.common.ObjectHashMap; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.builtins.objects.contextvars.PContextVarsContext; +import com.oracle.graal.python.builtins.objects.decimal.DecimalModuleState; import com.oracle.graal.python.builtins.objects.dict.PDict; import com.oracle.graal.python.builtins.objects.frame.PFrame; import com.oracle.graal.python.builtins.objects.frame.PFrame.Reference; @@ -760,6 +761,10 @@ static PythonThreadState getThreadState(Node n) { @CompilationFinal private NativeBz2Support nativeBz2lib; @CompilationFinal private NativeLZMASupport nativeLZMA; + // This is just a shortcut to the module state of _decimal module. + // It's used all the time in decimal builtins and needs to be fast. + @CompilationFinal private DecimalModuleState decimalModuleState; + // if set to 0 the VM will set it to whatever it likes private final AtomicLong pythonThreadStackSize = new AtomicLong(0); @@ -1242,6 +1247,15 @@ public void initialize() { } + public DecimalModuleState getDecimalModuleState() { + return decimalModuleState; + } + + public void setDecimalModuleState(DecimalModuleState state) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + decimalModuleState = state; + } + public void resetPerfCounter() { perfCounterStart = System.nanoTime(); } From e3b4e1092a7234e73e596da09842dc46564e80d3 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Wed, 14 Oct 2026 09:52:13 +0200 Subject: [PATCH 15/31] Avoid relying on maxsize in decimal tests --- graalpython/lib-python/3/_pydecimal.py | 3 ++- graalpython/lib-python/3/test/test_decimal.py | 25 +++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/graalpython/lib-python/3/_pydecimal.py b/graalpython/lib-python/3/_pydecimal.py index f83524a256..55565b3c8e 100644 --- a/graalpython/lib-python/3/_pydecimal.py +++ b/graalpython/lib-python/3/_pydecimal.py @@ -79,7 +79,8 @@ # Compatibility with the C version HAVE_THREADS = True HAVE_CONTEXTVAR = True -if sys.maxsize == 2**63-1: +# GraalPy change: assume 64bit regardless of maxsize +if True: MAX_PREC = 999999999999999999 MAX_EMAX = 999999999999999999 MIN_EMIN = -999999999999999999 diff --git a/graalpython/lib-python/3/test/test_decimal.py b/graalpython/lib-python/3/test/test_decimal.py index 23107c603f..6a5ef703b3 100644 --- a/graalpython/lib-python/3/test/test_decimal.py +++ b/graalpython/lib-python/3/test/test_decimal.py @@ -65,6 +65,9 @@ requires_cdecimal = unittest.skipUnless(C, "test requires C version") +# GraalPy change: decimal uses 64-bit limits despite its 32-bit sys.maxsize. +DECIMAL_MAXSIZE = 2**63-1 if sys.implementation.name == 'graalpy' else sys.maxsize + # Useful Test Constant Signals = { C: tuple(C.getcontext().flags.keys()) if C else None, @@ -5579,18 +5582,19 @@ def test_exact_conversion(self): c.traps[InvalidOperation] = True # Clamped - x = "0e%d" % sys.maxsize + # GraalPy change: use decimal's 64-bit boundaries. + x = "0e%d" % DECIMAL_MAXSIZE self.assertRaises(InvalidOperation, Decimal, x) - x = "0e%d" % (-sys.maxsize-1) + x = "0e%d" % (-DECIMAL_MAXSIZE-1) self.assertRaises(InvalidOperation, Decimal, x) # Overflow - x = "1e%d" % sys.maxsize + x = "1e%d" % DECIMAL_MAXSIZE self.assertRaises(InvalidOperation, Decimal, x) # Underflow - x = "1e%d" % (-sys.maxsize-1) + x = "1e%d" % (-DECIMAL_MAXSIZE-1) self.assertRaises(InvalidOperation, Decimal, x) def test_from_tuple(self): @@ -5608,29 +5612,30 @@ def test_from_tuple(self): c.traps[Underflow] = True # SSIZE_MAX - x = (1, (), sys.maxsize) + # GraalPy change: use decimal's 64-bit boundaries. + x = (1, (), DECIMAL_MAXSIZE) self.assertEqual(str(c.create_decimal(x)), '-0E+999999') self.assertRaises(InvalidOperation, Decimal, x) - x = (1, (0, 1, 2), sys.maxsize) + x = (1, (0, 1, 2), DECIMAL_MAXSIZE) self.assertRaises(Overflow, c.create_decimal, x) self.assertRaises(InvalidOperation, Decimal, x) # SSIZE_MIN - x = (1, (), -sys.maxsize-1) + x = (1, (), -DECIMAL_MAXSIZE-1) self.assertEqual(str(c.create_decimal(x)), '-0E-1000007') self.assertRaises(InvalidOperation, Decimal, x) - x = (1, (0, 1, 2), -sys.maxsize-1) + x = (1, (0, 1, 2), -DECIMAL_MAXSIZE-1) self.assertRaises(Underflow, c.create_decimal, x) self.assertRaises(InvalidOperation, Decimal, x) # OverflowError - x = (1, (), sys.maxsize+1) + x = (1, (), DECIMAL_MAXSIZE+1) self.assertRaises(OverflowError, c.create_decimal, x) self.assertRaises(OverflowError, Decimal, x) - x = (1, (), -sys.maxsize-2) + x = (1, (), -DECIMAL_MAXSIZE-2) self.assertRaises(OverflowError, c.create_decimal, x) self.assertRaises(OverflowError, Decimal, x) From a4ce555c86c9a4096d84f5b2af9bad38a9a30345 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Wed, 14 Oct 2026 09:52:42 +0200 Subject: [PATCH 16/31] Profile rounding mode in quantize --- .../graal/python/builtins/objects/decimal/DecimalNodes.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java index d6c7dacd33..cee627d5c6 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java @@ -97,6 +97,7 @@ import com.oracle.truffle.api.profiles.InlinedBranchProfile; import com.oracle.truffle.api.profiles.InlinedConditionProfile; import com.oracle.truffle.api.profiles.InlinedLoopConditionProfile; +import com.oracle.truffle.api.profiles.ValueProfile; import com.oracle.truffle.api.strings.TruffleString; import com.oracle.truffle.api.strings.TruffleStringBuilder; import com.oracle.truffle.api.strings.TruffleStringBuilderUTF32; @@ -1163,8 +1164,10 @@ public abstract static class QuantizeDecimalNode extends Node { @Specialization static PDecimal quantize(VirtualFrame frame, Node inliningTarget, PDecimal self, long exponent, PDecimalContext context, DecimalRounding rounding, @Cached InlinedBranchProfile nonCompactProfile, + @Cached(value = "createIdentityProfile()", inline = false) ValueProfile roundingProfile, @Cached RaiseSignalNode raiseSignal, @Cached PRaiseNode raiseNode) { + rounding = roundingProfile.profile(rounding); PythonLanguage language = PythonLanguage.get(inliningTarget); PDecimal fast = compactQuantize(frame, language, self, exponent, context, rounding, raiseSignal); if (fast != null) { From e2158c2004657707dd769b1a34598b320d593ff7 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Wed, 14 Oct 2026 09:54:08 +0200 Subject: [PATCH 17/31] Add inline cache to contextvar lookup --- .../test/builtin/modules/HamtTests.java | 23 ++++- .../contextvars/ContextVarBuiltins.java | 10 +- .../objects/contextvars/ContextVarNodes.java | 95 +++++++++++++++++++ .../builtins/objects/contextvars/Hamt.java | 19 ++-- .../objects/decimal/DecimalModuleState.java | 10 +- .../objects/decimal/DecimalNodes.java | 7 +- .../graal/python/runtime/PythonContext.java | 9 +- 7 files changed, 152 insertions(+), 21 deletions(-) create mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/ContextVarNodes.java diff --git a/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/builtin/modules/HamtTests.java b/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/builtin/modules/HamtTests.java index de8c549591..c4be366684 100644 --- a/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/builtin/modules/HamtTests.java +++ b/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/builtin/modules/HamtTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -42,6 +42,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -80,6 +81,26 @@ public void testHamtSetLookup() { assertEquals(1, hamt.lookup(1, 1)); } + @Test + public void testLookupEntry() { + Hamt.Entry first = new Hamt.Entry(1, 1, 42); + Hamt.Entry collision = new Hamt.Entry(2, 1, 99); + Hamt.Entry otherBranch = new Hamt.Entry(3, 32, 88); + Hamt empty = new Hamt(); + assertNull(empty.lookupEntry(1, 1)); + Hamt values = empty.withEntry(first).withEntry(collision).withEntry(otherBranch); + assertSame(first, values.lookupEntry(1, 1)); + assertSame(collision, values.lookupEntry(2, 1)); + assertSame(otherBranch, values.lookupEntry(3, 32)); + assertNull(values.lookupEntry(4, 1)); + Hamt.Entry replacement = new Hamt.Entry(3, 32, 89); + Hamt changed = values.withEntry(replacement); + assertSame(replacement, changed.lookupEntry(3, 32)); + assertSame(first, values.lookupEntry(1, 1)); + assertEquals(89, changed.lookup(3, 32)); + assertEquals(88, values.lookup(3, 32)); + } + @Test public void testBitmapExtremes() { Hamt hamt = new Hamt(); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/ContextVarBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/ContextVarBuiltins.java index 66ecc23206..7c864ed7d8 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/ContextVarBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/ContextVarBuiltins.java @@ -58,6 +58,7 @@ import com.oracle.graal.python.builtins.PythonBuiltins; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.contextvars.ContextVarBuiltinsClinicProviders.ContextVarNodeClinicProviderGen; +import com.oracle.graal.python.builtins.objects.contextvars.ContextVarNodes.GetValueNode; import com.oracle.graal.python.builtins.objects.type.TpSlots; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; @@ -119,14 +120,21 @@ public abstract static class GetNode extends PythonBinaryBuiltinNode { static Object get(PContextVar self, Object def, @Bind Node inliningTarget, @Cached InlinedConditionProfile defIsNoValueProfile, + @Cached GetValueNode getValue, @Cached PRaiseNode raiseNode) { Object defValue = defIsNoValueProfile.profile(inliningTarget, isNoValue(def)) ? PContextVar.NO_DEFAULT : def; PythonContext context = PythonContext.get(inliningTarget); PythonContext.PythonThreadState threadState = context.getThreadState(context.getLanguage(inliningTarget)); - Object value = self.get(inliningTarget, threadState, defValue); + Object value = getValue.execute(inliningTarget, self, threadState); if (value != null) { return value; } + if (defValue != PContextVar.NO_DEFAULT) { + return defValue; + } + if (self.getDefault() != PContextVar.NO_DEFAULT) { + return self.getDefault(); + } throw raiseNode.raise(inliningTarget, LookupError); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/ContextVarNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/ContextVarNodes.java new file mode 100644 index 0000000000..f5f94caad2 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/ContextVarNodes.java @@ -0,0 +1,95 @@ +/* + * 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. + */ +package com.oracle.graal.python.builtins.objects.contextvars; + +import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.nodes.PNodeWithContext; +import com.oracle.graal.python.runtime.PythonContext.PythonThreadState; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Cached.Shared; +import com.oracle.truffle.api.dsl.GenerateCached; +import com.oracle.truffle.api.dsl.GenerateInline; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.profiles.InlinedBranchProfile; + +public final class ContextVarNodes { + private ContextVarNodes() { + } + + @GenerateInline + @GenerateCached(false) + @GenerateUncached + public abstract static class GetValueNode extends PNodeWithContext { + public abstract Object execute(Node inliningTarget, PContextVar var, PythonThreadState threadState); + + public static GetValueNode getUncached() { + return ContextVarNodesFactory.GetValueNodeGen.getUncached(); + } + + @SuppressWarnings("unused") + @Specialization(guards = {"isSingleContext(inliningTarget)", "values == cachedValues", "var == cachedVar"}, limit = "3", excludeForUncached = true) + static Object doCached(Node inliningTarget, PContextVar var, PythonThreadState threadState, + @Shared("initializeContext") @Cached InlinedBranchProfile initializeContextProfile, + @Bind("threadState.getContextVarsContext(inliningTarget, initializeContextProfile).contextVarValues") Hamt values, + @Cached(value = "values", weak = true) Hamt cachedValues, + @Cached(value = "var", weak = true) PContextVar cachedVar, + @Cached(value = "lookupValue(cachedValues, cachedVar)", weak = true) Object cachedValue) { + return cachedValue == PNone.NO_VALUE ? null : cachedValue; + } + + @Specialization(replaces = "doCached") + static Object doGeneric(Node inliningTarget, PContextVar var, PythonThreadState threadState, + @Shared("initializeContext") @Cached InlinedBranchProfile initializeContextProfile) { + return var.getValue(inliningTarget, threadState, initializeContextProfile); + } + + @TruffleBoundary + static Object lookupValue(Hamt values, PContextVar var) { + Object value = values.lookup(var, var.getHash()); + // Weak caches cannot hold null, so use the sentinel for an unset variable. + return value == null ? PNone.NO_VALUE : value; + } + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/Hamt.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/Hamt.java index 34d24f1e50..b58023167e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/Hamt.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/contextvars/Hamt.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -223,7 +223,7 @@ public Hamt withEntry(Entry newEntry) { } @CompilerDirectives.TruffleBoundary - private static Object lookupKeyInPart(TreePart part, Object key, int hash, int hashShift) { + private static Entry lookupEntryInPart(TreePart part, Object key, int hash, int hashShift) { assert hashShift <= 25; if (part == null) { return null; @@ -231,7 +231,7 @@ private static Object lookupKeyInPart(TreePart part, Object key, int hash, int h if (part instanceof Entry) { Entry existing = (Entry) part; if (existing.hash == hash && PyObjectRichCompareBool.executeEqUncached(existing.key, key)) { - return existing.value; + return existing; } return null; } @@ -243,12 +243,12 @@ private static Object lookupKeyInPart(TreePart part, Object key, int hash, int h return null; } TreePart deeper = existing.elems[sparseIdx]; - return lookupKeyInPart(deeper, key, hash, hashShift + 5); + return lookupEntryInPart(deeper, key, hash, hashShift + 5); } if (part instanceof ArrayPart) { ArrayPart existing = (ArrayPart) part; int position = hashIdx(hash, hashShift); - return lookupKeyInPart(existing.elems[position], key, hash, hashShift + 5); + return lookupEntryInPart(existing.elems[position], key, hash, hashShift + 5); } if (part instanceof CollisionPart) { CollisionPart existing = (CollisionPart) part; @@ -257,7 +257,7 @@ private static Object lookupKeyInPart(TreePart part, Object key, int hash, int h } for (Entry entry : existing.elems) { if (PyObjectRichCompareBool.executeEqUncached(entry.key, key)) { - return entry.value; + return entry; } } return null; @@ -266,7 +266,12 @@ private static Object lookupKeyInPart(TreePart part, Object key, int hash, int h } public Object lookup(Object key, int hash) { - return lookupKeyInPart(root, key, hash, 0); + Entry entry = lookupEntry(key, hash); + return entry == null ? null : entry.value; + } + + public Entry lookupEntry(Object key, int hash) { + return lookupEntryInPart(root, key, hash, 0); } private static TreePart bitmapWithoutKey(BitmapPart existing, Object key, int hash, int hashShift) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java index 6294a59ec6..ebf2d693d6 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalModuleState.java @@ -45,6 +45,7 @@ import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.objects.contextvars.ContextVarNodes.GetValueNode; import com.oracle.graal.python.builtins.objects.contextvars.PContextVar; import com.oracle.graal.python.builtins.objects.type.PythonClass; import com.oracle.graal.python.runtime.PythonContext; @@ -137,16 +138,11 @@ public PDecimalContext copyContext(PythonLanguage language, PDecimalContext orig return copy; } - public PDecimalContext getCurrentContext(Node node) { - return getCurrentContext(node, InlinedBranchProfile.getUncached(), InlinedBranchProfile.getUncached(), InlinedBranchProfile.getUncached()); - } - - public PDecimalContext getCurrentContext(Node node, InlinedBranchProfile initializeContextVarsProfile, InlinedBranchProfile initializeDecimalContextProfile, - InlinedBranchProfile contextVarCacheMissProfile) { + public PDecimalContext getCurrentContext(Node node, InlinedBranchProfile initializeDecimalContextProfile, GetValueNode getValue) { PythonContext context = PythonContext.get(node); PythonLanguage language = context.getLanguage(node); PythonContext.PythonThreadState threadState = context.getThreadState(language); - Object value = currentContext.getValue(node, threadState, initializeContextVarsProfile, contextVarCacheMissProfile); + Object value = getValue.execute(node, currentContext, threadState); if (CompilerDirectives.injectBranchProbability(CompilerDirectives.SLOWPATH_PROBABILITY, value == null)) { initializeDecimalContextProfile.enter(node); return initializeCurrentContext(node, language, threadState); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java index cee627d5c6..349ea567f7 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java @@ -53,6 +53,7 @@ import com.oracle.graal.python.builtins.objects.PNotImplemented; import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; +import com.oracle.graal.python.builtins.objects.contextvars.ContextVarNodes.GetValueNode; import com.oracle.graal.python.builtins.objects.decimal.DecimalArithmetic.Result; import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.ConversionException; import com.oracle.graal.python.builtins.objects.decimal.DecimalUtils.Value; @@ -121,9 +122,9 @@ public abstract static class GetCurrentContextNode extends Node { @Specialization static PDecimalContext get(Node inliningTarget, - @Cached InlinedBranchProfile initializeContextVarsProfile, - @Cached InlinedBranchProfile initializeDecimalContextProfile) { - return DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget, initializeContextVarsProfile, initializeDecimalContextProfile); + @Cached InlinedBranchProfile initializeDecimalContextProfile, + @Cached GetValueNode getValue) { + return DecimalContextUtils.getState(inliningTarget).getCurrentContext(inliningTarget, initializeDecimalContextProfile, getValue); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java index 98601f8943..0f7eb6824c 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java @@ -515,13 +515,18 @@ public PContextVarsContext getContextVarsContext(Node node) { } public PContextVarsContext getContextVarsContext(Node node, InlinedBranchProfile initializeProfile) { - if (contextVarsContext == null) { + if (CompilerDirectives.injectBranchProbability(CompilerDirectives.SLOWPATH_PROBABILITY, contextVarsContext == null)) { initializeProfile.enter(node); - contextVarsContext = PFactory.createContextVarsContext(PythonLanguage.get(node)); + initializeContextVarsContext(node); } return contextVarsContext; } + @TruffleBoundary + private void initializeContextVarsContext(Node node) { + contextVarsContext = PFactory.createContextVarsContext(PythonLanguage.get(node)); + } + public void setContextVarsContext(PContextVarsContext contextVarsContext) { assert contextVarsContext != null; this.contextVarsContext = contextVarsContext; From ace8d31287d4a7a5122a22231cac045ff374f606 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Wed, 14 Oct 2026 10:55:41 +0200 Subject: [PATCH 18/31] Intern decimal rounding constants --- .../graal/python/builtins/objects/decimal/DecimalRounding.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalRounding.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalRounding.java index 36a9708667..393c4b7cc3 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalRounding.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalRounding.java @@ -40,6 +40,7 @@ */ package com.oracle.graal.python.builtins.objects.decimal; +import static com.oracle.graal.python.util.PythonUtils.internString; import static com.oracle.graal.python.util.PythonUtils.tsLiteral; import com.oracle.truffle.api.strings.TruffleString; @@ -58,7 +59,7 @@ public enum DecimalRounding { private final TruffleString pythonName; DecimalRounding() { - pythonName = tsLiteral(name()); + pythonName = internString(tsLiteral(name())); } public TruffleString getPythonName() { From c651aaa3be209c80702d923c3b66cdf4777cdcdc Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Wed, 14 Oct 2026 10:56:00 +0200 Subject: [PATCH 19/31] Match CPython decimal argument names --- .../graal/python/builtins/modules/DecimalModuleBuiltins.java | 2 +- .../builtins/objects/decimal/DecimalContextBuiltins.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java index a940ec23c1..4bb9922a57 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java @@ -197,7 +197,7 @@ static PDecimalContext get(@Bind Node inliningTarget, } } - @Builtin(name = "setcontext", minNumOfPositionalArgs = 1) + @Builtin(name = "setcontext", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"context"}) @GenerateNodeFactory public abstract static class SetcontextNode extends PythonUnaryBuiltinNode { @Specialization diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java index 3f77413f09..5058deef50 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java @@ -1069,7 +1069,7 @@ static PDecimalContext copy(PDecimalContext self, } } - @Builtin(name = "create_decimal", minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2) + @Builtin(name = "create_decimal", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 2, parameterNames = {"self", "num"}) @GenerateNodeFactory public abstract static class CreateDecimalNode extends PythonBinaryBuiltinNode { @Specialization @@ -1107,7 +1107,7 @@ static PDecimal create(VirtualFrame frame, PDecimalContext self, Object value, } } - @Builtin(name = "create_decimal_from_float", minNumOfPositionalArgs = 2) + @Builtin(name = "create_decimal_from_float", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 2, parameterNames = {"self", "f"}) @GenerateNodeFactory public abstract static class CreateDecimalFromFloatNode extends PythonBinaryBuiltinNode { @Specialization From 4b9f2a85fc37d32d3a0429da51fe350a8e6f74d0 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Wed, 14 Oct 2026 10:56:16 +0200 Subject: [PATCH 20/31] Distinguish exact decimal construction from context conversion --- .../objects/decimal/DecimalBuiltins.java | 2 +- .../decimal/DecimalContextBuiltins.java | 2 +- .../objects/decimal/DecimalNodes.java | 44 +++++++++---------- .../objects/decimal/DecimalUtils.java | 31 ++++++++----- 4 files changed, 43 insertions(+), 36 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java index d3c7a0cbe4..449ba2c25e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java @@ -1570,7 +1570,7 @@ static PDecimal construct(VirtualFrame frame, Object cls, Object value, Object c DecimalContextUtils.addStatus(frame, decimalContext, DecimalContextUtils.conditionMask(DecimalExceptionTypes.FloatOperation), raiseSignal); } try { - return createDecimal.execute(frame, inliningTarget, cls, value); + return createDecimal.execute(frame, inliningTarget, cls, value, true); } catch (ConversionException e) { errorProfile.enter(inliningTarget); DecimalContextUtils.addStatus(frame, decimalContext, DecimalContextUtils.conditionMask(e.condition), raiseSignal); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java index 5058deef50..e8a2dd6076 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java @@ -1091,7 +1091,7 @@ static PDecimal create(VirtualFrame frame, PDecimalContext self, Object value, if (unicodeCheck.execute(inliningTarget, value) && invalidContextString(asString.castKnownString(inliningTarget, value), codePointLengthNode, codePointAtIndexNode)) { throw new ConversionException(DecimalExceptionTypes.ConversionSyntax); } - PDecimal operand = createDecimal.execute(frame, inliningTarget, PythonBuiltinClassType.PDecimal, value); + PDecimal operand = createDecimal.execute(frame, inliningTarget, PythonBuiltinClassType.PDecimal, value, false); if ((operand.getKind() == Kind.NAN || operand.getKind() == Kind.SNAN) && operand.getCoefficient().signum() != 0 && DecimalUtils.digits(operand.getCoefficient()) > self.getPrecision() - self.getClamp()) { throw new ConversionException(DecimalExceptionTypes.ConversionSyntax); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java index 349ea567f7..46f5d87c28 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java @@ -295,16 +295,16 @@ static PDecimal convert(Node inliningTarget, Object value, @GenerateUncached @ImportStatic(PGuards.class) public abstract static class CreateDecimalNode extends Node { - public abstract PDecimal execute(VirtualFrame frame, Node inliningTarget, Object cls, Object value) throws ConversionException; + public abstract PDecimal execute(VirtualFrame frame, Node inliningTarget, Object cls, Object value, boolean exact) throws ConversionException; @Specialization(guards = "isNoValue(value)") - static PDecimal doDefault(@SuppressWarnings("unused") Node inliningTarget, Object cls, @SuppressWarnings("unused") PNone value, + static PDecimal doDefault(@SuppressWarnings("unused") Node inliningTarget, Object cls, @SuppressWarnings("unused") PNone value, @SuppressWarnings("unused") boolean exact, @Shared @Cached TypeNodes.GetInstanceShape getShape) { return PFactory.createDecimal(cls, getShape.execute(cls), 0L); } @Specialization - static PDecimal doDecimal(Node inliningTarget, Object cls, PDecimal value, + static PDecimal doDecimal(Node inliningTarget, Object cls, PDecimal value, @SuppressWarnings("unused") boolean exact, @Shared @Cached GetClassNode getClass, @Shared @Cached IsBuiltinClassExactProfile clsProfile, @Shared @Cached IsBuiltinClassExactProfile valueProfile, @@ -317,7 +317,7 @@ static PDecimal doDecimal(Node inliningTarget, Object cls, PDecimal value, } @Specialization - static PDecimal doTruffleString(Node inliningTarget, Object cls, TruffleString value, + static PDecimal doTruffleString(Node inliningTarget, Object cls, TruffleString value, boolean exact, @Cached TruffleString.CodePointAtIndexUTF32Node read, @Cached TruffleString.ToJavaStringNode toJavaString, @Cached InlinedLoopConditionProfile coefficientLoopProfile, @@ -327,37 +327,37 @@ static PDecimal doTruffleString(Node inliningTarget, Object cls, TruffleString v if (result != null) { return result; } - return createDecimal(cls, DecimalUtils.parse(toJavaString.execute(value)), getShape); + return createDecimal(cls, DecimalUtils.parse(toJavaString.execute(value), exact), getShape); } @Specialization(guards = "unicodeCheck.execute(inliningTarget, value)") - static PDecimal doString(Node inliningTarget, Object cls, Object value, + static PDecimal doString(Node inliningTarget, Object cls, Object value, boolean exact, @SuppressWarnings("unused") @Shared @Cached PyUnicodeCheckNode unicodeCheck, @Cached CastToJavaStringNode asString, @Shared @Cached TypeNodes.GetInstanceShape getShape) throws ConversionException { - return createDecimal(cls, DecimalUtils.parse(asString.execute(value)), getShape); + return createDecimal(cls, DecimalUtils.parse(asString.execute(value), exact), getShape); } @Specialization - static PDecimal doInt(@SuppressWarnings("unused") Node inliningTarget, Object cls, int value, + static PDecimal doInt(@SuppressWarnings("unused") Node inliningTarget, Object cls, int value, @SuppressWarnings("unused") boolean exact, @Shared @Cached TypeNodes.GetInstanceShape getShape) { return PFactory.createDecimal(cls, getShape.execute(cls), value); } @Specialization - static PDecimal doLong(@SuppressWarnings("unused") Node inliningTarget, Object cls, long value, + static PDecimal doLong(@SuppressWarnings("unused") Node inliningTarget, Object cls, long value, @SuppressWarnings("unused") boolean exact, @Shared @Cached TypeNodes.GetInstanceShape getShape) { return PFactory.createDecimal(cls, getShape.execute(cls), value); } @Specialization - static PDecimal doPInt(@SuppressWarnings("unused") Node inliningTarget, Object cls, PInt value, + static PDecimal doPInt(@SuppressWarnings("unused") Node inliningTarget, Object cls, PInt value, @SuppressWarnings("unused") boolean exact, @Shared @Cached TypeNodes.GetInstanceShape getShape) { return createDecimal(cls, DecimalUtils.fromInteger(value.getValue()), getShape); } @Specialization(guards = "longCheck.execute(inliningTarget, value)") - static PDecimal doIntGeneric(Node inliningTarget, Object cls, Object value, + static PDecimal doIntGeneric(Node inliningTarget, Object cls, Object value, @SuppressWarnings("unused") boolean exact, @SuppressWarnings("unused") @Shared @Cached PyLongCheckNode longCheck, @Shared @Cached CastToJavaBigIntegerNode asBigInteger, @Shared @Cached TypeNodes.GetInstanceShape getShape) { @@ -365,21 +365,21 @@ static PDecimal doIntGeneric(Node inliningTarget, Object cls, Object value, } @Specialization - static PDecimal doDouble(Node inliningTarget, Object cls, double value, + static PDecimal doDouble(Node inliningTarget, Object cls, double value, @SuppressWarnings("unused") boolean exact, @Shared @Cached InlinedBranchProfile nonCompactProfile, @Shared @Cached TypeNodes.GetInstanceShape getShape) { return fromDouble(inliningTarget, cls, value, getShape, nonCompactProfile); } @Specialization - static PDecimal doPFloat(Node inliningTarget, Object cls, PFloat value, + static PDecimal doPFloat(Node inliningTarget, Object cls, PFloat value, @SuppressWarnings("unused") boolean exact, @Shared @Cached InlinedBranchProfile nonCompactProfile, @Shared @Cached TypeNodes.GetInstanceShape getShape) { return fromDouble(inliningTarget, cls, value.getValue(), getShape, nonCompactProfile); } @Specialization(guards = "floatCheck.execute(inliningTarget, value)") - static PDecimal doFloatGeneric(VirtualFrame frame, Node inliningTarget, Object cls, Object value, + static PDecimal doFloatGeneric(VirtualFrame frame, Node inliningTarget, Object cls, Object value, @SuppressWarnings("unused") boolean exact, @SuppressWarnings("unused") @Shared @Cached PyFloatCheckNode floatCheck, @Shared @Cached PyFloatAsDoubleNode asDouble, @Shared @Cached InlinedBranchProfile nonCompactProfile, @@ -388,7 +388,7 @@ static PDecimal doFloatGeneric(VirtualFrame frame, Node inliningTarget, Object c } @Specialization(guards = "tupleCheck.execute(inliningTarget, value)") - static PDecimal doTuple(VirtualFrame frame, Node inliningTarget, Object cls, Object value, + static PDecimal doTuple(VirtualFrame frame, Node inliningTarget, Object cls, Object value, boolean exact, @SuppressWarnings("unused") @Shared @Cached PyTupleCheckNode tupleCheck, @Shared @Cached GetTupleStorage getTupleStorage, @Shared @Cached("createFor($node)") BoundaryCallData boundaryCallData, @@ -396,14 +396,14 @@ static PDecimal doTuple(VirtualFrame frame, Node inliningTarget, Object cls, Obj SequenceStorage storage = getTupleStorage.execute(inliningTarget, value); Object state = BoundaryCallContext.enter(frame, boundaryCallData); try { - return createDecimal(cls, fromSequence(storage), getShape); + return createDecimal(cls, fromSequence(storage, exact), getShape); } finally { BoundaryCallContext.exit(frame, boundaryCallData, state); } } @Specialization(guards = "listCheck.execute(inliningTarget, value)") - static PDecimal doList(VirtualFrame frame, Node inliningTarget, Object cls, Object value, + static PDecimal doList(VirtualFrame frame, Node inliningTarget, Object cls, Object value, boolean exact, @SuppressWarnings("unused") @Shared @Cached PyListCheckNode listCheck, @Shared @Cached GetListStorageNode getListStorage, @Shared @Cached("createFor($node)") BoundaryCallData boundaryCallData, @@ -411,14 +411,14 @@ static PDecimal doList(VirtualFrame frame, Node inliningTarget, Object cls, Obje SequenceStorage storage = getListStorage.execute(inliningTarget, value); Object state = BoundaryCallContext.enter(frame, boundaryCallData); try { - return createDecimal(cls, fromSequence(storage), getShape); + return createDecimal(cls, fromSequence(storage, exact), getShape); } finally { BoundaryCallContext.exit(frame, boundaryCallData, state); } } @Fallback - static PDecimal doUnsupported(Node inliningTarget, @SuppressWarnings("unused") Object cls, Object value, + static PDecimal doUnsupported(Node inliningTarget, @SuppressWarnings("unused") Object cls, Object value, @SuppressWarnings("unused") boolean exact, @Shared @Cached GetClassNode getClass, @Shared @Cached TypeNodes.GetNameNode getName, @Shared @Cached PRaiseNode raiseNode) { @@ -554,7 +554,7 @@ private static PDecimal fromDouble(Node inliningTarget, Object cls, double value } @TruffleBoundary - private static Value fromSequence(SequenceStorage triple) throws ConversionException { + private static Value fromSequence(SequenceStorage triple, boolean exact) throws ConversionException { PRaiseNode raiseNode = PRaiseNode.getUncached(); // Decimal triples are (sign, digits, exponent), at indices 0, 1, and 2. if (triple.length() != 3) { @@ -616,9 +616,9 @@ private static Value fromSequence(SequenceStorage triple) throws ConversionExcep integer = new BigInteger(coefficient.toString()); } if (kind == Kind.FINITE) { - return DecimalUtils.finite(signValue != 0, integer, exponent); + return DecimalUtils.finite(signValue != 0, integer, exponent, exact); } - return new Value(signValue != 0, integer, 0, kind); + return new Value(signValue != 0, integer, exponent, kind); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java index 7b2bfb4862..f9c15cb124 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalUtils.java @@ -166,7 +166,7 @@ static Value fromFloat(double value) { } @TruffleBoundary - static Value parse(String input) throws ConversionException { + static Value parse(String input, boolean exact) throws ConversionException { int start = 0; int end = input.length(); while (start < end && StringUtils.isUnicodeWhitespace(input.codePointAt(start))) { @@ -246,7 +246,7 @@ static Value parse(String input) throws ConversionException { if (coefficient.isEmpty()) { throw new ConversionException(DecimalExceptionTypes.ConversionSyntax); } - long exponent = 0; + BigInteger exponent = BigInteger.ZERO; if (pos < text.length() && (text.charAt(pos) == 'e' || text.charAt(pos) == 'E')) { int exponentStart = ++pos; if (pos < text.length() && (text.charAt(pos) == '+' || text.charAt(pos) == '-')) { @@ -259,29 +259,36 @@ static Value parse(String input) throws ConversionException { if (pos == digitsStart || pos != text.length()) { throw new ConversionException(DecimalExceptionTypes.ConversionSyntax); } - try { - exponent = new BigInteger(text.substring(exponentStart)).longValueExact(); - } catch (ArithmeticException e) { - throw new ConversionException(DecimalExceptionTypes.InvalidOperation); - } + exponent = new BigInteger(text.substring(exponentStart)); } if (pos != text.length()) { throw new ConversionException(DecimalExceptionTypes.ConversionSyntax); } + exponent = exponent.subtract(BigInteger.valueOf(fractionalDigits)); + if (!exact) { + // String exponents need not fit in a long. Saturate only after accounting for + // fractional digits, so context conversion can still signal overflow/underflow. + exponent = exponent.max(BigInteger.valueOf(MIN_ETINY - 1)).min(BigInteger.valueOf(MAX_EMAX + 1)); + } try { - exponent = Math.subtractExact(exponent, fractionalDigits); + return finite(negative, new BigInteger(coefficient.toString()), exponent.longValueExact(), exact); } catch (ArithmeticException e) { throw new ConversionException(DecimalExceptionTypes.InvalidOperation); } - return finite(negative, new BigInteger(coefficient.toString()), exponent); } @TruffleBoundary - static Value finite(boolean negative, BigInteger coefficient, long exponent) throws ConversionException { + static Value finite(boolean negative, BigInteger coefficient, long exponent, boolean exact) throws ConversionException { // The adjusted exponent locates the most significant digit: exponent + digits - 1. // Both the stored exponent and the adjusted exponent must fit libmpdec's limits. - if (exponent < MIN_ETINY || exponent > MAX_EMAX || exponent + digits(coefficient) - 1 > MAX_EMAX) { - throw new ConversionException(DecimalExceptionTypes.InvalidOperation); + if (exact) { + if (exponent < MIN_ETINY || exponent > MAX_EMAX || exponent + digits(coefficient) - 1 > MAX_EMAX) { + throw new ConversionException(DecimalExceptionTypes.InvalidOperation); + } + } else { + // Preserve overflow/underflow for context conversion, but keep subsequent + // exponent arithmetic within long bounds even for Long.MIN_VALUE/MAX_VALUE. + exponent = Math.max(MIN_ETINY - 1, Math.min(exponent, MAX_EMAX + 1)); } return new Value(negative, coefficient, exponent, Kind.FINITE); } From 290ca35c206751478de5ea0372f4f1182c1d9442 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Wed, 14 Oct 2026 17:59:51 +0200 Subject: [PATCH 21/31] Retag decimal tests --- .../src/tests/unittest_tags/test_decimal.txt | 327 ++++++++++++++++++ 1 file changed, 327 insertions(+) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_decimal.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_decimal.txt index 43332c8b5a..b61c7eadc5 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_decimal.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_decimal.txt @@ -64,6 +64,332 @@ DocTestCase.decimal.Decimal.as_integer_ratio @ darwin-arm64,linux-aarch64,linux- DocTestCase.decimal.Decimal.from_float @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github DocTestCase.decimal._decimal_lshift_exact @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64-github DocTestCase.decimal.localcontext @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_decimal.CArithmeticOperatorsTest.test_addition @ linux-x86_64 +test.test_decimal.CArithmeticOperatorsTest.test_copy_sign @ linux-x86_64 +test.test_decimal.CArithmeticOperatorsTest.test_division @ linux-x86_64 +test.test_decimal.CArithmeticOperatorsTest.test_floor_div_module @ linux-x86_64 +test.test_decimal.CArithmeticOperatorsTest.test_floor_division @ linux-x86_64 +test.test_decimal.CArithmeticOperatorsTest.test_module @ linux-x86_64 +test.test_decimal.CArithmeticOperatorsTest.test_multiplication @ linux-x86_64 +test.test_decimal.CArithmeticOperatorsTest.test_nan_comparisons @ linux-x86_64 +test.test_decimal.CArithmeticOperatorsTest.test_powering @ linux-x86_64 +test.test_decimal.CArithmeticOperatorsTest.test_subtraction @ linux-x86_64 +test.test_decimal.CArithmeticOperatorsTest.test_unary_operators @ linux-x86_64 +test.test_decimal.CContextAPItests.test__clamp @ linux-x86_64 +test.test_decimal.CContextAPItests.test_abs @ linux-x86_64 +test.test_decimal.CContextAPItests.test_add @ linux-x86_64 +test.test_decimal.CContextAPItests.test_compare @ linux-x86_64 +test.test_decimal.CContextAPItests.test_compare_signal @ linux-x86_64 +test.test_decimal.CContextAPItests.test_compare_total @ linux-x86_64 +test.test_decimal.CContextAPItests.test_compare_total_mag @ linux-x86_64 +test.test_decimal.CContextAPItests.test_copy @ linux-x86_64 +test.test_decimal.CContextAPItests.test_copy_abs @ linux-x86_64 +test.test_decimal.CContextAPItests.test_copy_decimal @ linux-x86_64 +test.test_decimal.CContextAPItests.test_copy_negate @ linux-x86_64 +test.test_decimal.CContextAPItests.test_copy_sign @ linux-x86_64 +test.test_decimal.CContextAPItests.test_divide @ linux-x86_64 +test.test_decimal.CContextAPItests.test_divide_int @ linux-x86_64 +test.test_decimal.CContextAPItests.test_divmod @ linux-x86_64 +test.test_decimal.CContextAPItests.test_equality_with_other_types @ linux-x86_64 +test.test_decimal.CContextAPItests.test_exp @ linux-x86_64 +test.test_decimal.CContextAPItests.test_fma @ linux-x86_64 +test.test_decimal.CContextAPItests.test_is_finite @ linux-x86_64 +test.test_decimal.CContextAPItests.test_is_infinite @ linux-x86_64 +test.test_decimal.CContextAPItests.test_is_nan @ linux-x86_64 +test.test_decimal.CContextAPItests.test_is_normal @ linux-x86_64 +test.test_decimal.CContextAPItests.test_is_qnan @ linux-x86_64 +test.test_decimal.CContextAPItests.test_is_signed @ linux-x86_64 +test.test_decimal.CContextAPItests.test_is_snan @ linux-x86_64 +test.test_decimal.CContextAPItests.test_is_subnormal @ linux-x86_64 +test.test_decimal.CContextAPItests.test_is_zero @ linux-x86_64 +test.test_decimal.CContextAPItests.test_ln @ linux-x86_64 +test.test_decimal.CContextAPItests.test_log10 @ linux-x86_64 +test.test_decimal.CContextAPItests.test_logb @ linux-x86_64 +test.test_decimal.CContextAPItests.test_logical_and @ linux-x86_64 +test.test_decimal.CContextAPItests.test_logical_invert @ linux-x86_64 +test.test_decimal.CContextAPItests.test_logical_or @ linux-x86_64 +test.test_decimal.CContextAPItests.test_logical_xor @ linux-x86_64 +test.test_decimal.CContextAPItests.test_max @ linux-x86_64 +test.test_decimal.CContextAPItests.test_max_mag @ linux-x86_64 +test.test_decimal.CContextAPItests.test_min @ linux-x86_64 +test.test_decimal.CContextAPItests.test_min_mag @ linux-x86_64 +test.test_decimal.CContextAPItests.test_minus @ linux-x86_64 +test.test_decimal.CContextAPItests.test_multiply @ linux-x86_64 +test.test_decimal.CContextAPItests.test_next_minus @ linux-x86_64 +test.test_decimal.CContextAPItests.test_next_plus @ linux-x86_64 +test.test_decimal.CContextAPItests.test_next_toward @ linux-x86_64 +test.test_decimal.CContextAPItests.test_none_args @ linux-x86_64 +test.test_decimal.CContextAPItests.test_normalize @ linux-x86_64 +test.test_decimal.CContextAPItests.test_number_class @ linux-x86_64 +test.test_decimal.CContextAPItests.test_pickle @ linux-x86_64 +test.test_decimal.CContextAPItests.test_plus @ linux-x86_64 +test.test_decimal.CContextAPItests.test_power @ linux-x86_64 +test.test_decimal.CContextAPItests.test_quantize @ linux-x86_64 +test.test_decimal.CContextAPItests.test_remainder @ linux-x86_64 +test.test_decimal.CContextAPItests.test_remainder_near @ linux-x86_64 +test.test_decimal.CContextAPItests.test_rotate @ linux-x86_64 +test.test_decimal.CContextAPItests.test_same_quantum @ linux-x86_64 +test.test_decimal.CContextAPItests.test_scaleb @ linux-x86_64 +test.test_decimal.CContextAPItests.test_shift @ linux-x86_64 +test.test_decimal.CContextAPItests.test_sqrt @ linux-x86_64 +test.test_decimal.CContextAPItests.test_subtract @ linux-x86_64 +test.test_decimal.CContextAPItests.test_to_eng_string @ linux-x86_64 +test.test_decimal.CContextAPItests.test_to_integral_exact @ linux-x86_64 +test.test_decimal.CContextAPItests.test_to_integral_value @ linux-x86_64 +test.test_decimal.CContextAPItests.test_to_sci_string @ linux-x86_64 +test.test_decimal.CContextFlags.test_flag_comparisons @ linux-x86_64 +test.test_decimal.CContextFlags.test_flags_irrelevant @ linux-x86_64 +test.test_decimal.CContextFlags.test_float_comparison @ linux-x86_64 +test.test_decimal.CContextFlags.test_float_operation @ linux-x86_64 +test.test_decimal.CContextFlags.test_float_operation_default @ linux-x86_64 +test.test_decimal.CContextInputValidation.test_invalid_context @ linux-x86_64 +test.test_decimal.CContextSubclassing.test_context_subclassing @ linux-x86_64 +test.test_decimal.CContextWithStatement.test_local_context_kwargs_does_not_overwrite_existing_argument @ linux-x86_64 +test.test_decimal.CContextWithStatement.test_localcontext @ linux-x86_64 +test.test_decimal.CContextWithStatement.test_localcontext_kwargs @ linux-x86_64 +test.test_decimal.CContextWithStatement.test_localcontextarg @ linux-x86_64 +test.test_decimal.CContextWithStatement.test_nested_with_statements @ linux-x86_64 +test.test_decimal.CContextWithStatement.test_with_statements_gc1 @ linux-x86_64 +test.test_decimal.CContextWithStatement.test_with_statements_gc2 @ linux-x86_64 +test.test_decimal.CContextWithStatement.test_with_statements_gc3 @ linux-x86_64 +test.test_decimal.CCoverage.test_adjusted @ linux-x86_64 +test.test_decimal.CCoverage.test_canonical @ linux-x86_64 +test.test_decimal.CCoverage.test_context_repr @ linux-x86_64 +test.test_decimal.CCoverage.test_copy @ linux-x86_64 +test.test_decimal.CCoverage.test_create_decimal @ linux-x86_64 +test.test_decimal.CCoverage.test_divmod @ linux-x86_64 +test.test_decimal.CCoverage.test_implicit_context @ linux-x86_64 +test.test_decimal.CCoverage.test_int @ linux-x86_64 +test.test_decimal.CCoverage.test_power @ linux-x86_64 +test.test_decimal.CCoverage.test_quantize @ linux-x86_64 +test.test_decimal.CCoverage.test_radix @ linux-x86_64 +test.test_decimal.CCoverage.test_rop @ linux-x86_64 +test.test_decimal.CCoverage.test_round @ linux-x86_64 +test.test_decimal.CExplicitConstructionTest.test_explicit_context_create_decimal @ linux-x86_64 +test.test_decimal.CExplicitConstructionTest.test_explicit_context_create_from_float @ linux-x86_64 +test.test_decimal.CExplicitConstructionTest.test_explicit_empty @ linux-x86_64 +test.test_decimal.CExplicitConstructionTest.test_explicit_from_Decimal @ linux-x86_64 +test.test_decimal.CExplicitConstructionTest.test_explicit_from_None @ linux-x86_64 +test.test_decimal.CExplicitConstructionTest.test_explicit_from_bool @ linux-x86_64 +test.test_decimal.CExplicitConstructionTest.test_explicit_from_float @ linux-x86_64 +test.test_decimal.CExplicitConstructionTest.test_explicit_from_int @ linux-x86_64 +test.test_decimal.CExplicitConstructionTest.test_explicit_from_list @ linux-x86_64 +test.test_decimal.CExplicitConstructionTest.test_explicit_from_string @ linux-x86_64 +test.test_decimal.CExplicitConstructionTest.test_explicit_from_tuples @ linux-x86_64 +test.test_decimal.CExplicitConstructionTest.test_unicode_digits @ linux-x86_64 +test.test_decimal.CFormatTest.test_decimal_from_float_argument_type @ linux-x86_64 +test.test_decimal.CFormatTest.test_deprecated_N_format @ linux-x86_64 +test.test_decimal.CFormatTest.test_formatting @ linux-x86_64 +test.test_decimal.CFormatTest.test_n_format @ linux-x86_64 +test.test_decimal.CFormatTest.test_negative_zero_bad_format @ linux-x86_64 +test.test_decimal.CFormatTest.test_negative_zero_format_directed_rounding @ linux-x86_64 +test.test_decimal.CFormatTest.test_wide_char_separator_decimal_point @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_abs @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_add @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_and @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_base @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_clamp @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_class @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_compare @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_comparetotal @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_comparetotmag @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_copy @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_copyabs @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_copynegate @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_copysign @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddAbs @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddAdd @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddAnd @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddBase @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddCanonical @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddClass @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddCompare @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddCompareSig @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddCompareTotal @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddCompareTotalMag @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddCopy @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddCopyAbs @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddCopyNegate @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddCopySign @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddDivide @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddDivideInt @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddEncode @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddFMA @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddInvert @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddLogB @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddMax @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddMaxMag @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddMin @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddMinMag @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddMinus @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddMultiply @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddNextMinus @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddNextPlus @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddNextToward @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddOr @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddPlus @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddQuantize @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddReduce @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddRemainder @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddRemainderNear @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddRotate @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddSameQuantum @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddScaleB @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddShift @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddSubtract @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddToIntegral @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ddXor @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_decDouble @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_decQuad @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_decSingle @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_divide @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_divideint @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqAbs @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqAdd @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqAnd @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqBase @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqCanonical @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqClass @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqCompare @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqCompareSig @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqCompareTotal @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqCompareTotalMag @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqCopy @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqCopyAbs @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqCopyNegate @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqCopySign @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqDivide @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqDivideInt @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqEncode @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqFMA @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqInvert @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqLogB @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqMax @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqMaxMag @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqMin @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqMinMag @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqMinus @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqMultiply @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqNextMinus @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqNextPlus @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqNextToward @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqOr @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqPlus @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqQuantize @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqReduce @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqRemainder @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqRemainderNear @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqRotate @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqSameQuantum @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqScaleB @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqShift @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqSubtract @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqToIntegral @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dqXor @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dsBase @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_dsEncode @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_exp @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_extra @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_fma @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_inexact @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_invert @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_ln @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_log10 @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_logb @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_max @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_maxmag @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_min @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_minmag @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_minus @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_multiply @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_nextminus @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_nextplus @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_nexttoward @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_or @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_plus @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_power @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_powersqrt @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_quantize @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_randomBound32 @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_randoms @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_reduce @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_remainder @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_remainderNear @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_rescale @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_rotate @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_rounding @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_samequantum @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_scaleb @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_shift @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_squareroot @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_subtract @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_testall @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_tointegral @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_tointegralx @ linux-x86_64 +test.test_decimal.CIBMTestCases.test_xor @ linux-x86_64 +test.test_decimal.CImplicitConstructionTest.test_implicit_from_Decimal @ linux-x86_64 +test.test_decimal.CImplicitConstructionTest.test_implicit_from_None @ linux-x86_64 +test.test_decimal.CImplicitConstructionTest.test_implicit_from_float @ linux-x86_64 +test.test_decimal.CImplicitConstructionTest.test_implicit_from_int @ linux-x86_64 +test.test_decimal.CImplicitConstructionTest.test_implicit_from_string @ linux-x86_64 +test.test_decimal.CImplicitConstructionTest.test_rop @ linux-x86_64 +test.test_decimal.CPythonAPItests.test_abc @ linux-x86_64 +test.test_decimal.CPythonAPItests.test_complex @ linux-x86_64 +test.test_decimal.CPythonAPItests.test_create_decimal_from_float @ linux-x86_64 +test.test_decimal.CPythonAPItests.test_exception_hierarchy @ linux-x86_64 +test.test_decimal.CPythonAPItests.test_from_float @ linux-x86_64 +test.test_decimal.CPythonAPItests.test_int @ linux-x86_64 +test.test_decimal.CPythonAPItests.test_named_parameters @ linux-x86_64 +test.test_decimal.CPythonAPItests.test_pickle @ linux-x86_64 +test.test_decimal.CPythonAPItests.test_quantize @ linux-x86_64 +test.test_decimal.CPythonAPItests.test_trunc @ linux-x86_64 +test.test_decimal.CSpecialContexts.test_context_templates @ linux-x86_64 +test.test_decimal.CSpecialContexts.test_default_context @ linux-x86_64 +test.test_decimal.CThreadingTest.test_threading @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_as_integer_ratio @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_as_nonzero @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_as_tuple @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_comparison_operators @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_conversions_from_int @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_copy_and_deepcopy_methods @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_decimal_complex_comparison @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_decimal_float_comparison @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_decimal_fraction_comparison @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_eval_round_trip @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_hash_method @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_hash_method_nan @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_implicit_context @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_min_and_max_methods @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_nan_to_float @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_none_args @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_snan_to_float @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_subclassing @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_tonum_methods @ linux-x86_64 +test.test_decimal.CUsabilityTest.test_tostring_methods @ linux-x86_64 +test.test_decimal.CWhitebox.test_bignum @ linux-x86_64 +test.test_decimal.CWhitebox.test_c_context_errors @ linux-x86_64 +test.test_decimal.CWhitebox.test_c_context_repr @ linux-x86_64 +test.test_decimal.CWhitebox.test_c_disallow_instantiation @ linux-x86_64 +test.test_decimal.CWhitebox.test_c_format @ linux-x86_64 +test.test_decimal.CWhitebox.test_c_funcs @ linux-x86_64 +test.test_decimal.CWhitebox.test_c_immutable_types @ linux-x86_64 +test.test_decimal.CWhitebox.test_c_input_restriction @ linux-x86_64 +test.test_decimal.CWhitebox.test_c_integral @ linux-x86_64 +test.test_decimal.CWhitebox.test_c_round @ linux-x86_64 +test.test_decimal.CWhitebox.test_c_signaldict_segfault @ linux-x86_64 +test.test_decimal.CWhitebox.test_c_valid_context @ linux-x86_64 +test.test_decimal.CWhitebox.test_exact_conversion @ linux-x86_64 +test.test_decimal.CWhitebox.test_format_fallback_capitals @ linux-x86_64 +test.test_decimal.CWhitebox.test_format_fallback_rounding @ linux-x86_64 +test.test_decimal.CWhitebox.test_from_tuple @ linux-x86_64 +test.test_decimal.CWhitebox.test_internal_use_of_overridden_methods @ linux-x86_64 +test.test_decimal.CWhitebox.test_invalid_construction @ linux-x86_64 +test.test_decimal.CWhitebox.test_invalid_override @ linux-x86_64 +test.test_decimal.CWhitebox.test_sizeof @ linux-x86_64 +test.test_decimal.CWhitebox.test_va_args_exceptions @ linux-x86_64 +test.test_decimal.CheckAttributes.test_context_attributes @ linux-x86_64 +test.test_decimal.CheckAttributes.test_decimal_attributes @ linux-x86_64 +test.test_decimal.CheckAttributes.test_module_attributes @ linux-x86_64 test.test_decimal.PyArithmeticOperatorsTest.test_addition @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_decimal.PyArithmeticOperatorsTest.test_copy_sign @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_decimal.PyArithmeticOperatorsTest.test_division @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github @@ -372,3 +698,4 @@ test.test_decimal.PyWhitebox.test_py_decimal_id @ darwin-arm64,linux-aarch64,lin test.test_decimal.PyWhitebox.test_py_exact_power @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_decimal.PyWhitebox.test_py_immutability_operations @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_decimal.PyWhitebox.test_py_rescale @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_decimal.SignatureTest.test_inspect_types @ linux-x86_64 From ea1e548cab3d66a103ae0dc9e989ee8b7a1bf22a Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Wed, 14 Oct 2026 18:01:11 +0200 Subject: [PATCH 22/31] Add tests for decimal module --- .../src/tests/test_decimal.py | 2493 +++++++++++++++++ 1 file changed, 2493 insertions(+) create mode 100644 graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py b/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py new file mode 100644 index 0000000000..3fd5dc30fc --- /dev/null +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py @@ -0,0 +1,2493 @@ +# 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 _decimal as decimal +import _pydecimal as reference +import asyncio +import contextvars +import copy +import decimal as public_decimal +import gc +import inspect +import math +import numbers +import operator +import pickle +import random +import sys +import threading +import unittest +import warnings +import weakref +from collections.abc import MutableMapping +from fractions import Fraction + + +# Both implementations expose rounding modes as strings. +ROUNDINGS = ("ROUND_DOWN", "ROUND_UP", "ROUND_CEILING", "ROUND_FLOOR", + "ROUND_HALF_UP", "ROUND_HALF_DOWN", "ROUND_HALF_EVEN", "ROUND_05UP") +SIGNALS = (decimal.InvalidOperation, decimal.FloatOperation, decimal.DivisionByZero, + decimal.Overflow, decimal.Underflow, decimal.Subnormal, decimal.Inexact, + decimal.Rounded, decimal.Clamped) + + +def enabled_flags(context): + return sorted(signal.__name__ for signal, enabled in context.flags.items() if enabled) + + + +class DecimalInitializationTests(unittest.TestCase): + def test_exception_hierarchy(self): + bases = { + "DecimalException": (ArithmeticError,), + "InvalidOperation": (decimal.DecimalException,), + "FloatOperation": (decimal.DecimalException, TypeError), + "DivisionByZero": (decimal.DecimalException, ZeroDivisionError), + "Inexact": (decimal.DecimalException,), + "Rounded": (decimal.DecimalException,), + "Subnormal": (decimal.DecimalException,), + "Overflow": (decimal.Inexact, decimal.Rounded), + "Underflow": (decimal.Inexact, decimal.Rounded, decimal.Subnormal), + "Clamped": (decimal.DecimalException,), + "ConversionSyntax": (decimal.InvalidOperation,), + "DivisionImpossible": (decimal.InvalidOperation,), + "DivisionUndefined": (decimal.InvalidOperation, ZeroDivisionError), + "InvalidContext": (decimal.InvalidOperation,), + } + for name, expected in bases.items(): + with self.subTest(name=name): + cls = getattr(decimal, name) + self.assertEqual(cls.__bases__, expected) + self.assertEqual(cls.__module__, "decimal") + exc = cls("message", 42) + self.assertEqual(exc.args, ("message", 42)) + self.assertEqual(str(exc), "('message', 42)") + self.assertEqual(repr(exc), f"{name}('message', 42)") + try: + raise exc + except ArithmeticError as caught: + self.assertIs(caught, exc) + self.assertTrue(issubclass(decimal.FloatOperation, TypeError)) + self.assertTrue(issubclass(decimal.DivisionUndefined, ZeroDivisionError)) + self.assertTrue(issubclass(decimal.Underflow, decimal.Subnormal)) + + def test_public_types_and_builtins(self): + self.assertEqual(decimal.Decimal.__module__, "decimal") + self.assertEqual(decimal.Context.__module__, "decimal") + self.assertIs(decimal.Decimal.exp.__objclass__, decimal.Decimal) + self.assertIs(decimal.Context.prec.__objclass__, decimal.Context) + self.assertIn("__new__", vars(decimal.Decimal)) + self.assertIn("__new__", vars(decimal.Context)) + + def test_decimal_tuple(self): + cls = decimal.DecimalTuple + self.assertEqual(cls.__bases__, (tuple,)) + self.assertEqual(cls.__module__, "decimal") + self.assertEqual(cls._fields, ("sign", "digits", "exponent")) + self.assertEqual(cls.__match_args__, cls._fields) + self.assertEqual(cls._field_defaults, {}) + self.assertIs(cls._make.__self__, cls) + self.assertEqual(cls.__slots__, ()) + self.assertEqual(cls.__doc__, "DecimalTuple(sign, digits, exponent)") + for name in ("__getitem__", "__len__", "__hash__", "__iter__"): + self.assertIs(getattr(cls, name), getattr(tuple, name)) + value = cls(0, (1, 2), -1) + self.assertEqual(value, (0, (1, 2), -1)) + self.assertEqual(cls(sign=0, digits=(1, 2), exponent=-1), value) + self.assertEqual((value.sign, value.digits, value.exponent), tuple(value)) + self.assertEqual(repr(value), "DecimalTuple(sign=0, digits=(1, 2), exponent=-1)") + self.assertEqual(hash(value), hash(tuple(value))) + self.assertFalse(hasattr(value, "__dict__")) + self.assertIs(type(cls._make(iter(value))), cls) + self.assertEqual(cls._make(value), value) + self.assertEqual(value._asdict(), {"sign": 0, "digits": (1, 2), "exponent": -1}) + self.assertEqual(value.__getnewargs__(), tuple(value)) + self.assertEqual(value._replace(sign=1), cls(1, (1, 2), -1)) + self.assertEqual(value.__replace__(exponent=2), cls(0, (1, 2), 2)) + with self.assertRaises(AttributeError): + value.sign = 1 + with self.assertRaises(TypeError): + cls._make((0, (1, 2))) + with self.assertRaises(TypeError): + value._replace(unknown=1) + with self.assertRaises(TypeError): + cls(0, (1, 2)) + + def test_decimal_tuple_subclass_and_copy(self): + class TupleSubclass(decimal.DecimalTuple): + __slots__ = () + + value = TupleSubclass(1, (4, 2), -2) + self.assertIs(type(TupleSubclass._make(value)), TupleSubclass) + self.assertIs(type(value._replace(sign=0)), TupleSubclass) + self.assertEqual(repr(value), "TupleSubclass(sign=1, digits=(4, 2), exponent=-2)") + for copied in (copy.copy(value), copy.deepcopy(value)): + self.assertIs(type(copied), TupleSubclass) + self.assertEqual(copied, value) + + def test_decimal_tuple_pickle(self): + value = decimal.DecimalTuple(0, (1, 2), -1) + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): + restored = pickle.loads(pickle.dumps(value, protocol)) + self.assertIs(type(restored), decimal.DecimalTuple) + self.assertEqual(restored, value) + + def test_signal_dict_inheritance(self): + # These are internal types retained by the module state. + mixin, = (cls for cls in object.__subclasses__() if cls.__name__ == "SignalDictMixin" and cls.__module__ == "decimal") + signal_dict, = mixin.__subclasses__() + self.assertEqual(signal_dict.__bases__, (mixin, MutableMapping)) + self.assertIs(signal_dict.items, MutableMapping.items) + self.assertIs(signal_dict.update, MutableMapping.update) + self.assertIs(signal_dict.copy, mixin.copy) + self.assertIsNone(signal_dict.__hash__) + for name in ("__getitem__", "__setitem__", "__delitem__", "__len__", "__iter__", "__repr__", "__eq__"): + self.assertIs(getattr(signal_dict, name), getattr(mixin, name)) + + +class DecimalConstructionTests(unittest.TestCase): + def test_module_constants(self): + for name in ROUNDINGS: + self.assertEqual(getattr(decimal, name), name) + self.assertEqual(decimal.MAX_PREC, 999999999999999999) + self.assertEqual(decimal.MAX_EMAX, decimal.MAX_PREC) + self.assertEqual(decimal.MIN_EMIN, -decimal.MAX_EMAX) + self.assertEqual(decimal.MIN_ETINY, -1999999999999999997) + self.assertIs(decimal.HAVE_THREADS, True) + self.assertIs(decimal.HAVE_CONTEXTVAR, True) + self.assertEqual(decimal.__version__, "1.70") + self.assertIsInstance(decimal.__libmpdec_version__, str) + + def test_default_and_integer(self): + self.assertEqual(repr(decimal.Decimal()), "Decimal('0')") + for value in (0, 42, -42, True, False, -(2**31), 2**31 - 1, 2**31, -(2**31) - 1, + 2**63 - 1, 2**63, -2**63, -2**63 + 1, -(10**100), 10**100): + with self.subTest(value=value): + self.assertEqual(repr(decimal.Decimal(value)), f"Decimal('{int(value)}')") + + def test_strings(self): + cases = { + "00012.3400": "12.3400", + "-0": "-0", + "-0.000": "-0.000", + ".000001": "0.000001", + ".0000001": "1E-7", + "1.2300e-7": "1.2300E-7", + "1.2300e2": "123.00", + "1.2300e5": "1.2300E+5", + "0e-7": "0E-7", + "-0e+10": "-0E+10", + "1e3000000000": "1E+3000000000", + "1e999999999999999999": "1E+999999999999999999", + "1e-1999999999999999997": "1E-1999999999999999997", + "1e00000000000000000000000000000001": "1E+1", + " +12.300 ": "12.300", + "\x1c12\x1c": "12", + "\u00a0\u0661\u0662.\u0663\uff10\u2002": "12.30", + "\U0001d7d9.\U0001d7da": "1.2", + "_1_._2_e_+_3_": "1.2E+3", + "iNf": "Infinity", + "-infinity": "-Infinity", + "+NaN": "NaN", + "-nan00120": "-NaN120", + "NaN000": "NaN", + "-sNaN0120": "-sNaN120", + "sNaN0": "sNaN", + } + for value, expected in cases.items(): + with self.subTest(value=value): + self.assertEqual(repr(decimal.Decimal(value)), f"Decimal('{expected}')") + + def test_exact_float_conversion(self): + cases = { + 0.1: "0.1000000000000000055511151231257827021181583404541015625", + 1.5: "1.5", + -1.5: "-1.5", + float("inf"): "Infinity", + float("-inf"): "-Infinity", + } + for value, expected in cases.items(): + self.assertEqual(repr(decimal.Decimal(value)), f"Decimal('{expected}')") + self.assertEqual(repr(decimal.Decimal(0.0)), "Decimal('0')") + self.assertEqual(repr(decimal.Decimal(-0.0)), "Decimal('-0')") + self.assertEqual(repr(decimal.Decimal(float("nan"))), "Decimal('NaN')") + + def test_exact_float_coefficient_boundaries(self): + cases = [ + ("0x1p-3", "0.125"), + ("0x1p-27", "7.450580596923828125E-9"), + ("0x1p-28", "3.7252902984619140625E-9"), + ("0x1p+62", "4611686018427387904"), + ("0x1.fffffffffffffp+62", "9223372036854774784"), + ("0x1p+63", "9223372036854775808"), + ("0x1.8000000000001p+0", "1.5000000000000002220446049250313080847263336181640625"), + ] + for literal, expected in cases: + for sign in ("", "-"): + with self.subTest(value=sign + literal): + self.assertEqual(repr(decimal.Decimal(float.fromhex(sign + literal))), + f"Decimal('{sign}{expected}')") + + def test_triples(self): + cases = [ + ((0, (1, 2, 3, 0), -2), "12.30"), + ([1, [0, 0, 0], -3], "-0.000"), + ((0, (), 2), "0E+2"), + ((0, (0, 0, 1, 2), "n"), "NaN12"), + ((1, (), "N"), "-sNaN"), + ((1, (1, 2), "F"), "-Infinity"), + (decimal.DecimalTuple(0, (1, 2, 0), -1), "12.0"), + ((False, (True, False), 0), "10"), + ] + for value, expected in cases: + with self.subTest(value=value): + self.assertEqual(repr(decimal.Decimal(value)), f"Decimal('{expected}')") + + def test_decimal_copy_and_subclasses(self): + class DecimalSubclass(decimal.Decimal): + pass + + value = decimal.Decimal("-1.2000") + self.assertIs(decimal.Decimal(value), value) + sub = DecimalSubclass(value) + self.assertIs(type(sub), DecimalSubclass) + self.assertEqual(repr(sub), "Decimal('-1.2000')") + copied = decimal.Decimal(sub) + self.assertIs(type(copied), decimal.Decimal) + self.assertIsNot(copied, sub) + self.assertEqual(repr(copied), repr(value)) + self.assertEqual(repr(decimal.Decimal(value="1.20", context=None)), "Decimal('1.20')") + + def test_primitive_subclasses_do_not_coerce(self): + class IntSubclass(int): + def __int__(self): + raise AssertionError + + class FloatSubclass(float): + def __float__(self): + raise AssertionError + + class StringSubclass(str): + def __str__(self): + raise AssertionError + + self.assertEqual(repr(decimal.Decimal(IntSubclass(42))), "Decimal('42')") + self.assertEqual(repr(decimal.Decimal(FloatSubclass(1.5))), "Decimal('1.5')") + self.assertEqual(repr(decimal.Decimal(StringSubclass("1.20"))), "Decimal('1.20')") + + def test_invalid_strings(self): + for value in ("", " ", ".", "1.2.3", "1e", "1e+", "1 e2", "--1", "1\x00", "NaN1.2", "Infinity1", "\u00b21"): + with self.subTest(value=value): + with self.assertRaises(decimal.InvalidOperation) as caught: + decimal.Decimal(value) + self.assertEqual(caught.exception.args, ([decimal.ConversionSyntax],)) + for value in ("1e1000000000000000000", "10e999999999999999999", "1e-1999999999999999998", + "0e-1999999999999999998", "0e999999999999999999999999", "1e-9223372036854775808"): + with self.subTest(value=value): + with self.assertRaises(decimal.InvalidOperation) as caught: + decimal.Decimal(value) + self.assertEqual(caught.exception.args, ([decimal.InvalidOperation],)) + + def test_invalid_triples(self): + for value in ((0, (1,)), (0, (1,), 0, 0), (2, (1,), 0), (0.0, (1,), 0), + (0, (1,), 0.0), (0, (1,), "x"), (0, (10,), 0), (0, (1.0,), 0), + (0, "12", 0), (0, iter([1, 2]), 0)): + with self.subTest(value=value): + with self.assertRaises(ValueError): + decimal.Decimal(value) + for value in ((2**100, (1,), 0), (0, (2**100,), 0), (0, (1,), 2**100)): + with self.assertRaises(OverflowError): + decimal.Decimal(value) + + def test_invalid_types_and_arguments(self): + class IndexOnly: + def __index__(self): + return 1 + + for value in (None, b"1", object(), IndexOnly()): + with self.subTest(value=type(value)): + with self.assertRaises(TypeError): + decimal.Decimal(value) + with self.assertRaises(TypeError): + decimal.Decimal("1", object()) + with self.assertRaises(TypeError): + decimal.Decimal("1", None, None) + with self.assertRaises(TypeError): + decimal.Decimal(unknown=1) + + +class DecimalStringCreationTests(unittest.TestCase): + def test_mixed_constructor_inputs(self): + class StringSubclass(str): + pass + + for value in ("1.20", 42, 10**30, True, 1.5, (0, (1, 2), -1), b"1", None, object(), + "1.20", StringSubclass("1.20"), "\u0661.\u0662", "".join(("1", ".20"))): + with self.subTest(value=value): + if value is None or isinstance(value, bytes) or type(value) is object: + with self.assertRaises(TypeError): + decimal.Decimal(value) + else: + self.assertEqual(decimal.Decimal(value).as_tuple(), reference.Decimal(value).as_tuple()) + + def test_compact_strings_and_fallback(self): + values = ( + "0", "-0", "+0", "-0.000", "00012.3400", ".1", "1.", "-.0", + "1e0", "1E+2", "-1.2300e-7", "0e-999999999999999", "1e9999999999999999", + "99999999999999999", "999999999999999999", "9999999999999999999", + "9223372036854775807", "9223372036854775808", + "0." + "0" * 15, "0." + "0" * 16, "0." + "0" * 17, + "1e999999999999999999", "1e-1999999999999999997", + " +12.300 ", "_1_._2_e_+_3_", "\u0661.\u0662", "\U0001d7d9.\U0001d7da", + "Inf", "-Infinity", "NaN123", "-sNaN123", + ) + + class DecimalSubclass(decimal.Decimal): + pass + + class StringSubclass(str): + def __str__(self): + raise AssertionError("string subclasses must not be coerced") + + with decimal.localcontext(prec=1, Emin=-1, Emax=1) as context: + context.clear_flags() + for value in values: + expected = reference.Decimal(value).as_tuple() + for cls in (decimal.Decimal, DecimalSubclass): + for text in (value, StringSubclass(value)): + with self.subTest(value=value, cls=cls, text_type=type(text)): + result = cls(text) + self.assertIs(type(result), cls) + self.assertEqual(result.as_tuple(), expected) + self.assertFalse(any(context.flags.values())) + + def test_random_short_strings(self): + rng = random.Random(80152) + for _ in range(300): + digits = "".join(str(rng.randrange(10)) for _ in range(rng.randrange(1, 20))) + point = rng.randrange(len(digits) + 1) + value = rng.choice(("", "+", "-")) + digits[:point] + "." + digits[point:] + if rng.randrange(2): + value += rng.choice(("e", "E")) + rng.choice(("", "+", "-")) + str(rng.randrange(10**5)) + with self.subTest(value=value): + self.assertEqual(decimal.Decimal(value).as_tuple(), reference.Decimal(value).as_tuple()) + + def test_invalid_short_strings(self): + for value in ("", "+", "-", ".", "+.", "1..0", "1e", "1e+", "1e-", "1e+-2", + "1e2.0", "1e2e3", "1e 2", "1x", "1\x00", "1+", "1-e2", "1.2.3"): + with self.subTest(value=value): + with decimal.localcontext() as context: + context.clear_traps() + context.clear_flags() + self.assertTrue(decimal.Decimal(value).is_qnan()) + self.assertTrue(context.flags[decimal.InvalidOperation]) + self.assertEqual(sum(context.flags.values()), 1) + + +class DecimalContextTests(unittest.TestCase): + def setUp(self): + self.original = decimal.getcontext() + decimal.setcontext(decimal.Context()) + + def tearDown(self): + decimal.setcontext(self.original) + + def assertSameSettings(self, left, right): + for name in ('prec', 'rounding', 'Emin', 'Emax', 'capitals', 'clamp', 'flags', 'traps'): + self.assertEqual(getattr(left, name), getattr(right, name), name) + + def test_templates(self): + default = decimal.DefaultContext + self.assertEqual((default.prec, default.rounding, default.Emin, default.Emax, + default.capitals, default.clamp), + (28, decimal.ROUND_HALF_EVEN, -999999, 999999, 1, 0)) + self.assertEqual([s for s in SIGNALS if default.traps[s]], + [decimal.InvalidOperation, decimal.DivisionByZero, decimal.Overflow]) + self.assertFalse(any(default.flags.values())) + basic = decimal.BasicContext + self.assertEqual((basic.prec, basic.rounding), (9, decimal.ROUND_HALF_UP)) + self.assertEqual([s for s in SIGNALS if basic.traps[s]], + [decimal.InvalidOperation, decimal.DivisionByZero, decimal.Overflow, + decimal.Underflow, decimal.Clamped]) + extended = decimal.ExtendedContext + self.assertEqual((extended.prec, extended.rounding), (9, decimal.ROUND_HALF_EVEN)) + self.assertFalse(any(extended.traps.values())) + + def test_constructor_and_reinitialization(self): + ctx = decimal.Context(17, decimal.ROUND_DOWN, -50, 80, 0, 1, + [decimal.Inexact], [decimal.FloatOperation]) + self.assertEqual((ctx.prec, ctx.rounding, ctx.Emin, ctx.Emax, ctx.capitals, ctx.clamp), + (17, decimal.ROUND_DOWN, -50, 80, 0, 1)) + self.assertEqual([s for s in SIGNALS if ctx.flags[s]], [decimal.Inexact]) + self.assertEqual([s for s in SIGNALS if ctx.traps[s]], [decimal.FloatOperation]) + ctx.__init__(prec=None, rounding=None, Emin=None, Emax=None, capitals=None, + clamp=None, flags=None, traps=None) + self.assertEqual(ctx.prec, 17) + self.assertTrue(ctx.flags[decimal.Inexact]) + ctx.__init__(prec=12) + self.assertEqual(ctx.prec, 12) + self.assertEqual(ctx.capitals, 0) + self.assertTrue(ctx.traps[decimal.FloatOperation]) + with self.assertRaises(TypeError): + decimal.Context(1, 2, 3, 4, 5, 6, 7, 8, 9) + with self.assertRaises(TypeError): + decimal.Context(unknown=1) + with self.assertRaises(TypeError): + decimal.Context(12, prec=15) + + def test_default_context_inheritance(self): + template = decimal.DefaultContext + saved = template.copy() + try: + template.prec = 42 + template.capitals = 0 + template.flags[decimal.Rounded] = True + template.traps[decimal.FloatOperation] = True + ctx = decimal.Context() + self.assertEqual(ctx.prec, 42) + self.assertEqual(ctx.capitals, 1) + self.assertTrue(ctx.flags[decimal.Rounded]) + self.assertTrue(ctx.traps[decimal.FloatOperation]) + self.assertIsNot(ctx.flags, template.flags) + fresh = contextvars.Context().run(decimal.getcontext) + self.assertEqual(fresh.prec, 42) + self.assertEqual(fresh.capitals, 0) + self.assertFalse(any(fresh.flags.values())) + finally: + template.__init__(*saved.__reduce__()[1]) + + def test_attribute_ranges(self): + ctx = decimal.Context() + ranges = {'prec': (1, decimal.MAX_PREC), 'Emin': (decimal.MIN_EMIN, 0), + 'Emax': (0, decimal.MAX_EMAX), 'capitals': (0, 1), 'clamp': (0, 1)} + for name, (low, high) in ranges.items(): + for value in (low, high, True, False): + if name == 'prec' and not value: + continue + if name == 'Emin' and value is True: + continue + setattr(ctx, name, value) + self.assertEqual(getattr(ctx, name), value) + old = getattr(ctx, name) + for value in (low - 1, high + 1): + with self.assertRaises(ValueError): + setattr(ctx, name, value) + self.assertEqual(getattr(ctx, name), old) + for value in (None, '1', 1.5, object()): + with self.assertRaises(TypeError): + setattr(ctx, name, value) + for value in (2 ** 100, -(2 ** 100)): + with self.assertRaises(OverflowError): + setattr(ctx, name, value) + with self.assertRaises(ValueError): + ctx.clamp = 2 ** 32 + ctx.prec = decimal.MAX_PREC + ctx.Emin = decimal.MIN_EMIN + ctx.Emax = decimal.MAX_EMAX + self.assertEqual(ctx.Etiny(), decimal.MIN_ETINY) + self.assertEqual(ctx.Etop(), 1) + + def test_index_conversion(self): + class Index: + def __index__(self): + return 23 + # PyLong_AsSsize_t requires an actual int, even when __index__ is available. + with self.assertRaises(TypeError): + decimal.Context(prec=Index()) + ctx = decimal.Context() + with self.assertRaises(TypeError): + ctx.prec = Index() + + def test_rounding(self): + ctx = decimal.Context() + for rounding in ROUNDINGS: + ctx.rounding = ''.join([rounding[:4], rounding[4:]]) + self.assertEqual(ctx.rounding, rounding) + for value in (None, 1, object(), 'ROUND_UNKNOWN'): + with self.assertRaises(TypeError): + ctx.rounding = value + + def test_repr_and_exponent_helpers(self): + ctx = decimal.Context(prec=5, Emin=-10, Emax=15, flags=[decimal.Rounded, decimal.Clamped], traps=[]) + self.assertEqual(ctx.Etiny(), -14) + self.assertEqual(ctx.Etop(), 11) + self.assertEqual(repr(ctx.radix()), "Decimal('10')") + self.assertEqual(repr(ctx), 'Context(prec=5, rounding=ROUND_HALF_EVEN, Emin=-10, Emax=15, ' + 'capitals=1, clamp=0, flags=[Clamped, Rounded], traps=[])') + + def test_attribute_slots(self): + ctx = decimal.Context() + for name in ('prec', 'Emin', 'Emax', 'rounding', 'capitals', 'clamp', 'flags', 'traps', 'unknown'): + with self.assertRaises(AttributeError): + delattr(ctx, name) + with self.assertRaises(AttributeError): + ctx.unknown = 1 + with self.assertRaises(AttributeError): + getattr(ctx, 'unknown') + with self.assertRaises(TypeError): + ctx.__getattribute__(1) + with self.assertRaises(TypeError): + ctx.__setattr__(1, 2) + class Subclass(decimal.Context): + pass + sub = Subclass() + sub.extra = 12 + self.assertEqual(sub.extra, 12) + with self.assertRaises(AttributeError): + del sub.extra + + def test_copy_and_pickle(self): + ctx = decimal.Context(prec=13, rounding=decimal.ROUND_FLOOR, Emin=-70, Emax=30, + capitals=0, clamp=1, flags=[decimal.Inexact], traps=[decimal.Underflow]) + for cloned in (ctx.copy(), ctx.__copy__(), copy.copy(ctx), copy.deepcopy(ctx)): + self.assertIsNot(cloned, ctx) + self.assertIs(type(cloned), decimal.Context) + self.assertSameSettings(cloned, ctx) + self.assertIsNot(cloned.flags, ctx.flags) + self.assertIsNot(cloned.traps, ctx.traps) + cloned.clear_flags() + self.assertTrue(ctx.flags[decimal.Inexact]) + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): + cloned = pickle.loads(pickle.dumps(ctx, protocol)) + self.assertSameSettings(cloned, ctx) + class Subclass(decimal.Context): + pass + sub = Subclass(prec=41) + self.assertIs(type(sub.copy()), decimal.Context) + self.assertIs(sub.__reduce__()[0], Subclass) + + def test_get_and_set_context(self): + ctx = decimal.Context(prec=16) + self.assertIsNone(decimal.setcontext(ctx)) + self.assertIs(decimal.getcontext(), ctx) + for value in (None, 1, object()): + with self.assertRaises(TypeError): + decimal.setcontext(value) + for template in (decimal.DefaultContext, decimal.BasicContext, decimal.ExtendedContext): + decimal.setcontext(template) + current = decimal.getcontext() + self.assertIsNot(current, template) + self.assertSameSettings(current, template) + + def test_localcontext(self): + outer = decimal.getcontext() + outer.prec = 37 + outer.flags[decimal.Inexact] = True + with decimal.localcontext() as local: + self.assertIs(decimal.getcontext(), local) + self.assertIsNot(local, outer) + self.assertSameSettings(local, outer) + local.prec = 12 + local.clear_flags() + with decimal.localcontext(prec=19, rounding=decimal.ROUND_UP) as inner: + self.assertEqual(inner.prec, 19) + self.assertEqual(inner.rounding, decimal.ROUND_UP) + self.assertIs(decimal.getcontext(), local) + self.assertIs(decimal.getcontext(), outer) + self.assertEqual(outer.prec, 37) + self.assertTrue(outer.flags[decimal.Inexact]) + source = decimal.Context(prec=21) + with decimal.localcontext(source, prec=14, capitals=0, flags=[], traps=[]) as local: + self.assertEqual(local.prec, 14) + self.assertEqual(local.capitals, 0) + self.assertFalse(any(local.traps.values())) + self.assertEqual(source.prec, 21) + for kw in ({'unknown': 1}, {'prec': 0}, {'ctx': 1}): + with self.assertRaises((TypeError, ValueError)): + decimal.localcontext(**kw) + self.assertIs(decimal.getcontext(), outer) + + def test_localcontext_restoration_after_exception(self): + outer = decimal.getcontext() + with self.assertRaisesRegex(RuntimeError, 'test'): + with decimal.localcontext(): + raise RuntimeError('test') + self.assertIs(decimal.getcontext(), outer) + manager = decimal.localcontext() + another = decimal.Context() + decimal.setcontext(another) + manager.__enter__() + self.assertIsNone(manager.__exit__()) + # CPython captures the outer context at manager creation, not entry. + self.assertIs(decimal.getcontext(), outer) + + def test_contextvar_isolation(self): + outer = decimal.getcontext() + def run(): + inner = decimal.getcontext() + self.assertIsNot(inner, outer) + inner.prec = 47 + return inner + inner = contextvars.Context().run(run) + self.assertEqual(inner.prec, 47) + self.assertIs(decimal.getcontext(), outer) + results = [] + def thread_run(): + results.append(decimal.getcontext()) + thread = threading.Thread(target=thread_run) + thread.start() + thread.join() + self.assertIsNot(results[0], outer) + + def test_async_context_switching(self): + outer = decimal.getcontext() + async def run(precision): + with decimal.localcontext(prec=precision) as local: + await asyncio.sleep(0) + self.assertIs(decimal.getcontext(), local) + return local.prec + async def main(): + return await asyncio.gather(run(17), run(29)) + self.assertEqual(asyncio.run(main()), [17, 29]) + self.assertIs(decimal.getcontext(), outer) + + def test_constructor_signals(self): + ctx = decimal.Context(traps=[]) + value = decimal.Decimal('bad', context=ctx) + self.assertEqual(repr(value), "Decimal('NaN')") + self.assertTrue(ctx.flags[decimal.InvalidOperation]) + self.assertFalse(decimal.getcontext().flags[decimal.InvalidOperation]) + ctx.traps[decimal.InvalidOperation] = True + ctx.clear_flags() + with self.assertRaises(decimal.InvalidOperation) as caught: + decimal.Decimal('bad', context=ctx) + self.assertEqual(caught.exception.args, ([decimal.ConversionSyntax],)) + self.assertTrue(ctx.flags[decimal.InvalidOperation]) + ctx.clear_flags() + with self.assertRaises(decimal.InvalidOperation) as caught: + decimal.Decimal('1e99999999999999999999', context=ctx) + self.assertEqual(caught.exception.args, ([decimal.InvalidOperation],)) + self.assertTrue(ctx.flags[decimal.InvalidOperation]) + ctx.clear_flags() + decimal.Decimal('sNaN123', context=ctx) + self.assertFalse(any(ctx.flags.values())) + + def test_float_operation(self): + class FloatSubclass(float): + pass + ctx = decimal.Context() + for value in (0.1, float('inf'), float('nan'), FloatSubclass(1.5)): + ctx.clear_flags() + decimal.Decimal(value, context=ctx) + self.assertTrue(ctx.flags[decimal.FloatOperation]) + ctx.clear_flags() + ctx.traps[decimal.FloatOperation] = True + with self.assertRaises(decimal.FloatOperation) as caught: + decimal.Decimal(value, context=ctx) + self.assertEqual(caught.exception.args, ([decimal.FloatOperation],)) + self.assertTrue(ctx.flags[decimal.FloatOperation]) + ctx.traps[decimal.FloatOperation] = False + decimal.Decimal(0.5) + self.assertTrue(decimal.getcontext().flags[decimal.FloatOperation]) + + def test_repr_capitals(self): + ctx = decimal.getcontext() + ctx.capitals = 0 + self.assertEqual(repr(decimal.Decimal('1E+10')), "Decimal('1e+10')") + ctx.capitals = 1 + self.assertEqual(repr(decimal.Decimal('1E+10')), "Decimal('1E+10')") + + +class DecimalSignalDictTests(unittest.TestCase): + def setUp(self): + self.ctx = decimal.Context(flags=[], traps=[]) + + def test_mapping(self): + flags = self.ctx.flags + self.assertIsInstance(flags, MutableMapping) + self.assertEqual(len(flags), 9) + self.assertEqual(tuple(flags), SIGNALS) + self.assertEqual(flags.copy(), dict.fromkeys(SIGNALS, False)) + self.assertFalse(any(flags.values())) + self.assertIs(flags, self.ctx.flags) + flags[decimal.Inexact] = 17 + self.assertIs(flags[decimal.Inexact], True) + flags[decimal.Inexact] = None + self.assertIs(flags[decimal.Inexact], False) + flags.update({decimal.Rounded: True, decimal.Clamped: True}) + self.assertTrue(flags[decimal.Rounded]) + self.assertTrue(flags[decimal.Clamped]) + self.assertEqual(flags, flags.copy()) + self.assertFalse(flags != flags.copy()) + self.assertNotEqual(flags, self.ctx.traps) + self.assertNotEqual(flags, {decimal.Inexact: True}) + with self.assertRaises(TypeError): + hash(flags) + with self.assertRaises(TypeError): + flags < flags.copy() + self.ctx.clear_flags() + self.assertFalse(any(flags.values())) + traps = self.ctx.traps + traps[decimal.Inexact] = True + self.ctx.clear_traps() + self.assertFalse(any(traps.values())) + + def test_repr(self): + flags = self.ctx.flags + expected = '{' + ', '.join(f":False" for s in SIGNALS) + '}' + self.assertEqual(repr(flags), expected) + + def test_keys_and_deletion(self): + class InvalidSubclass(decimal.InvalidOperation): + pass + for key in (None, 1, [], decimal.ConversionSyntax, InvalidSubclass): + with self.assertRaises(KeyError): + self.ctx.flags[key] + with self.assertRaises(KeyError): + self.ctx.flags[key] = True + for key in (decimal.Inexact, object()): + with self.assertRaisesRegex(ValueError, 'signal keys cannot be deleted'): + del self.ctx.flags[key] + + def test_assignment_preserves_view(self): + flags = self.ctx.flags + traps = self.ctx.traps + values = dict.fromkeys(SIGNALS, False) + values[decimal.Inexact] = [1] + self.ctx.flags = values + self.assertIs(self.ctx.flags, flags) + self.assertTrue(flags[decimal.Inexact]) + self.ctx.traps = flags + self.assertIs(self.ctx.traps, traps) + self.assertTrue(traps[decimal.Inexact]) + flags[decimal.Inexact] = False + self.assertTrue(traps[decimal.Inexact]) + for invalid in ({}, {decimal.Inexact: True}, dict(values, extra=False)): + with self.assertRaises(KeyError): + self.ctx.flags = invalid + for invalid in ([decimal.Inexact], (), None, 1): + with self.assertRaises(TypeError): + self.ctx.flags = invalid + with self.assertRaises(KeyError): + decimal.Context(flags=[decimal.ConversionSyntax]) + with self.assertRaises(TypeError): + decimal.Context(flags=(decimal.Inexact,)) + + def test_truth_errors_preserve_flags(self): + class BadBool: + def __bool__(self): + raise RuntimeError('bool failed') + flags = self.ctx.flags + before = flags.copy() + with self.assertRaisesRegex(RuntimeError, 'bool failed'): + flags[decimal.Inexact] = BadBool() + self.assertEqual(flags, before) + values = before.copy() + values[decimal.Inexact] = BadBool() + with self.assertRaises(RuntimeError): + self.ctx.flags = values + self.assertEqual(flags, before) + with self.assertRaises(RuntimeError): + flags == values + + def test_ownership(self): + flags = self.ctx.flags + del self.ctx + flags[decimal.Inexact] = True + self.assertTrue(flags[decimal.Inexact]) + self.assertEqual(len(flags), 9) + + def test_unbound_signal_dict(self): + cls = type(self.ctx.flags) + invalid = cls() + for operation in (lambda: len(invalid), lambda: iter(invalid), lambda: repr(invalid), + lambda: invalid.copy(), lambda: invalid[decimal.Inexact], + lambda: invalid == self.ctx.flags, lambda: self.ctx.flags == invalid): + with self.assertRaisesRegex(ValueError, 'invalid signal dict'): + operation() + with self.assertRaisesRegex(ValueError, 'invalid signal dict'): + invalid[decimal.Inexact] = True + + @unittest.skipUnless(sys.implementation.name == 'graalpy', 'CPython 3.13 dereferences an unbound SignalDict here') + def test_unbound_signal_dict_assignment(self): + invalid = type(self.ctx.flags)() + with self.assertRaisesRegex(ValueError, 'invalid signal dict'): + self.ctx.flags = invalid + + +class DecimalNumericSlotsTests(unittest.TestCase): + binary = (operator.add, operator.sub, operator.mul, operator.truediv, + operator.floordiv, operator.mod, divmod) + + @staticmethod + def describe(value): + if type(value) is tuple: + return tuple(map(repr, value)) + return repr(value) + + def check_binary(self, left, right, operation, prec=7, rounding="ROUND_HALF_EVEN", clamp=0, + emin=-20, emax=20): + results = [] + for module in (decimal, reference): + with module.localcontext(prec=prec, rounding=rounding, Emin=emin, Emax=emax, clamp=clamp) as context: + context.clear_traps() + context.clear_flags() + result = operation(module.Decimal(left), module.Decimal(right)) + if module is reference and operation is operator.floordiv: + # libmpdec finalizes the integer quotient; _pydecimal leaves it unfinalized. + result = result._fix(context) + elif module is reference and operation is divmod: + result = (result[0]._fix(context), result[1]) + results.append((self.describe(result), enabled_flags(context))) + self.assertEqual(results[0], results[1], (left, right, operation.__name__, prec, rounding, clamp, emin, emax)) + + def test_special_values(self): + values = ("0", "-0", "0.00", "-0E+9", "1", "-3.00", "Infinity", "-Infinity", + "NaN123456", "-NaN9", "sNaN54321", "-sNaN7") + for operation in self.binary: + for left in values: + for right in values: + self.check_binary(left, right, operation, prec=3, clamp=1) + + def test_rounding_and_exponent_limits(self): + pairs = (("1.2300", "0"), ("999.5", "0"), ("-999.5", "0"), + ("0.0009995", "0"), ("1E+999999999999999999", "1E-1999999999999999997"), + ("1", "1E-999999999999999999"), ("1", "-1E-999999999999999999"), + ("-1", "1E-999999999999999999"), ("1E-999999999999999999", "1"), + ("-2", "3"), ("-10", "3"), ("999", "0.1"), ("1E-8", "3")) + for rounding in ROUNDINGS: + for clamp in (0, 1): + for left, right in pairs: + for operation in self.binary: + self.check_binary(left, right, operation, prec=3, rounding=rounding, + clamp=clamp, emin=-3, emax=3) + + def test_random_arithmetic(self): + rng = random.Random(80152) + for i in range(300): + left = f"{rng.randrange(-10**40, 10**40)}e{rng.randrange(-65, 15)}" + right = f"{rng.randrange(-10**30, 10**30)}e{rng.randrange(-55, 15)}" + for operation in self.binary: + self.check_binary(left, right, operation, prec=rng.choice((1, 3, 7, 28, 50)), + rounding=rng.choice(ROUNDINGS), clamp=i % 2) + + def test_coefficient_digit_boundaries(self): + # Check both sides of decimal and binary magnitude boundaries, including the compact + # coefficient limit and the small-power cache limit. Precision makes a one-digit error + # observable in the rounded result or flags. + for exponent in (1, 18, 19, 20, 28, 38, 63, 64, 126, 127, 128, 129, 256, 1024): + power = 10**exponent + binary_power = 1 << power.bit_length() + for magnitude in (power - 1, power, power + 1, binary_power - 1, binary_power, binary_power + 1): + for sign in (-1, 1): + self.check_binary(sign * magnitude, 1, operator.mul, prec=exponent, emin=-2000, emax=2000) + self.check_binary(sign * magnitude, 3, operator.truediv, prec=28, emin=-2000, emax=2000) + + def test_unary(self): + for rounding in ROUNDINGS: + for text in ("0", "-0.00", "1.2300", "-1.2300", "999.5", "-999.5", "1E-99", + "-1E-99", "Infinity", "-Infinity", "NaN123456", "-NaN9", "-sNaN12345"): + for operation in (operator.pos, operator.neg, abs): + results = [] + for module in (decimal, reference): + with module.localcontext(prec=3, rounding=rounding, Emin=-3, Emax=3, clamp=1) as context: + context.clear_traps() + context.clear_flags() + result = operation(module.Decimal(text)) + results.append((repr(result), enabled_flags(context))) + self.assertEqual(results[0], results[1], (text, rounding, operation.__name__)) + + def test_integer_operands_and_reflection(self): + for integer in (False, True, -7, 0, 11, -(2**63), 2**63-1, 2**63, 10**80): + for operation in self.binary + (operator.pow,): + if operation is operator.pow and abs(integer) > 100: + continue + for reverse in (False, True): + results = [] + for module in (decimal, reference): + with module.localcontext() as context: + context.clear_traps() + context.clear_flags() + value = module.Decimal("3") + result = operation(integer, value) if reverse else operation(value, integer) + results.append((self.describe(result), enabled_flags(context))) + self.assertEqual(results[0], results[1], (integer, reverse, operation.__name__)) + + class IntegerSubclass(int): + def __int__(self): + raise AssertionError("must use the stored integer value") + + self.assertEqual(repr(decimal.Decimal(2) + IntegerSubclass(3)), "Decimal('5')") + + class Reflected: + def __radd__(self, other): + return "reflected" + + self.assertEqual(decimal.Decimal(1) + Reflected(), "reflected") + for operation in self.binary + (operator.pow,): + for other in (1.0, "1", None, complex(1), object()): + with self.assertRaises(TypeError): + operation(decimal.Decimal(1), other) + with self.assertRaises(TypeError): + operation(other, decimal.Decimal(1)) + + def test_base_type_results(self): + class Subclass(decimal.Decimal): + pass + + for operation in (operator.pos, operator.neg, abs): + self.assertIs(type(operation(Subclass(2))), decimal.Decimal) + for operation in self.binary + (operator.pow,): + result = operation(Subclass(2), Subclass(3)) + for value in result if type(result) is tuple else (result,): + self.assertIs(type(value), decimal.Decimal) + + def test_conversion_slots(self): + for text in ("0", "-0", "0E+999999999999999999", "-0E-1999999999999999997", "1.99", "-1.99", + "9223372036854775807", "-9223372036854775808", "1E+100", "1E-999999999999999999"): + with decimal.localcontext(prec=1, Emin=-1, Emax=1) as context: + context.clear_flags() + self.assertEqual(int(decimal.Decimal(text)), int(reference.Decimal(text)) if reference.Decimal(text) else 0) + self.assertEqual(bool(decimal.Decimal(text)), bool(reference.Decimal(text))) + self.assertEqual(enabled_flags(context), []) + for text in ("0", "-0", "-0.00", "0E+999999999999999999", "0.1", "1.234567890123456789", + "1E+999999999999999999", "-1E-1999999999999999997", "5E-324", "2.4703282292062327E-324", + "1.7976931348623157E+308", "Infinity", "-Infinity", "NaN", "-NaN"): + result = float(decimal.Decimal(text)) + expected = float(reference.Decimal(text)) + if math.isnan(expected): + self.assertTrue(math.isnan(result)) + else: + self.assertEqual(result, expected, text) + self.assertEqual(math.copysign(1, result), math.copysign(1, expected), text) + for text in ("Infinity", "-Infinity"): + with self.assertRaises(OverflowError): + int(decimal.Decimal(text)) + for text in ("NaN", "-NaN", "sNaN"): + with self.assertRaises(ValueError): + int(decimal.Decimal(text)) + self.assertTrue(bool(decimal.Decimal(text))) + with self.assertRaises(ValueError): + float(decimal.Decimal("sNaN")) + + def test_power(self): + pairs = (("0", "0"), ("-0", "3"), ("0", "-3"), ("0", "Infinity"), + ("-2", "0.5"), ("-2", "Infinity"), ("Infinity", "0"), ("-Infinity", "-3"), + ("1.00", "Infinity"), ("1.00", "100"), ("1.00", "-3"), ("1", "0.5"), + ("2", "10"), ("2", "-3"), ("2", "0.5"), ("4", "0.5"), + ("32", "0.2"), ("0.25", "-0.5"), ("1E+10", "0.1"), + ("1.000001", "1000000"), ("10.0", "2"), ("2", "1000"), + ("2", "1E+999999999999999999"), ("0.9", "1E+999999999999999999"), + ("2", "1E-999999999999999999"), ("0.9", "1E-999999999999999999"), + ("NaN123", "sNaN45"), ("-sNaN67", "0")) + for rounding in ROUNDINGS: + for left, right in pairs: + if (left, right) == ("1E+10", "0.1") or right == "1E-999999999999999999": + # The C implementation rounds ln, the product, and exp separately; + # its directed rounding can differ from _pydecimal at exact boundaries. + continue + self.check_binary(left, right, operator.pow, prec=7, rounding=rounding) + for left, right in (("1.23456789", "-123"), ("2", "0.5"), ("1.000000000001", "123456789")): + self.check_binary(left, right, operator.pow, prec=100) + + def test_power_working_precision(self): + # CPython _decimal results, including the intermediate rounding documented for power. + for rounding in ROUNDINGS: + with decimal.localcontext(prec=7, Emin=-20, Emax=20, rounding=rounding) as context: + context.clear_flags() + result = decimal.Decimal("1E+10") ** decimal.Decimal("0.1") + expected = "9.999999" if rounding in ("ROUND_DOWN", "ROUND_FLOOR", "ROUND_05UP") else "10.00000" + self.assertEqual(repr(result), f"Decimal('{expected}')") + self.assertEqual(enabled_flags(context), ["Inexact", "Rounded"]) + for base in ("2", "0.9"): + context.clear_flags() + self.assertEqual(repr(decimal.Decimal(base) ** decimal.Decimal("1E-999999999999999999")), + "Decimal('1.000000')") + self.assertEqual(enabled_flags(context), ["Inexact", "Rounded"]) + with decimal.localcontext(prec=10, Emin=-9, Emax=9) as context: + context.clear_flags() + self.assertEqual(repr(decimal.Decimal("1E-18") ** decimal.Decimal("0.5")), "Decimal('1.000000000E-9')") + self.assertEqual(enabled_flags(context), ["Inexact", "Rounded", "Subnormal", "Underflow"]) + with decimal.localcontext(prec=50, Emin=-34, Emax=80, rounding=decimal.ROUND_CEILING, clamp=1) as context: + context.clear_flags() + self.assertEqual(repr(decimal.Decimal("8623471193863540274e8") ** -24), "Decimal('0E-83')") + self.assertEqual(enabled_flags(context), ["Clamped", "Inexact", "Rounded", "Subnormal", "Underflow"]) + with decimal.localcontext(prec=100, Emin=-56, Emax=83, rounding=decimal.ROUND_05UP, clamp=1) as context: + context.clear_traps() + context.clear_flags() + result = decimal.Decimal("-60644599447416923197e-8") ** 67 + self.assertEqual(enabled_flags(context), ["Inexact", "Overflow", "Rounded"]) + self.assertEqual(repr(result), "Decimal('-" + "9" * 84 + "." + "9" * 16 + "')") + + def test_large_context_and_zero_exponents(self): + with decimal.localcontext(prec=decimal.MAX_PREC, Emin=decimal.MIN_EMIN, Emax=decimal.MAX_EMAX) as context: + context.clear_flags() + for result in (+decimal.Decimal(1), decimal.Decimal(2) * 3, decimal.Decimal(3) ** 2, + decimal.Decimal("1.0") ** 2): + self.assertIn(repr(result), ("Decimal('1')", "Decimal('6')", "Decimal('9')", "Decimal('1.00')")) + self.assertEqual(enabled_flags(context), []) + for exponent in (decimal.MAX_EMAX, decimal.MIN_ETINY): + zero = decimal.Decimal((1, (0,), exponent)) + self.assertEqual(repr(decimal.Decimal(-2) ** zero), "Decimal('1')") + self.assertEqual(repr(pow(decimal.Decimal(-2), zero, decimal.Decimal(5))), "Decimal('1')") + with self.assertRaises(MemoryError): + int(decimal.Decimal((0, (1,), decimal.MAX_EMAX))) + with decimal.localcontext(Emax=decimal.MAX_EMAX, Emin=decimal.MIN_EMIN) as context: + context.clear_traps() + context.clear_flags() + result = decimal.Decimal("1E+999999999999999999") ** decimal.Decimal("9.999") + self.assertEqual(repr(result), "Decimal('Infinity')") + self.assertEqual(enabled_flags(context), ["Inexact", "Overflow", "Rounded"]) + + def test_modular_power(self): + cases = (("-2", "3", "5"), ("-2", "4", "-5"), ("-5", "3", "5"), + ("2.00", "3.0", "5.000"), ("2", "1E+5", "123"), + ("1E+10000", "3", "7"), ("0", "0", "1"), + ("2", "-1", "5"), ("2", "0.5", "5"), ("2", "3", "0"), + ("2", "3", "12345"), ("Infinity", "2", "5"), + ("NaN123456", "sNaN45", "-sNaN67"), ("2", "NaN123456", "5")) + for left, right, modulus in cases: + results = [] + for module in (decimal, reference): + with module.localcontext(prec=3) as context: + context.clear_traps() + context.clear_flags() + result = pow(module.Decimal(left), module.Decimal(right), module.Decimal(modulus)) + results.append((repr(result), enabled_flags(context))) + self.assertEqual(results[0], results[1], (left, right, modulus)) + self.assertEqual(repr(pow(decimal.Decimal(2), 3, 5)), "Decimal('3')") + + def test_sticky_flags_and_trap_precedence(self): + with decimal.localcontext(prec=3, Emin=-3, Emax=3) as context: + context.clear_flags() + self.assertEqual(repr(+decimal.Decimal("1.2300")), "Decimal('1.23')") + self.assertEqual(enabled_flags(context), ["Rounded"]) + self.assertEqual(repr(decimal.Decimal(1) / decimal.Decimal(3)), "Decimal('0.333')") + self.assertEqual(enabled_flags(context), ["Inexact", "Rounded"]) + context.clear_flags() + with self.assertRaises(decimal.Overflow): + decimal.Decimal("9999") * 10 + self.assertEqual(enabled_flags(context), ["Inexact", "Overflow", "Rounded"]) + context.clear_flags() + with self.assertRaises(decimal.InvalidOperation) as caught: + divmod(decimal.Decimal(1), decimal.Decimal(0)) + self.assertEqual(caught.exception.args, ([decimal.InvalidOperation, decimal.DivisionByZero],)) + self.assertEqual(enabled_flags(context), ["DivisionByZero", "InvalidOperation"]) + context.clear_flags() + with self.assertRaises(decimal.InvalidOperation) as caught: + decimal.Decimal(0) / decimal.Decimal(0) + self.assertEqual(caught.exception.args, ([decimal.DivisionUndefined],)) + context.clear_flags() + context.traps[decimal.Underflow] = True + with self.assertRaises(decimal.Underflow): + decimal.Decimal("1E-9") / 3 + self.assertEqual(enabled_flags(context), ["Clamped", "Inexact", "Rounded", "Subnormal", "Underflow"]) + + +class DecimalDunderTests(unittest.TestCase): + def test_strings_and_attributes(self): + for capitals in (0, 1): + with decimal.localcontext(capitals=capitals): + for text in ("1.2300", "-0E-9", "1E+99", "Infinity", "-NaN123", "sNaN9"): + value = decimal.Decimal(text) + expected = text.replace("E", "e") if not capitals else text + self.assertEqual(str(value), expected) + self.assertEqual(repr(value), f"Decimal('{expected}')") + self.assertIs(value.real, value) + self.assertEqual(repr(value.imag), "Decimal('0')") + self.assertIs(type(value.imag), decimal.Decimal) + self.assertEqual(value.__getattribute__("__class__"), decimal.Decimal) + with self.assertRaises(AttributeError): + decimal.Decimal(1).missing + with self.assertRaises(TypeError): + decimal.Decimal(1).__getattribute__(1) + self.assertIsInstance(decimal.Decimal(1), numbers.Number) + + def test_copy_and_pickle(self): + class Subclass(decimal.Decimal): + @property + def custom(self): + return "descriptor" + + value = Subclass("-1.2300") + self.assertEqual(value.custom, "descriptor") + self.assertIs(value.__copy__(), value) + self.assertIs(value.__deepcopy__({}), value) + self.assertEqual(value.__reduce__(), (Subclass, ("-1.2300",))) + for text in ("-0.00", "1E-9", "Infinity", "NaN123", "-sNaN9"): + value = decimal.Decimal(text) + self.assertIs(copy.copy(value), value) + self.assertIs(copy.deepcopy(value), value) + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): + self.assertEqual(repr(pickle.loads(pickle.dumps(value, protocol))), repr(value)) + self.assertGreater(decimal.Decimal(1).__sizeof__(), 0) + self.assertGreater(decimal.Decimal("1" * 500).__sizeof__(), decimal.Decimal(1).__sizeof__()) + + def test_hash(self): + for text in ("0", "-0E+99", "1.00", "-1", "123456789012345678901234567890", + "0.1", "0.5", "1E-999999999999999999", "1E+999999999999999999", + "-1E-1999999999999999997", "Infinity", "-Infinity"): + with decimal.localcontext(prec=1) as context: + context.clear_flags() + self.assertEqual(hash(decimal.Decimal(text)), hash(reference.Decimal(text)), text) + self.assertFalse(any(context.flags.values())) + for value in (0, -1, 2**100, Fraction(1, 2), 0.125, complex(2)): + self.assertEqual(hash(decimal.Decimal(str(value.real) if isinstance(value, complex) else + "0.5" if isinstance(value, Fraction) else str(value))), hash(value)) + nan = decimal.Decimal("NaN") + self.assertEqual(hash(nan), object.__hash__(nan)) + self.assertEqual(hash(nan), hash(nan)) + with self.assertRaises(TypeError): + hash(decimal.Decimal("sNaN")) + + def test_comparisons(self): + operations = (operator.eq, operator.ne, operator.lt, operator.le, operator.gt, operator.ge) + values = ("0", "-0", "-0.00", "1", "-2.50", "0.1", "1E-999999999999999999", + "1E+999999999999999999", "Infinity", "-Infinity", "NaN123", "sNaN9") + for left in values: + for right in values + (False, True, 10**100, 0.1, float("nan"), float("inf"), Fraction(1, 3), Fraction(-5, 2)): + for operation in operations: + results = [] + for module in (decimal, reference): + with module.localcontext(prec=1, Emin=-1, Emax=1) as context: + context.clear_traps() + context.clear_flags() + other = module.Decimal(right) if isinstance(right, str) else right + results.append((operation(module.Decimal(left), other), enabled_flags(context))) + self.assertEqual(results[0], results[1], (left, right, operation.__name__)) + value = decimal.Decimal("1") + self.assertIs(value.__eq__("1"), NotImplemented) + self.assertIs(value.__lt__(object()), NotImplemented) + self.assertIs(value.__eq__(complex(1, 1)), NotImplemented) + self.assertIs(value.__lt__(complex(1)), NotImplemented) + with decimal.localcontext() as context: + context.clear_flags() + context.traps[decimal.FloatOperation] = True + self.assertTrue(value == 1.0) + self.assertTrue(value == complex(1)) + self.assertTrue(context.flags[decimal.FloatOperation]) + with self.assertRaises(decimal.FloatOperation): + value < 2.0 + with self.assertRaises(decimal.InvalidOperation): + decimal.Decimal("NaN") < 1 + with self.assertRaises(decimal.InvalidOperation): + decimal.Decimal("sNaN") == 1 + self.assertFalse(decimal.Decimal("NaN") == 1) + + def test_integer_rounding(self): + for text in ("0", "-0.00", "0E+999999999999999999", "2.5", "3.5", "-2.5", "-3.5", + "1.00001", "-1.00001", "1E-999999999999999999", "-1E-999999999999999999", + "123456789012345678901234567890.9", "1E+100"): + for operation in (round, math.ceil, math.floor, math.trunc): + with decimal.localcontext(prec=1, Emin=-1, Emax=1, rounding=decimal.ROUND_UP) as context: + context.clear_flags() + self.assertEqual(operation(decimal.Decimal(text)), operation(reference.Decimal(text)) if reference.Decimal(text) else 0, (text, operation)) + self.assertIs(type(operation(decimal.Decimal(text))), int) + self.assertFalse(any(context.flags.values())) + for text in ("Infinity", "-Infinity", "NaN", "sNaN"): + for operation in (round, math.ceil, math.floor, math.trunc): + with self.assertRaises(OverflowError if "Infinity" in text else ValueError): + operation(decimal.Decimal(text)) + + def test_round_ndigits(self): + for rounding in ROUNDINGS: + for text in ("1.2300", "-0.0000", "0E+20", "0E-20", "999.99", "-1.2345", "1E-20", + "-1E-20", "Infinity", "NaN123456", "sNaN123456"): + for ndigits in (-10, -3, -1, 0, 1, 4, 10, True, False, -(2**63), 2**63-1): + for clamp in (0, 1): + results = [] + for module in (decimal, reference): + with module.localcontext(prec=5, Emin=-3, Emax=3, rounding=rounding, clamp=clamp) as context: + context.clear_traps() + context.clear_flags() + result = round(module.Decimal(text), ndigits) + results.append((repr(result), enabled_flags(context))) + self.assertEqual(results[0], results[1], (text, ndigits, rounding, clamp)) + for ndigits in (None, 1.0, "1", object()): + with self.assertRaises(TypeError): + decimal.Decimal(1).__round__(ndigits) + with self.assertRaises(OverflowError): + round(decimal.Decimal(1), 2**63) + + def test_complex(self): + for text in ("0", "-0", "-0.00", "0.1", "1E+999999999999999999", "Infinity", "-Infinity", "NaN", "-NaN"): + value = complex(decimal.Decimal(text)) + expected = complex(reference.Decimal(text)) + if math.isnan(expected.real): + self.assertTrue(math.isnan(value.real)) + else: + self.assertEqual(value, expected) + self.assertEqual(math.copysign(1, value.real), math.copysign(1, expected.real)) + self.assertEqual(math.copysign(1, value.imag), 1) + with self.assertRaises(ValueError): + complex(decimal.Decimal("sNaN")) + + def test_format(self): + specs = ("", "f", "F", "g", "G", "e", "E", "%", ".0f", ".3f", ".0g", ".4g", ".0e", ".3e", + "+20,.2f", "020,.2f", "^30", "*>30.4g", "😀^30.3f", "\x00<30", "=+20.4f", " 20.2%", + "#g", "#.0f", "z.0f", "_.3f", "n") + for rounding in ROUNDINGS: + for capitals in (0, 1): + for text in ("0", "-0.00", "0E+9", "0E-9", "1.2300", "-123456789.999", "9.9999", + "-0.001", "1E-10", "1E+10", "Infinity", "NaN123", "-sNaN9"): + for spec in specs: + results = [] + for module in (decimal, reference): + with module.localcontext(prec=1, Emin=-1, Emax=1, rounding=rounding, capitals=capitals) as context: + context.clear_flags() + results.append((format(module.Decimal(text), spec), enabled_flags(context))) + self.assertEqual(results[0], results[1], (text, spec, rounding, capitals)) + rng = random.Random(80152) + for _ in range(300): + text = f"{rng.randrange(-10**40, 10**40)}e{rng.randrange(-65, 15)}" + spec = f"{rng.randrange(5, 50)}.{rng.randrange(0, 20)}{rng.choice('eEfFgG%')}" + self.assertEqual(format(decimal.Decimal(text), spec), format(reference.Decimal(text), spec), (text, spec)) + + def test_format_errors_and_overrides(self): + value = decimal.Decimal("1234567.89") + self.assertEqual(value.__format__("n", {"decimal_point": "@", "thousands_sep": "_", "grouping": "\3\2\0"}), + "12_34_567@89") + self.assertEqual(value.__format__("f", {"decimal_point": "😀", "thousands_sep": "•", "grouping": "\3\0"}), + "1•234•567😀89") + for spec in ("s", ".", "..2f", "<020", ",n", "++f", "2.3q", "#.00f", "z.00f"): + with self.assertRaises(ValueError): + format(value, spec) + self.assertEqual(value.__format__("#f", None), "1234567.89") + self.assertEqual(value.__format__("_f", None), "1_234_567.89") + self.assertEqual(value.__format__("١٦.٢f", None), " 1234567.89") + self.assertEqual(value.__format__(".٠٢f", None), "1234567.89") + with self.assertRaises(TypeError): + value.__format__(1) + for override in (None, [], 1): + with self.assertRaises(TypeError): + value.__format__("f", override) + for override in ({"decimal_point": ""}, {"decimal_point": "abcde"}, {"grouping": "\xff"}): + with self.assertRaises(ValueError): + value.__format__("f", override) + with self.assertRaises(ValueError): + format(value, f"{decimal.MAX_PREC + 1}f") + + def test_rational_attribute_validation(self): + class Index: + def __index__(self): + raise AssertionError("must not coerce rational attributes") + + class Rational: + numerator = 1 + denominator = 2 + + numbers.Rational.register(Rational) + value = Rational() + self.assertTrue(decimal.Decimal("0.5") == value) + for invalid in (Index(), "1", 1.0): + value.numerator = invalid + with self.assertRaises(TypeError): + decimal.Decimal(1) == value + value.numerator = 1 + value.denominator = invalid + with self.assertRaises(TypeError): + decimal.Decimal(1) == value + # Specials only need the numerator. + self.assertTrue(decimal.Decimal("Infinity") > value) + value.denominator = 2 + + def test_format_deprecated_N(self): + value = decimal.Decimal("1.234E+9") + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always", DeprecationWarning) + self.assertEqual(format(value, "N"), format(value, "n").upper()) + self.assertEqual(len(caught), 1) + self.assertIs(caught[0].category, DeprecationWarning) + self.assertEqual(caught[0].filename, __file__) + format(value, "N>20.3f") + self.assertEqual(len(caught), 1) + + def test_compact_hash_exponents(self): + rng = random.Random(80152) + coefficients = (0, 1, 2**61 - 1, 2**61, 2**63 - 1) + exponents = (-1999999999999999997, -999999999999999999, -100, -19, -1, 1, 18, 100, 999999999999999999) + for coefficient in coefficients: + for exponent in exponents: + for sign in ("", "-"): + bounded_exponent = min(exponent, decimal.MAX_EMAX - len(str(coefficient)) + 1) + text = f"{sign}{coefficient}E{bounded_exponent}" + self.assertEqual(hash(decimal.Decimal(text)), hash(reference.Decimal(text)), text) + for _ in range(500): + text = f"{rng.randrange(-(2**63 - 1), 2**63)}E{rng.randrange(-1000, 1001)}" + self.assertEqual(hash(decimal.Decimal(text)), hash(reference.Decimal(text)), text) + + def test_compact_comparisons(self): + rng = random.Random(80152) + operations = (operator.eq, operator.ne, operator.lt, operator.le, operator.gt, operator.ge) + integers = (False, True, -1, 0, 1, -(2**31), 2**31, -(2**63), 2**63 - 1, 2**63, 10**100) + values = ("0", "-0E+999999999999999999", "0E-1999999999999999997", "1.00", "-1.001", + "9223372036854775807", "922337203685477580E+1", "922337203685477581E+1", + "1E-1999999999999999997", "1E+999999999999999999") + pairs = [(left, right) for left in values for right in values + integers] + for _ in range(500): + left = f"{rng.randrange(-(2**63 - 1), 2**63)}E{rng.randrange(-20, 21)}" + right = f"{rng.randrange(-(2**63 - 1), 2**63)}E{rng.randrange(-20, 21)}" + pairs.append((left, right)) + for left, right in pairs: + for operation in operations: + other = decimal.Decimal(right) if isinstance(right, str) else right + expected = reference.Decimal(right) if isinstance(right, str) else right + self.assertEqual(operation(decimal.Decimal(left), other), operation(reference.Decimal(left), expected), + (left, right, operation.__name__)) + self.assertEqual(operation(other, decimal.Decimal(left)), operation(expected, reference.Decimal(left)), + (right, left, operation.__name__)) + + def test_compact_integer_rounding_boundaries(self): + for coefficient in (0, 1, 4999999999999999999, 5000000000000000000, 5000000000000000001, 2**63 - 1): + for exponent in (-999999999999999999, -20, -19, -18, -1, 0, 1, 18, 20): + for sign in ("", "-"): + text = f"{sign}{coefficient}E{exponent}" + for operation in (round, math.floor, math.ceil): + self.assertEqual(operation(decimal.Decimal(text)), operation(reference.Decimal(text)), (text, operation)) + + +class DecimalMethodTests(unittest.TestCase): + unary = ("normalize", "to_integral", "to_integral_value", "to_integral_exact", "sqrt", "logb", + "next_minus", "next_plus", "logical_invert", "is_normal", "is_subnormal", "number_class", + "to_eng_string") + binary = ("compare", "compare_signal", "compare_total", "compare_total_mag", "max", "min", + "max_mag", "min_mag", "next_toward", "remainder_near", "copy_sign", "same_quantum", + "logical_and", "logical_or", "logical_xor", "rotate", "shift", "scaleb") + + def check_method(self, name, *values, **settings): + results = [] + for module in (decimal, reference): + with module.localcontext(**settings) as context: + context.clear_traps() + context.clear_flags() + operands = [module.Decimal(v) for v in values] + try: + result = getattr(operands[0], name)(*operands[1:]) + if isinstance(result, module.Decimal): + result = repr(result) + results.append((result, enabled_flags(context))) + except (ValueError, OverflowError) as exc: + results.append((type(exc).__name__, enabled_flags(context))) + self.assertEqual(results[0], results[1], (name, values, settings)) + + def test_special_values(self): + values = ("0", "0.00", "-0.00", "0E+7", "1", "1.00", "1.000", "-1", "-1.00", "Infinity", "-Infinity", + "NaN", "-NaN12345", "sNaN12345", "-sNaN9") + for value in values: + for name in self.unary: + self.check_method(name, value, prec=3, Emin=-3, Emax=4, clamp=1) + for other in values: + for name in self.binary: + self.check_method(name, value, other, prec=3, Emin=-3, Emax=4, clamp=1) + for third in ("-2", "Infinity", "-Infinity", "NaN98", "sNaN765"): + self.check_method("fma", value, other, third, prec=3, Emin=-3, Emax=4, clamp=1) + + def test_rounding_and_exponent_boundaries(self): + values = ("-0E-100", "0E+100", "1.00000", "1.23", "-1.23000", "9.9999", "-9.9999", + "1.00001", "-1.00001", "0.999999", "-0.999999", "1E-3", "1E-8", "-1E-8", + "99999", "-99999", "1E+100", "-1E+100", "1.0001E-3", "9.9999E-4") + for rounding in ROUNDINGS: + for clamp in (0, 1): + for value in values: + for name in self.unary: + self.check_method(name, value, prec=3, Emin=-3, Emax=4, rounding=rounding, clamp=clamp) + for other in ("0", "-0", "1E-8", "-1E-8", "1", "-1", "3.14", "1E+8"): + for name in ("next_toward", "remainder_near", "max", "min", "max_mag", "min_mag"): + self.check_method(name, value, other, prec=3, Emin=-3, Emax=4, rounding=rounding, clamp=clamp) + + def test_digit_operations(self): + for prec in (1, 3, 9, 28): + for value in ("0", "-0", "1", "101", "1100101001101", "11111111111111111111111111111", + "2", "11.0", "-11", "123456789.00"): + for name in ("logical_and", "logical_or", "logical_xor"): + for other in ("0", "1", "1010101010101", "2", "-0"): + self.check_method(name, value, other, prec=prec) + for name in ("rotate", "shift", "scaleb"): + for count in (str(-prec-1), str(-prec), "-1", "0", "1", str(prec), str(prec+1), + "1.0", "1E+1", "Infinity", "NaN"): + self.check_method(name, value, count, prec=prec) + + def test_random_arithmetic(self): + rng = random.Random(80152) + for i in range(500): + operands = [f"{rng.randrange(-10**40, 10**40)}E{rng.randrange(-45, 25)}" for _ in range(3)] + settings = dict(prec=rng.choice((1, 3, 5, 19, 28, 50)), Emin=-20, Emax=20, + rounding=rng.choice(ROUNDINGS), clamp=i % 2) + for name in ("sqrt", "normalize", "next_minus", "next_plus", "to_integral_exact", "to_eng_string"): + self.check_method(name, operands[0], **settings) + for name in ("next_toward", "remainder_near", "max_mag", "min_mag"): + self.check_method(name, *operands[:2], **settings) + self.check_method("fma", *operands, **settings) + + def test_conversion_and_classification(self): + values = ("0", "-0E-100", "0E+999999999999999999", "1.23000", "123456789123456789123456789", + "-100.00", "1E-30", "3.14E+30", "Infinity", "-Infinity", "NaN", "sNaN", "-NaN123") + for value in values: + for name in ("as_tuple", "as_integer_ratio", "adjusted", "is_canonical", "is_finite", "is_infinite", + "is_nan", "is_qnan", "is_snan", "is_signed", "is_zero", "copy_abs", "copy_negate", "radix"): + self.check_method(name, value) + obj = decimal.Decimal(value) + self.assertIs(obj.canonical(), obj) + self.assertIs(obj.conjugate(), obj) + for value in (0, -0.0, 0.1, -1.25, 5e-324, float("inf"), float("nan"), 10**100, True): + result = decimal.Decimal.from_float(value) + expected = reference.Decimal.from_float(value) + self.assertEqual(result.as_tuple(), expected.as_tuple()) + self.assertIs(type(result), decimal.Decimal) + + def test_exact_product_and_nearest_remainder(self): + for prec in (1, 3, 9): + for a, b, c in (("123456789", "123456789", "-15241578750190520"), + ("9.9999", "9.9999", "-99.998"), ("-0", "2", "-0.00")): + self.check_method("fma", a, b, c, prec=prec) + for a, b in (("1", "123456789123456789E-10"), ("3", "2"), ("5", "2"), + ("-3", "2"), ("-5", "-2"), ("999.5", "1"), ("998.5", "1")): + self.check_method("remainder_near", a, b, prec=prec) + for a, b in (("1E-3", "0"), ("-1E-3", "0"), ("0", "-1"), ("-0", "1")): + self.check_method("next_toward", a, b, prec=prec, Emin=-3, Emax=3) + + def test_extreme_exponents(self): + for value in ("1E+999999999999999999", "1E-999999999999999999", "0E-1999999999999999997", + "0E+999999999999999999", "-0E-999999999999999999"): + for name in ("normalize", "sqrt", "logb", "next_minus", "next_plus", "to_integral_exact", "to_eng_string"): + self.check_method(name, value, prec=3, Emin=-3, Emax=3) + for name in ("next_toward", "scaleb", "shift", "rotate", "compare_total", "remainder_near"): + self.check_method(name, value, "1", prec=3, Emin=-3, Emax=3) + # A huge precision need not allocate a huge coefficient for small, exact results. + context = decimal.Context(prec=decimal.MAX_PREC, Emin=decimal.MIN_EMIN, Emax=decimal.MAX_EMAX) + for name in ("rotate", "shift"): + self.assertEqual(getattr(decimal.Decimal("12.0"), name)(1, context=context).as_tuple(), (0, (1, 2, 0, 0), -1)) + self.assertTrue(decimal.Decimal(0).next_plus(context=context).is_finite()) + for value in ("1", "4.00", "2.25", "40000.00"): + obj = decimal.Decimal(value) + self.assertEqual(obj.sqrt(context=context) ** 2, obj) + + def test_context_arguments_and_traps(self): + value = decimal.Decimal("1.2345") + with decimal.localcontext(prec=1) as current: + current.clear_flags() + context = decimal.Context(prec=3, rounding=decimal.ROUND_DOWN) + context.clear_traps() + for name in self.unary: + context.clear_flags() + getattr(value, name)(context=context) + self.assertFalse(any(current.flags.values()), name) + for name in self.binary: + context.clear_flags() + getattr(value, name)(1, context=context) + self.assertFalse(any(current.flags.values()), name) + context.clear_flags() + self.assertEqual(str(value.fma(1, 0, context=context)), "1.23") + self.assertFalse(any(current.flags.values())) + self.assertEqual(str(value.to_integral(rounding=decimal.ROUND_UP, context=context)), "2") + self.assertEqual(context.rounding, decimal.ROUND_DOWN) + context.traps[decimal.Inexact] = True + context.clear_flags() + with self.assertRaises(decimal.Inexact): + value.to_integral_exact(context=context) + self.assertEqual(enabled_flags(context), ["Inexact", "Rounded"]) + context.traps[decimal.InvalidOperation] = True + with self.assertRaises(decimal.InvalidOperation): + decimal.Decimal("sNaN").compare_total(1, context=context).compare_signal(decimal.Decimal("NaN"), context=context) + + def test_argument_validation_and_subclasses(self): + class Subclass(decimal.Decimal): + pass + + class FloatSubclass(decimal.Decimal): + def __new__(cls, value): + assert type(value) is decimal.Decimal + return super().__new__(cls, value) + + self.assertIs(type(FloatSubclass.from_float(0.5)), FloatSubclass) + value = Subclass("1.23") + for name in self.unary: + with self.assertRaises(TypeError): + getattr(value, name)(context=1) + with self.assertRaises(TypeError): + getattr(value, name)(extra=1) + for name in self.binary: + for other in ("1", 1.0, None, object()): + with self.assertRaises(TypeError): + getattr(value, name)(other) + with decimal.localcontext() as context: + context.clear_traps() + result = getattr(value, name)(True) + if isinstance(result, decimal.Decimal): + self.assertIs(type(result), decimal.Decimal) + with self.assertRaises(TypeError): + getattr(value, name)(1, context=1) + with self.assertRaises(TypeError): + getattr(value, name)(1, extra=1) + for name in ("to_integral", "to_integral_value", "to_integral_exact"): + for rounding in ("bad", 1, object()): + with self.assertRaises(TypeError): + getattr(value, name)(rounding=rounding) + for operands in (("1", 1), (1, "1"), (1.0, 1), (1, None)): + with self.assertRaises(TypeError): + decimal.Decimal(1).fma(*operands) + with decimal.localcontext() as context: + context.clear_flags() + context.traps[decimal.FloatOperation] = True + decimal.Decimal.from_float(0.1) + self.assertFalse(any(context.flags.values())) + for value in ("1", decimal.Decimal(1), None): + with self.assertRaises(TypeError): + decimal.Decimal.from_float(value) + + +class DecimalContextMethodTests(unittest.TestCase): + unary = ("abs", "exp", "ln", "log10", "minus", "next_minus", "next_plus", "normalize", "plus", + "to_integral", "to_integral_exact", "to_integral_value", "sqrt", "is_finite", "is_infinite", + "is_nan", "is_normal", "is_qnan", "is_signed", "is_snan", "is_subnormal", "is_zero", + "copy_abs", "copy_decimal", "copy_negate", "logb", "logical_invert", "number_class", + "to_sci_string", "to_eng_string") + binary = ("add", "compare", "compare_signal", "divide", "divide_int", "divmod", "max", "max_mag", + "min", "min_mag", "multiply", "next_toward", "quantize", "remainder", "remainder_near", + "subtract", "power", "compare_total", "compare_total_mag", "copy_sign", "logical_and", + "logical_or", "logical_xor", "rotate", "same_quantum", "scaleb", "shift") + + @staticmethod + def snapshot(module, value): + if isinstance(value, module.Decimal): + return value.as_tuple() + if isinstance(value, tuple): + return tuple(DecimalContextMethodTests.snapshot(module, item) for item in value) + return value + + def check_operation(self, name, *values, **settings): + results = [] + for module in (decimal, reference): + # The receiver must control rounding and status independently of the active context. + with module.localcontext(prec=19, Emin=-99, Emax=99) as active: + active.clear_flags() + context = module.Context(traps=[], **settings) + operands = [module.Decimal(value) if isinstance(value, str) else value for value in values] + try: + result = getattr(context, name)(*operands) + results.append((self.snapshot(module, result), enabled_flags(context))) + except module.DecimalException as exc: + results.append((type(exc).__name__, enabled_flags(context))) + self.assertFalse(any(active.flags.values()), (name, values)) + self.assertEqual(results[0], results[1], (name, values, settings)) + + def test_operations_use_receiver_context(self): + settings = dict(prec=3, Emin=-3, Emax=4, clamp=1) + values = ("-0.00", "1.2345", "-12.50", "1E-5", "1E+7", "Infinity", "-Infinity", "NaN12345", "sNaN9") + for name in self.unary: + for value in values: + self.check_operation(name, value, **settings) + for name in self.binary: + for left in values: + for right in ("0", "-1.00", "2.1", "Infinity", "NaN7", "sNaN8"): + self.check_operation(name, left, right, **settings) + for values in (("1.234", "5.678", "-7.007"), ("0", "Infinity", "NaN7"), + ("NaN7", "sNaN8", "sNaN9"), ("1E+7", "1", "-1E+7")): + self.check_operation("fma", *values, **settings) + + def test_rounding_modes(self): + for rounding in ROUNDINGS: + for name in ("abs", "minus", "plus", "normalize", "to_integral", "to_integral_exact", "to_integral_value"): + self.check_operation(name, "-1.250", prec=2, rounding=rounding) + self.check_operation(name, "-0.00", prec=2, rounding=rounding) + for name in ("add", "subtract", "multiply", "divide", "quantize", "remainder", "remainder_near"): + self.check_operation(name, "1.250", "1", prec=2, rounding=rounding) + + def test_exact_integer_operands(self): + for name in self.unary: + for value in (True, False, -10, 101, 2**80): + self.check_operation(name, value, prec=7) + for name in self.binary: + self.check_operation(name, 10, 3, prec=7) + self.check_operation(name, "10", 3, prec=7) + self.check_operation(name, 10, "3", prec=7) + self.check_operation("fma", 2, 3, 4, prec=2) + self.check_operation("power", 2, 8, 5, prec=2) + + def test_invalid_operand_types(self): + context = decimal.Context(traps=[]) + for name in self.unary + self.binary + ("fma",): + count = 1 if name in self.unary else 3 if name == "fma" else 2 + for position in range(count): + for invalid in ("1", 1.0, None, (), object()): + operands = [decimal.Decimal(1)] * count + operands[position] = invalid + with self.assertRaises(TypeError, msg=(name, position, invalid)): + getattr(context, name)(*operands) + with self.assertRaises(TypeError, msg=name): + getattr(context, name)(*[1] * (count - 1)) + if name != "power": + with self.assertRaises(TypeError, msg=name): + getattr(context, name)(*[1] * (count + 1)) + with self.assertRaises(TypeError, msg=name): + getattr(context, name)(a=1) + self.assertFalse(any(context.flags.values())) + + def test_canonical_and_identity(self): + class DecimalSubclass(decimal.Decimal): + pass + context = decimal.Context(prec=1, traps=[]) + for operand in (decimal.Decimal("-0.00"), decimal.Decimal("sNaN1234"), DecimalSubclass("1.234")): + self.assertIs(context.canonical(operand), operand) + self.assertIs(context.copy_decimal(operand), operand) + self.assertIs(context.is_canonical(operand), True) + for name in ("canonical", "is_canonical"): + for invalid in (1, "1", None): + with self.assertRaisesRegex(TypeError, "argument must be a Decimal"): + getattr(context, name)(invalid) + self.assertFalse(any(context.flags.values())) + + def test_quiet_operations_preserve_representation(self): + context = decimal.Context(prec=1, Emin=-1, Emax=1, traps=[decimal.InvalidOperation]) + value = decimal.Decimal("-sNaN1234") + self.assertEqual(context.copy_abs(value).as_tuple(), decimal.Decimal("sNaN1234").as_tuple()) + self.assertEqual(context.copy_negate(value).as_tuple(), decimal.Decimal("sNaN1234").as_tuple()) + self.assertEqual(context.copy_sign(value, 1).as_tuple(), decimal.Decimal("sNaN1234").as_tuple()) + self.assertEqual(context.compare_total(value, value), 0) + self.assertEqual(context.compare_total_mag(value, value), 0) + self.assertTrue(context.same_quantum(value, decimal.Decimal("NaN"))) + self.assertFalse(any(context.flags.values())) + + def test_power_keywords(self): + context = decimal.Context(prec=3) + self.assertEqual(context.power(a=2, b=8, modulo=5), 1) + self.assertEqual(context.power(2, b=8, modulo=None), 256) + self.assertEqual(context.power(2, 8), 256) + for kwargs in ({"a": 2, "b": 8, "unknown": 1}, {"a": 2}, {"b": 8}): + with self.assertRaises(TypeError): + context.power(**kwargs) + with self.assertRaises(TypeError): + context.power(2, 8, a=2) + with self.assertRaises(TypeError): + context.power(2, 8, 5, 1) + with self.assertRaises(TypeError): + context.power(2, 8, "5") + + def test_traps_and_flag_ownership(self): + with decimal.localcontext(prec=19) as active: + active.clear_flags() + context = decimal.Context(prec=2, traps=[decimal.Inexact]) + with self.assertRaises(decimal.Inexact): + context.add(decimal.Decimal("1.23"), 0) + self.assertEqual(enabled_flags(context), ["Inexact", "Rounded"]) + self.assertFalse(any(active.flags.values())) + context.clear_flags() + context.traps[decimal.DivisionByZero] = True + with self.assertRaises(decimal.DivisionByZero): + context.divide(1, 0) + self.assertEqual(enabled_flags(context), ["DivisionByZero"]) + context.clear_flags() + context.traps[decimal.InvalidOperation] = True + with self.assertRaises(decimal.InvalidOperation) as caught: + context.divmod(0, 0) + self.assertEqual(caught.exception.args, ([decimal.DivisionUndefined],)) + self.assertTrue(context.flags[decimal.InvalidOperation]) + self.assertFalse(any(active.flags.values())) + + def test_apply_preserves_sign_and_signaling_nan(self): + context = decimal.Context(prec=2, clamp=1, traps=[decimal.InvalidOperation]) + for value, expected in (("-0.00", "-0.00"), ("sNaN123", "sNaN3"), ("-NaN123", "-NaN3"), + ("1.234", "1.2")): + context.clear_flags() + self.assertEqual(context._apply(decimal.Decimal(value)).as_tuple(), decimal.Decimal(expected).as_tuple()) + self.assertFalse(context.flags[decimal.InvalidOperation]) + self.assertEqual(context._apply(12), 12) + with self.assertRaises(TypeError): + context._apply("12") + + def test_create_decimal(self): + context = decimal.Context(prec=3, traps=[]) + self.assertEqual(context.create_decimal(), 0) + for value in (12345, True, "1.2345", decimal.Decimal("1.2345"), (1, (1, 2, 3, 4, 5), -4), + [1, (1, 2, 3, 4, 5), -4], 2**80): + context.clear_flags() + result = context.create_decimal(value) + self.assertIs(type(result), decimal.Decimal) + self.assertEqual(len(result.as_tuple().digits) <= 3, True) + for value in ("-0.00", "sNaN12", "-NaN12", "Infinity", "-Infinity"): + context.clear_flags() + self.assertEqual(context.create_decimal(value).as_tuple(), decimal.Decimal(value).as_tuple()) + self.assertFalse(any(context.flags.values())) + self.assertEqual(context.create_decimal("١.٢٣٤"), decimal.Decimal("1.23")) + for invalid in (" 1", "1 ", "1\n", "\u00a01", "1\u2003", "1_2", "NaN_1", "bad", "NaN1234", + decimal.Decimal("sNaN1234"), (1, (1, 2, 3, 4), "n")): + context.clear_flags() + self.assertTrue(context.create_decimal(invalid).is_qnan()) + self.assertEqual(enabled_flags(context), ["InvalidOperation"]) + context.traps[decimal.InvalidOperation] = True + with self.assertRaises(decimal.InvalidOperation) as caught: + context.create_decimal(invalid) + self.assertEqual(caught.exception.args, ([decimal.ConversionSyntax],)) + context.traps[decimal.InvalidOperation] = False + for invalid in (None, object()): + with self.assertRaises(TypeError): + context.create_decimal(invalid) + with self.assertRaises(ValueError): + context.create_decimal((2, (1,), 0)) + + def test_create_decimal_bounds(self): + for clamp in (0, 1): + for value in ("-0E-99", "0E+99", "1.2345", "1E-9", "9.999E+9", "-1E+9"): + for module in (decimal, reference): + context = module.Context(prec=3, Emin=-3, Emax=4, clamp=clamp, traps=[]) + result = context.create_decimal(value) + snapshot = (result.as_tuple(), enabled_flags(context)) + if module is decimal: + expected = snapshot + else: + self.assertEqual(expected, snapshot, (value, clamp)) + + def test_float_construction(self): + context = decimal.Context(prec=5, traps=[]) + for value in (0.1, -0.0, float("inf"), float("-inf"), float("nan"), 1.5): + context.clear_flags() + explicit = context.create_decimal_from_float(value) + self.assertFalse(context.flags[decimal.FloatOperation]) + explicit_flags = enabled_flags(context) + context.clear_flags() + implicit = context.create_decimal(value) + self.assertEqual(explicit.as_tuple(), implicit.as_tuple()) + self.assertEqual(enabled_flags(context), sorted(explicit_flags + ["FloatOperation"])) + for value in (True, 123456, 2**80): + context.clear_flags() + explicit = context.create_decimal_from_float(value) + explicit_flags = enabled_flags(context) + context.clear_flags() + self.assertEqual(context.create_decimal(value), explicit) + self.assertEqual(enabled_flags(context), explicit_flags) + for value in ("1", decimal.Decimal(1), None): + with self.assertRaisesRegex(TypeError, "argument must be int or float"): + context.create_decimal_from_float(value) + context.traps[decimal.FloatOperation] = True + with self.assertRaises(decimal.FloatOperation): + context.create_decimal(0.1) + context.clear_flags() + context.create_decimal_from_float(0.1) + self.assertFalse(context.flags[decimal.FloatOperation]) + context.traps[decimal.Inexact] = True + with self.assertRaises(decimal.Inexact): + context.create_decimal_from_float(0.1) + + def test_strings_use_receiver_capitals(self): + with decimal.localcontext(capitals=1): + context = decimal.Context(capitals=0) + value = decimal.Decimal("1E+10") + self.assertEqual(context.to_sci_string(value), "1e+10") + self.assertEqual(context.to_eng_string(value), "10e+9") + self.assertFalse(any(context.flags.values())) + + +class DecimalQuantizeTests(unittest.TestCase): + def check_quantize(self, text, quantum, override=None, **settings): + results = [] + for module in (decimal, reference): + with module.localcontext(**settings) as context: + context.clear_traps() + context.clear_flags() + other = module.Decimal(quantum) if isinstance(quantum, str) else quantum + result = module.Decimal(text).quantize(other, rounding=override) + results.append((repr(result), enabled_flags(context))) + self.assertEqual(results[0], results[1], (text, quantum, override, settings)) + + def test_special_values(self): + values = ("0", "-0.00", "2.5", "Infinity", "-Infinity", "NaN123456", "-NaN123", + "sNaN54321", "-sNaN9") + for left in values: + for right in values: + self.check_quantize(left, right, prec=3, clamp=1) + + def test_compact_rounding_and_limits(self): + values = ("0", "-0.0000", "1.2300", "-9.995", "5.00001", "-0.5000", "99.99", "-99.99", + "9223372036854775807", "9223372036854775800", "922337203685477581E-1", + "5000000000000000000E-19", "5000000000000000001E-19", + "4999999999999999999E-19", "9223372036854775807E-20", + "1E-999999999999999999", "0E+999999999999999999", + "0E-1999999999999999997", "1E+999999999999999999") + for rounding in ROUNDINGS: + for clamp in (0, 1): + for text in values: + for exp in (-9, -6, -3, -1, 0, 1, 3, 5): + self.check_quantize(text, f"1E{exp:+}", prec=5, Emin=-3, Emax=5, + rounding=rounding, clamp=clamp) + for exp in (decimal.MAX_EMAX, decimal.MIN_ETINY): + self.check_quantize("0", f"1E{exp:+}", prec=decimal.MAX_PREC, + Emin=decimal.MIN_EMIN, Emax=decimal.MAX_EMAX) + + def test_scaling_and_rounding_overflow(self): + for rounding in ROUNDINGS: + for text, quantum in (("9223372036854775807", "1E-1"), ("922337203685477581", "1E-1"), + ("922337203685477580", "1E-1"), ("9999999999999999999", "1E+1"), + ("999999999999999999", "1E+17"), ("9223372036854775807E+20", "1E+18"), + ("999.9", "1")): + for prec in (3, 19, 20, 25): + self.check_quantize(text, quantum, prec=prec, Emin=-30, Emax=30, rounding=rounding) + + def test_random_quantize(self): + rng = random.Random(80152) + for i in range(1500): + limit = 2**63 if i % 2 else 10**40 + text = f"{rng.randrange(-limit, limit)}E{rng.randrange(-45, 25)}" + quantum = f"1E{rng.randrange(-45, 25)}" + self.check_quantize(text, quantum, prec=rng.choice((1, 3, 5, 19, 28, 50)), + rounding=rng.choice(ROUNDINGS), clamp=i % 2, Emin=-20, Emax=20) + + def test_explicit_context_and_rounding(self): + with decimal.localcontext(prec=1, rounding=decimal.ROUND_DOWN) as current: + current.clear_flags() + context = decimal.Context(prec=10, rounding=decimal.ROUND_FLOOR) + context.clear_flags() + value = decimal.Decimal("1.234") + self.assertEqual(str(value.quantize(decimal.Decimal("0.01"), decimal.ROUND_UP, context)), "1.24") + self.assertEqual(context.rounding, decimal.ROUND_FLOOR) + self.assertEqual(enabled_flags(context), ["Inexact", "Rounded"]) + self.assertFalse(any(current.flags.values())) + self.assertEqual(str(value.quantize(exp=decimal.Decimal("0.01"), rounding=None, context=context)), "1.23") + context.traps[decimal.Inexact] = True + with self.assertRaises(decimal.Inexact): + value.quantize(decimal.Decimal("0.01"), context=context) + with decimal.localcontext(prec=2, Emin=-3, Emax=3) as context: + context.clear_flags() + with self.assertRaises(decimal.InvalidOperation): + decimal.Decimal("999.9").quantize(decimal.Decimal(1)) + self.assertEqual(enabled_flags(context), ["InvalidOperation"]) + context.clear_flags() + result = decimal.Decimal("1E-6").quantize(decimal.Decimal("1E-4")) + self.assertEqual(str(result), "0.0000") + self.assertEqual(enabled_flags(context), ["Inexact", "Rounded"]) + + def test_conversion_and_subclasses(self): + class Integer(int): + def __int__(self): + raise AssertionError("must use stored integer") + + class Subclass(decimal.Decimal): + pass + + for exp in (False, True, 0, 10**100, Integer(1)): + self.check_quantize("12.34", exp) + result = Subclass("1.23").quantize(Subclass("0.1")) + self.assertIs(type(result), decimal.Decimal) + self.assertEqual(str(result), "1.2") + value = decimal.Decimal("1.23") + for exp in ("0.1", 0.1, None, object()): + with self.assertRaises(TypeError): + value.quantize(exp) + for rounding in ("bad", 1, object()): + with self.assertRaises(TypeError): + value.quantize(decimal.Decimal("0.1"), rounding=rounding) + with self.assertRaises(TypeError): + value.quantize(decimal.Decimal("0.1"), context=1) + with self.assertRaises(TypeError): + value.quantize(decimal.Decimal("0.1"), extra=True) + + +class DecimalTranscendentalTests(unittest.TestCase): + methods = ("exp", "ln", "log10") + + def check_method(self, name, value, **settings): + results = [] + for module in (decimal, reference): + context = module.Context(**settings) + context.clear_traps() + operand = module.Decimal(value) + result = getattr(operand, name)(context=context) + results.append((result.as_tuple(), enabled_flags(context))) + self.assertEqual(results[0], results[1], (name, value, settings)) + + def test_special_values_and_exact_results(self): + values = ("0", "-0E-100", "0E+100", "1", "1.00000", "-1", "Infinity", "-Infinity", + "NaN", "-NaN12345", "sNaN12345", "-sNaN9", "10", "1E-100", "1E+100", "100.00") + for value in values: + for name in self.methods: + for clamp in (0, 1): + self.check_method(name, value, prec=3, Emin=-3, Emax=4, clamp=clamp) + + def test_rounding_and_exponent_boundaries(self): + values = ("1.23", "-1.23", "9.9999", "-9.9999", "1.00001", "0.999999", "1E-3", + "1E-8", "-1E-8", "99999", "-99999", "1E+100", "-1E+100", "1.0001E-3", + "9.9999E-4", "0.5", "-10", "23.02585", "-11.512925", "1E+25", "1E-25") + for rounding in ROUNDINGS: + for clamp in (0, 1): + for value in values: + for name in self.methods: + self.check_method(name, value, prec=3, Emin=-3, Emax=4, rounding=rounding, clamp=clamp) + for name in self.methods: + for value in ("1.23456789", "0.9999", "1E+25", "1E-25"): + self.check_method(name, value, prec=1, Emin=0, Emax=0) + + def test_high_precision_and_arguments_close_to_one(self): + for prec in (1, 9, 28, 50, 100): + for value in ("2", "0.5", "1.00000000000000000000000000000000000000000000000001", + "0.99999999999999999999999999999999999999999999999999", "3.141592653589793"): + for name in self.methods: + self.check_method(name, value, prec=prec) + # Huge exponents must not require constructing enormous powers of ten. + for value in ("1E+999999999999999999", "-1E+999999999999999999", + "1E-999999999999999999", "-1E-999999999999999999"): + for name in self.methods: + self.check_method(name, value, prec=9) + + def test_values_near_rounding_midpoints(self): + context = reference.Context(prec=100) + for target in ("1.234565", "9.999995", "0.00001234565"): + midpoint = reference.Decimal(target) + arguments = { + "exp": midpoint.ln(context=context), + "ln": midpoint.exp(context=context), + "log10": context.power(10, midpoint), + } + for name, argument in arguments.items(): + self.check_method(name, str(argument), prec=6) + for prec in (16, 21, 27, 29, 30): + self.check_method("exp", "-9E-8", prec=prec) + + def test_exp_tiny_inputs_at_minimum_normal(self): + # CPython's C implementation rounds very tiny inputs to one in its work + # context, whereas _pydecimal reports tininess for all negative inputs. + for prec in (1, 3, 9, 28): + context = decimal.Context(prec=prec, Emin=0, Emax=0) + context.clear_traps() + for coefficient, tiny in (("8.9", True), ("9", True), ("9.1", False)): + context.clear_flags() + value = decimal.Decimal(f"-{coefficient}E{-prec-4}") + result = value.exp(context=context) + self.assertEqual(result.as_tuple(), decimal.Decimal("1" + "0" * (prec - 1) + f"E{1-prec}").as_tuple()) + expected_flags = ["Inexact", "Rounded"] if tiny else ["Inexact", "Rounded", "Subnormal", "Underflow"] + self.assertEqual(enabled_flags(context), expected_flags) + + def test_random_values(self): + rng = random.Random(80152) + for i in range(100): + value = f"{rng.randrange(1, 10**40)}E{rng.randrange(-65, -15)}" + for name in self.methods: + self.check_method(name, value, prec=rng.choice((1, 3, 19, 28, 50)), + Emin=-20, Emax=20, rounding=rng.choice(ROUNDINGS), clamp=i % 2) + + def test_explicit_context_and_traps(self): + with decimal.localcontext(prec=1) as current: + current.clear_flags() + context = decimal.Context(prec=9, rounding=decimal.ROUND_DOWN) + context.clear_traps() + for name in self.methods: + context.clear_flags() + value = decimal.Decimal("2") + self.assertEqual(getattr(value, name)(context), getattr(value, name)(context=context)) + self.assertEqual(enabled_flags(context), ["Inexact", "Rounded"]) + self.assertFalse(any(current.flags.values())) + self.assertEqual(context.rounding, decimal.ROUND_DOWN) + context.traps[decimal.Inexact] = True + context.clear_flags() + with self.assertRaises(decimal.Inexact): + getattr(value, name)(context=context) + self.assertEqual(enabled_flags(context), ["Inexact", "Rounded"]) + context.traps[decimal.Inexact] = False + context.traps[decimal.InvalidOperation] = True + for name in self.methods: + with self.assertRaises(decimal.InvalidOperation): + getattr(decimal.Decimal("sNaN"), name)(context=context) + for name in ("ln", "log10"): + with self.assertRaises(decimal.InvalidOperation): + getattr(decimal.Decimal("-1"), name)(context=context) + context.clear_flags() + self.assertEqual(getattr(decimal.Decimal("-0"), name)(context=context), decimal.Decimal("-Infinity")) + self.assertFalse(any(context.flags.values())) + context.traps[decimal.Overflow] = True + with self.assertRaises(decimal.Overflow): + decimal.Decimal("1E100").exp(context=context) + context.traps[decimal.Underflow] = True + with self.assertRaises(decimal.Underflow): + decimal.Decimal("-1E100").exp(context=context) + + def test_argument_validation_and_subclasses(self): + class Subclass(decimal.Decimal): + pass + + value = Subclass("2") + for name in self.methods: + method = getattr(value, name) + self.assertIs(type(method()), decimal.Decimal) + self.assertEqual(method(), method(None)) + for context in (1, "context", object()): + with self.assertRaises(TypeError): + method(context=context) + with self.assertRaises(TypeError): + method(extra=1) + with self.assertRaises(TypeError): + method(None, None) + with self.assertRaises(TypeError): + method(None, context=None) + with self.assertRaises(TypeError): + getattr(decimal.Decimal, name)(self=value) + + +class DecimalCompatibilityTests(unittest.TestCase): + def test_extreme_tuple_exponents(self): + cases = ( + (2**63 - 1, (), "-0E+999999", {public_decimal.Clamped}), + (-2**63, (), "-0E-1000007", {public_decimal.Clamped}), + (2**63 - 1, (0, 1, 2), "-Infinity", + {public_decimal.Overflow, public_decimal.Inexact, public_decimal.Rounded}), + (-2**63, (0, 1, 2), "-0E-1000007", + {public_decimal.Underflow, public_decimal.Subnormal, public_decimal.Inexact, + public_decimal.Rounded, public_decimal.Clamped}), + ) + for exponent, digits, expected, flags in cases: + for sequence in (tuple, list): + with self.subTest(exponent=exponent, digits=digits, sequence=sequence): + context = public_decimal.Context(prec=9, traps=[]) + value = sequence((1, digits, exponent)) + self.assertEqual(str(context.create_decimal(value)), expected) + self.assertEqual({signal for signal, enabled in context.flags.items() if enabled}, flags) + with self.assertRaises(public_decimal.InvalidOperation): + public_decimal.Decimal(value) + + def test_extreme_tuple_traps_and_integer_overflow(self): + context = public_decimal.Context(prec=9) + context.traps[public_decimal.Underflow] = True + for exponent, signal in ((2**63 - 1, public_decimal.Overflow), (-2**63, public_decimal.Underflow)): + with self.assertRaises(signal): + context.create_decimal((1, (0, 1, 2), exponent)) + for exponent in (2**63, -2**63 - 1): + for constructor in (public_decimal.Decimal, context.create_decimal): + with self.assertRaises(OverflowError): + constructor((1, (), exponent)) + + def test_extreme_string_exponents(self): + class String(str): + pass + + for exponent in (2**63 - 1, 2**63, 10**100, -2**63, -2**63 - 1, -10**100): + for coefficient in ("-0", "-0.0", "-12", "-1.2"): + for string_type in (str, String): + with self.subTest(exponent=exponent, coefficient=coefficient, string_type=string_type): + context = public_decimal.Context(prec=9, traps=[]) + value = string_type(f"{coefficient}e{exponent}") + if coefficient in ("-0", "-0.0"): + expected = "-0E+999999" if exponent > 0 else "-0E-1000007" + flags = {public_decimal.Clamped} + elif exponent > 0: + expected = "-Infinity" + flags = {public_decimal.Overflow, public_decimal.Inexact, public_decimal.Rounded} + else: + expected = "-0E-1000007" + flags = {public_decimal.Underflow, public_decimal.Subnormal, public_decimal.Inexact, + public_decimal.Rounded, public_decimal.Clamped} + self.assertEqual(str(context.create_decimal(value)), expected) + self.assertEqual({signal for signal, enabled in context.flags.items() if enabled}, flags) + with self.assertRaises(public_decimal.InvalidOperation): + public_decimal.Decimal(value) + + def test_extreme_string_traps_and_syntax(self): + context = public_decimal.Context(prec=9) + context.traps[public_decimal.Underflow] = True + for value, signal in (("1e9223372036854775808", public_decimal.Overflow), + ("1.2e-9223372036854775808", public_decimal.Underflow)): + with self.assertRaises(signal): + context.create_decimal(value) + for value in ("1e999999999999999999999x", "1e+", " 1e999999999999999999999", "1_0"): + with self.assertRaises(public_decimal.InvalidOperation): + context.create_decimal(value) + + def test_context_conversion_for_all_input_types(self): + class Integer(int): + pass + + class Float(float): + pass + + class String(str): + pass + + class Decimal(public_decimal.Decimal): + pass + + cases = [12345, 2**40, 10**100, Integer(12345), True, False, + 1.1, Float(1.1), 5e-324, float("inf"), float("nan"), + "1.2345", "1e99", "1e-99", String("1.2345"), + public_decimal.Decimal("1.2345"), Decimal("1.2345")] + for value in cases: + with self.subTest(value=value, type=type(value)): + reference_value = (reference.Decimal(str(value)) + if isinstance(value, public_decimal.Decimal) else value) + context = public_decimal.Context(prec=3, Emin=-3, Emax=3, traps=[]) + expected_context = reference.Context(prec=3, Emin=-3, Emax=3, traps=[]) + result = context.create_decimal(value) + expected = expected_context.create_decimal(reference_value) + self.assertEqual(str(result), str(expected)) + self.assertEqual(enabled_flags(context), enabled_flags(expected_context)) + + def test_localcontext_argument_binding(self): + context = public_decimal.Context(prec=9) + with public_decimal.localcontext(ctx=context, prec=3, rounding=public_decimal.ROUND_DOWN) as local: + self.assertEqual(local.prec, 3) + self.assertEqual(local.rounding, public_decimal.ROUND_DOWN) + self.assertEqual(context.prec, 9) + with public_decimal.localcontext(context, 3, public_decimal.ROUND_DOWN) as local: + self.assertEqual(local.prec, 3) + self.assertEqual(local.rounding, public_decimal.ROUND_DOWN) + with self.assertRaises(TypeError): + public_decimal.localcontext(context=context) + with self.assertRaises(TypeError): + public_decimal.setcontext(context=context) + with self.assertRaises(TypeError): + context.create_decimal(num="1") + with self.assertRaises(TypeError): + context.create_decimal_from_float(f=1.0) + self.assertEqual(context.create_decimal(), public_decimal.Decimal(0)) + + def test_cpython_argument_names(self): + for function, names in ( + (public_decimal.setcontext, ["context"]), + (public_decimal.Context.create_decimal, ["self", "num"]), + (public_decimal.Context.create_decimal_from_float, ["self", "f"]), + ): + self.assertEqual(list(inspect.signature(function).parameters), names) + + +class DecimalCompactTests(unittest.TestCase): + def check_add_subtract(self, left, right, **settings): + for operation in (operator.add, operator.sub): + results = [] + for module in (decimal, reference): + with module.localcontext(**settings) as context: + context.clear_traps() + context.clear_flags() + result = operation(module.Decimal(left), module.Decimal(right)) + results.append((repr(result), enabled_flags(context))) + self.assertEqual(results[0], results[1], (left, right, operation.__name__, settings)) + + def test_different_exponents(self): + pairs = (("1.2", "0.03"), ("1.20", "1.2"), ("1E+18", "1"), + ("9E+18", "1"), ("1E+19", "1"), + ("922337203685477580E+1", "7"), # largest compact sum + ("922337203685477580E+1", "8"), # sum overflows + ("922337203685477581E+1", "1"), # scaling overflows + ("0E+999999999999999999", "1.00"), + ("0E+999999999999999999", "0E-1999999999999999997"), + ("0E-1999999999999999997", "1"), + ("1E+999999999999999999", "1E-1999999999999999997"), + ("1E-4", "0.00010"), ("999E+2", "1")) + for rounding in ROUNDINGS: + for prec, emin, emax, clamp in ((28, -999999, 999999, 0), (19, -20, 20, 0), + (3, -3, 3, 0), (3, -3, 3, 1)): + for a, b in pairs: + for left, right in ((a, b), (b, a)): + for left_sign, right_sign in (("", ""), ("-", ""), ("", "-"), ("-", "-")): + self.check_add_subtract(left_sign + left, right_sign + right, prec=prec, + rounding=rounding, Emin=emin, Emax=emax, clamp=clamp) + + def test_random_compact_operands(self): + rng = random.Random(80152) + for i in range(300): + left = f"{rng.randrange(-(2**63 - 1), 2**63)}e{rng.randrange(-20, 21)}" + right = f"{rng.randrange(-(2**63 - 1), 2**63)}e{rng.randrange(-20, 21)}" + self.check_add_subtract(left, right, prec=rng.choice((3, 19, 28, 50)), + rounding=rng.choice(ROUNDINGS), Emin=-20, Emax=20, clamp=i % 2) + + def test_integer_conversion(self): + for coefficient in (0, 1, 9, 10, 12345, 922337203685477580, 922337203685477581, 2**63 - 1): + for exponent in (-1999999999999999997, -20, -19, -18, -5, -1, 0, 1, 5, 18, 19, 20): + for sign in ("", "-"): + text = f"{sign}{coefficient}e{exponent}" + with decimal.localcontext(prec=1, Emin=-1, Emax=1) as context: + context.clear_flags() + self.assertEqual(int(decimal.Decimal(text)), int(reference.Decimal(text)), text) + self.assertFalse(any(context.flags.values()), text) + for sign in ("", "-"): + self.assertEqual(int(decimal.Decimal(f"{sign}0E+999999999999999999")), 0) + + +class DecimalCompactOptimizationTests(unittest.TestCase): + def test_compact_formatting(self): + coefficients = {0, 1, 9, 10, 100, 2**63 - 1} + for power in range(1, 19): + coefficients.update((10**power - 1, 10**power, 10**power + 1)) + exponents = tuple(range(-26, 27)) + (-1999999999999999997, 999999999999999999) + for capitals in (0, 1): + with (decimal.localcontext(capitals=capitals) as actual_context, + reference.localcontext(capitals=capitals) as expected_context): + actual_context.clear_flags() + for coefficient in sorted(coefficients): + for sign in (0, 1): + for exponent in exponents: + valid_exponent = (min(exponent, decimal.MAX_EMAX - len(str(coefficient)) + 1) + if coefficient else exponent) + value = (sign, tuple(map(int, str(coefficient))), valid_exponent) + actual = decimal.Decimal(value) + expected = reference.Decimal(value) + self.assertEqual(str(actual), str(expected), value) + self.assertEqual(actual_context.to_sci_string(actual), + expected_context.to_sci_string(expected), value) + self.assertFalse(any(actual_context.flags.values())) + + def test_formatting_fallbacks(self): + for capitals in (0, 1): + with (decimal.localcontext(capitals=capitals) as actual_context, + reference.localcontext(capitals=capitals) as expected_context): + for text in ('Infinity', '-Infinity', 'NaN', '-NaN123', 'sNaN9', '9223372036854775808', + '123456789012345678901234567890.00', '0E-7', '-0.000000', '1.2300E+30'): + actual = decimal.Decimal(text) + expected = reference.Decimal(text) + self.assertEqual(str(actual), str(expected), text) + self.assertEqual(actual_context.to_sci_string(actual), + expected_context.to_sci_string(expected), text) + + def test_digit_count_boundaries(self): + coefficients = {0, 1, 2**63 - 1} + for power in range(1, 19): + coefficients.update((10**power - 1, 10**power, 10**power + 1)) + rng = random.Random(80152) + coefficients.update(rng.randrange(1, 2**63) for _ in range(200)) + for coefficient in coefficients: + for precision in (max(1, len(str(coefficient)) - 1), len(str(coefficient)), 28): + with decimal.localcontext(prec=precision), reference.localcontext(prec=precision): + self.assertEqual(str(+decimal.Decimal(coefficient)), str(+reference.Decimal(coefficient)), + (coefficient, precision)) + + def test_scale_overflow_boundaries(self): + for shift in range(1, 21): + limit = (2**63 - 1) // 10**shift + for coefficient in (0, 1, max(0, limit - 1), limit, limit + 1): + for sign in ('', '-'): + text = f'{sign}{coefficient}E+{shift}' + with decimal.localcontext(prec=40), reference.localcontext(prec=40): + actual = decimal.Decimal(text) + decimal.Decimal('1') + expected = reference.Decimal(text) + reference.Decimal('1') + self.assertEqual(str(actual), str(expected), text) + + +class ContextLookupOptimizationTests(unittest.TestCase): + def test_contextvar_set_reset_and_defaults(self): + value = contextvars.ContextVar('value') + other = contextvars.ContextVar('other') + self.assertEqual(value.get(1), 1) + self.assertEqual(value.get(2), 2) + outer = value.set('outer') + for _ in range(2000): + self.assertEqual(value.get(), 'outer') + changed = other.set('unrelated') + self.assertEqual(value.get(), 'outer') + inner = value.set(None) + self.assertIsNone(value.get()) + value.reset(inner) + self.assertEqual(value.get(), 'outer') + other.reset(changed) + self.assertEqual(value.get(), 'outer') + value.reset(outer) + self.assertEqual(value.get('missing'), 'missing') + with self.assertRaises(LookupError): + value.get() + + def test_missing_contextvar_does_not_retain_default(self): + class Default: + pass + + def read_default(): + default = Default() + var = contextvars.ContextVar('missing', default=default) + self.assertIs(var.get(), default) + return weakref.ref(default) + + context = contextvars.Context() + reference = context.run(read_default) + gc.collect() + self.assertIsNone(reference()) + self.assertEqual(len(context), 0) + + def test_alternating_lookup_sites(self): + a = contextvars.ContextVar('a') + b = contextvars.ContextVar('b') + context = contextvars.Context() + context.run(a.set, 1) + context.run(b.set, 2) + + def read(): + return a.get(), b.get() + + for _ in range(20000): + self.assertEqual(context.run(read), (1, 2)) + copied = context.copy() + copied.run(a.set, 3) + self.assertEqual(copied.run(read), (3, 2)) + self.assertEqual(context.run(read), (1, 2)) + context.run(b.set, None) + self.assertEqual(context.run(read), (1, None)) + + def test_lookup_cache_limit_and_variable_guard(self): + a = contextvars.ContextVar('a') + b = contextvars.ContextVar('b') + contexts = [contextvars.Context() for _ in range(5)] + for i, context in enumerate(contexts): + context.run(a.set, i) + context.run(b.set, i + 10) + + def read(var, default=None): + return var.get(default) + + # Fill three entries, then exceed the limit while keeping all maps alive. + for context, var, expected in ((contexts[0], a, 0), (contexts[0], b, 10), (contexts[1], a, 1)): + for _ in range(2000): + self.assertEqual(context.run(read, var), expected) + for i, context in enumerate(contexts): + self.assertEqual(context.run(read, a), i) + self.assertEqual(context.run(read, b), i + 10) + contexts[0].run(a.set, None) + self.assertIsNone(contexts[0].run(read, a)) + + def read_missing(default): + return a.get(default) + + empty = contextvars.Context() + for default in (1, 2, None): + self.assertEqual(empty.run(read_missing, default), default) + empty.run(a.set, 42) + self.assertEqual(empty.run(read_missing, 3), 42) + + def test_reset_does_not_retain_cached_value(self): + class Value: + pass + + var = contextvars.ContextVar('value') + + def read_and_reset(): + value = Value() + token = var.set(value) + self.assertIs(var.get(), value) + reference = weakref.ref(value) + var.reset(token) + return reference + + context = contextvars.Context() + reference = context.run(read_and_reset) + gc.collect() + self.assertIsNone(reference()) + + def test_copied_contexts_and_cache_interference(self): + a = contextvars.ContextVar('a') + b = contextvars.ContextVar('b') + original = contextvars.Context() + original.run(a.set, 'original') + for _ in range(2000): + self.assertEqual(original.run(a.get), 'original') + copied = original.copy() + copied.run(a.set, 'copy') + copied.run(b.set, 'other') + self.assertEqual(copied.run(b.get), 'other') + self.assertEqual(copied.run(a.get), 'copy') + self.assertEqual(original.run(a.get), 'original') + fresh = contextvars.Context() + self.assertEqual(fresh.run(a.get, 'missing'), 'missing') + token = fresh.run(a.set, 'fresh') + self.assertEqual(fresh.run(a.get), 'fresh') + fresh.run(a.reset, token) + self.assertEqual(fresh.run(a.get, 'missing'), 'missing') + + def test_decimal_context_transitions(self): + def calculate(): + return decimal.Decimal('1.2345') + decimal.Decimal('0') + + original = decimal.getcontext() + try: + decimal.setcontext(decimal.Context(prec=3, rounding=decimal.ROUND_DOWN)) + for _ in range(20000): + self.assertEqual(str(calculate()), '1.23') + unrelated = contextvars.ContextVar('unrelated') + token = unrelated.set(42) + self.assertEqual(str(calculate()), '1.23') + unrelated.reset(token) + self.assertEqual(str(calculate()), '1.23') + with decimal.localcontext(prec=2): + self.assertEqual(str(calculate()), '1.2') + copied = contextvars.copy_context() + self.assertEqual(str(calculate()), '1.23') + self.assertEqual(str(copied.run(calculate)), '1.2') + decimal.getcontext().prec = 4 + self.assertEqual(str(calculate()), '1.234') + decimal.setcontext(decimal.Context(prec=2, rounding=decimal.ROUND_UP)) + self.assertEqual(str(calculate()), '1.3') + fresh = contextvars.Context() + self.assertEqual(str(fresh.run(calculate)), '1.2345') + self.assertEqual(str(calculate()), '1.3') + results = [] + thread = threading.Thread(target=lambda: results.append(str(calculate()))) + thread.start() + thread.join() + self.assertEqual(results, ['1.2345']) + self.assertEqual(str(calculate()), '1.3') + finally: + decimal.setcontext(original) + + def test_rounding_changes_after_warmup(self): + actual = decimal.Context(rounding=decimal.ROUND_DOWN) + expected = reference.Context(rounding=reference.ROUND_DOWN) + quantum = decimal.Decimal('0.01') + + def quantize(value, rounding=None): + return value.quantize(quantum, rounding=rounding, context=actual) + + value = decimal.Decimal('1.235') + for _ in range(20000): + self.assertEqual(str(quantize(value)), '1.23') + for rounding in ROUNDINGS: + actual.rounding = getattr(decimal, rounding) + expected.rounding = getattr(reference, rounding) + for text in ('1.235', '-1.235', '1.234', '-1.234', '0.005', '-0.005', '1.250', '0'): + self.assertEqual(str(quantize(decimal.Decimal(text))), + str(expected.quantize(reference.Decimal(text), reference.Decimal('0.01')))) + self.assertEqual(str(quantize(value, decimal.ROUND_DOWN)), '1.23') + actual.prec = expected.prec = 2 + actual.traps[decimal.InvalidOperation] = False + expected.traps[reference.InvalidOperation] = False + self.assertEqual(str(quantize(value)), + str(expected.quantize(reference.Decimal('1.235'), reference.Decimal('0.01')))) + self.assertTrue(actual.flags[decimal.InvalidOperation]) + + +def load_tests(loader, tests, pattern): + # unittest normally sorts classes by name; keep the basic suites first. + return unittest.TestSuite(loader.loadTestsFromTestCase(test_case) for test_case in ( + DecimalInitializationTests, + DecimalConstructionTests, + DecimalStringCreationTests, + DecimalContextTests, + DecimalSignalDictTests, + DecimalNumericSlotsTests, + DecimalDunderTests, + DecimalMethodTests, + DecimalContextMethodTests, + DecimalQuantizeTests, + DecimalTranscendentalTests, + DecimalCompatibilityTests, + DecimalCompactTests, + DecimalCompactOptimizationTests, + ContextLookupOptimizationTests, + )) + + +if __name__ == '__main__': + unittest.main() From a96e89d54ac07853e558c4a5a23321fc5acad2d6 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Wed, 14 Oct 2026 18:26:46 +0200 Subject: [PATCH 23/31] Hardcode PyObject size when getting basicsize --- .../graal/python/builtins/objects/cext/capi/CExtNodes.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java index 9103e137ef..646e55a2ea 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CExtNodes.java @@ -728,7 +728,11 @@ static long doSingleContext(Object cls, CFields nativeMember, HiddenAttr managed current = getBaseClassNode.execute(inliningTarget, current); } while (current != null); // return the value from PyBaseObject - assumed to be 0 for vectorcall_offset - return isBasicsizeOrWeaklistoffset ? indexedSlotsSize + CStructs.PyObject.size() : 0L; + if (isBasicsizeOrWeaklistoffset) { + // Hardcode sizeof(PyObject) because the C API might not be loaded yet + return 16 + indexedSlotsSize; + } + return 0L; } } From 3c8f93e15d8c057d51d91b676f73ce88ae711343 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Wed, 14 Oct 2026 18:29:36 +0200 Subject: [PATCH 24/31] Fix a few math issues --- .../src/tests/test_decimal.py | 53 +++++++++++++++++++ .../objects/decimal/DecimalArithmetic.java | 49 ++++++++++++----- 2 files changed, 88 insertions(+), 14 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py b/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py index 3fd5dc30fc..264121d614 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py @@ -1044,6 +1044,59 @@ def test_power_working_precision(self): self.assertEqual(enabled_flags(context), ["Inexact", "Overflow", "Rounded"]) self.assertEqual(repr(result), "Decimal('-" + "9" * 84 + "." + "9" * 16 + "')") + def test_max_precision_exact_division(self): + cases = (("1", "1", "1"), ("1", "2", "0.5"), ("1.00", "2", "0.50"), + ("1", "40", "0.025"), ("3", "6", "0.5"), ("1", "1024", "0.0009765625"), + ("1E+100", "2", "5E+99"), ("-1", "2", "-0.5"), ("1", "-2", "-0.5")) + for bounds in (dict(Emin=decimal.MIN_EMIN, Emax=decimal.MAX_EMAX), dict(clamp=1)): + with decimal.localcontext(prec=decimal.MAX_PREC, **bounds) as context: + context.clear_flags() + for a, b, expected in cases: + a, b = decimal.Decimal(a), decimal.Decimal(b) + self.assertEqual(str(a / b), expected) + self.assertEqual(str(context.divide(a, b)), expected) + if not bounds.get("clamp"): + self.assertEqual(str(decimal.Decimal(2) ** -1), "0.5") + self.assertEqual(str(decimal.Decimal(2) ** -10), "0.0009765625") + self.assertEqual(enabled_flags(context), []) + with self.assertRaises(MemoryError): + context.divide(decimal.Decimal(1), decimal.Decimal(3)) + with decimal.localcontext(prec=decimal.MAX_PREC, Emin=0, Emax=99) as context: + # The bounded retry is accepted only when it raises no signals, even for + # a terminating quotient that would be subnormal in the retry context. + with self.assertRaises(MemoryError): + context.divide(decimal.Decimal(1), decimal.Decimal(2)) + + def test_power_of_one_clamping(self): + # CPython's powers-of-one shortcut preserves the quantum without finalization. + cases = (("1", "3", "1", []), ("-1", "3", "-1", []), + ("1.0", "1", "1.0", []), ("-1.0", "1", "-1.0", []), + ("1.0", "-1", "1", []), ("-1.0", "-1", "-1", []), + ("1.00", "2", "1.00", ["Rounded"])) + for rounding in ROUNDINGS: + with decimal.localcontext(prec=3, Emin=-9, Emax=1, clamp=1, rounding=rounding) as context: + context.traps[decimal.Clamped] = True + for a, b, expected, flags in cases: + a, b = decimal.Decimal(a), decimal.Decimal(b) + for operation in (operator.pow, context.power): + context.clear_flags() + self.assertEqual(str(operation(a, b)), expected) + self.assertEqual(enabled_flags(context), flags) + + def test_power_of_one_preferred_exponent(self): + # libmpdec rounds the preferred digit count in the context before limiting it. + for rounding in ROUNDINGS: + with decimal.localcontext(prec=19, Emin=0, Emax=0, rounding=rounding) as context: + context.clear_flags() + a, b = decimal.Decimal("1.0"), decimal.Decimal("15.0") + self.assertEqual(str(a ** b), "1." + "0" * 18) + self.assertEqual(enabled_flags(context), ["Rounded"]) + context.traps[decimal.Rounded] = True + context.clear_flags() + with self.assertRaises(decimal.Rounded): + context.power(a, b) + self.assertEqual(enabled_flags(context), ["Rounded"]) + def test_large_context_and_zero_exponents(self): with decimal.localcontext(prec=decimal.MAX_PREC, Emin=decimal.MIN_EMIN, Emax=decimal.MAX_EMAX) as context: context.clear_flags() diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java index 34c7f1e804..bf2588bc2c 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java @@ -1243,6 +1243,27 @@ private Value divide(Value a, Value b) { if (isZero(a)) { return finish(zero(negative, idealExponent)); } + int initialStatus = status; + try { + return divideFinite(a, b, negative, idealExponent); + } catch (ArithmeticException e) { + // Like mpd_qdiv, retry at a bounded precision when the requested coefficient + // cannot be represented. An exact quotient needs at most this many digits. + long exactPrecision = digits(a.coefficient()) + 4L * digits(b.coefficient()); + if (exactPrecision >= precision) { + throw e; + } + DecimalArithmetic exact = new DecimalArithmetic(exactPrecision, emin, emax, rounding, clamp); + Value result = exact.divideFinite(a, b, negative, idealExponent); + if (exact.status != 0) { + throw e; + } + status = initialStatus; + return result; + } + } + + private Value divideFinite(Value a, Value b, boolean negative, long idealExponent) { long shift = digits(b.coefficient()) - digits(a.coefficient()) + precision + 1; BigInteger numerator = a.coefficient(); BigInteger denominator = b.coefficient(); @@ -1437,27 +1458,27 @@ private Value power(Value a, Value b) { if (!integerExponent) { status |= INEXACT | ROUNDED; exponent = 1 - precision; - } else if (b.negative() || a.exponent() == 0) { + } else if (b.negative()) { exponent = 0; } else { - Value parts = integralParts(b); - BigInteger multiplier = parts.coefficient(); - // Only need to distinguish the ideal quantum from the precision floor. - if (adjusted(b) >= digits(precision)) { - multiplier = BigInteger.valueOf(precision); + // _qcheck_pow_one calculates the preferred digit count in the user's + // context, but discards the multiplication's rounding and range signals. + DecimalArithmetic work = new DecimalArithmetic(precision, emin, emax, rounding, clamp); + Value preferred = work.multiply(b, DecimalUtils.fromInteger(BigInteger.valueOf(-a.exponent()))); + long shift; + if (!integral(preferred) || !isZero(preferred) && adjusted(preferred) > 18) { + shift = Long.MAX_VALUE; } else { - multiplier = multiplier.multiply(pow10(parts.exponent())); + shift = rescale(preferred, 0, DecimalRounding.ROUND_DOWN).coefficient().min(BigInteger.valueOf(Long.MAX_VALUE)).longValue(); } - BigInteger ideal = BigInteger.valueOf(a.exponent()).multiply(multiplier); - long floor = 1 - precision; - if (ideal.compareTo(BigInteger.valueOf(floor)) < 0) { + if (shift > precision - 1) { status |= ROUNDED; - exponent = floor; - } else { - exponent = ideal.longValueExact(); + shift = precision - 1; } + exponent = -shift; } - return finish(finite(negative, pow10(-exponent), exponent)); + // Powers of one return directly in libmpdec, without context clamping. + return finite(negative, pow10(-exponent), exponent); } if (b.kind() == Kind.INFINITY) { if ((comparedToOne > 0) != b.negative()) { From f3837b3961ef7c987b35db38c660f58b25194a38 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Fri, 16 Oct 2026 12:44:45 +0200 Subject: [PATCH 25/31] Few math performance improvements --- .../src/tests/test_decimal.py | 57 +++++ .../objects/decimal/DecimalArithmetic.java | 44 +++- .../objects/decimal/DecimalNodes.java | 198 ++++++++++++++++-- 3 files changed, 269 insertions(+), 30 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py b/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py index 264121d614..776a7da89e 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py @@ -896,6 +896,54 @@ def test_random_arithmetic(self): self.check_binary(left, right, operation, prec=rng.choice((1, 3, 7, 28, 50)), rounding=rng.choice(ROUNDINGS), clamp=i % 2) + def test_zero_remainder_with_large_exponent(self): + for exponent in (2147483648, decimal.MAX_EMAX, decimal.MIN_ETINY): + for sign in ('', '-'): + for right in ('1', '-3e-2', '7e+2', '9223372036854775808'): + for operation in (operator.floordiv, operator.mod, divmod): + self.check_binary(f'{sign}0e{exponent}', right, operation, + prec=3, emin=-3, emax=3, clamp=1) + + def test_compact_context_finalization(self): + pairs = (('123456789', '9'), ('999', '1'), ('9995e-4', '0'), + ('5000000000000000000e-19', '1'), ('9223372036854775807e-19', '1'), + ('10e-5', '1e-5'), ('9995e-7', '1e-7'), ('0e+20', '0e-20')) + settings = ((9, -20, 20, 0), (1, 0, 0, 0), (3, -3, 3, 0), + (3, -3, 3, 1), (28, -5, 5, 1)) + for rounding in ROUNDINGS: + for prec, emin, emax, clamp in settings: + for a, b in pairs: + for left_sign, right_sign in (('', ''), ('-', ''), ('', '-'), ('-', '-')): + for operation in (operator.add, operator.sub, operator.mul): + self.check_binary(left_sign + a, right_sign + b, operation, prec=prec, + rounding=rounding, emin=emin, emax=emax, clamp=clamp) + + def test_compact_division_and_remainder(self): + pairs = (('123456789', '97'), ('100', '2'), ('100.00', '2'), ('12.3', '0.2'), + ('99999', '3'), ('9223372036854775807', '7'), ('1e-8', '3e-8'), + ('123e-8', '122e-8'), ('9e+18', '1'), ('1', '9e+18')) + for rounding in ROUNDINGS: + for prec, emin, emax, clamp in ((28, -999999, 999999, 0), (3, -3, 1, 1), (1, 0, 0, 0)): + for a, b in pairs: + for left_sign, right_sign in (('', ''), ('-', ''), ('', '-'), ('-', '-')): + for operation in (operator.truediv, operator.floordiv, operator.mod, divmod): + self.check_binary(left_sign + a, right_sign + b, operation, prec=prec, + rounding=rounding, emin=emin, emax=emax, clamp=clamp) + + def test_compact_divmod_combines_signals_before_trapping(self): + with decimal.localcontext(prec=3, Emin=-3, Emax=1, clamp=1) as context: + context.clear_traps() + context.clear_flags() + context.traps[decimal.Inexact] = True + with self.assertRaises(decimal.Inexact): + divmod(decimal.Decimal('123e-8'), decimal.Decimal('122e-8')) + self.assertEqual(enabled_flags(context), ['Clamped', 'Inexact', 'Rounded', 'Subnormal', 'Underflow']) + + def test_exact_division_with_many_trailing_zeros(self): + for prec in (1, 3, 28, 2048): + for a, b in (('1', '2'), ('1.00', '40'), ('3', '6'), ('1', '1024')): + self.check_binary(a, b, operator.truediv, prec=prec, emin=-5000, emax=5000) + def test_coefficient_digit_boundaries(self): # Check both sides of decimal and binary magnitude boundaries, including the compact # coefficient limit and the small-power cache limit. Precision makes a one-digit error @@ -1473,6 +1521,15 @@ def test_rounding_and_exponent_boundaries(self): for name in ("next_toward", "remainder_near", "max", "min", "max_mag", "min_mag"): self.check_method(name, value, other, prec=3, Emin=-3, Emax=4, rounding=rounding, clamp=clamp) + def test_normalize_many_trailing_zeros(self): + for coefficient in ('1', '16', '125', '999'): + for zeros in (1, 3, 18, 128, 1024): + value = coefficient + '0' * zeros + f'e-{zeros}' + for clamp in (0, 1): + self.check_method('normalize', value, prec=zeros + 5, Emin=-2000, Emax=2000, clamp=clamp) + for coefficient in ('2000', '1600', '2500', '8000'): + self.check_method('normalize', coefficient + 'e-3', prec=5, Emin=-3, Emax=0, clamp=1) + def test_digit_operations(self): for prec in (1, 3, 9, 28): for value in ("0", "-0", "1", "101", "1100101001101", "11111111111111111111111111111", diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java index bf2588bc2c..cf6fb3363c 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalArithmetic.java @@ -795,14 +795,39 @@ private Value normalize(Value a) { if (isZero(result)) { return zero(result.negative(), 0); } - long exponent = result.exponent(); long maximum = clamp == 0 ? emax : emax - precision + 1; - BigInteger coefficient = result.coefficient(); - while (exponent < maximum && coefficient.remainder(BigInteger.TEN).signum() == 0) { - coefficient = coefficient.divide(BigInteger.TEN); - exponent++; + return stripTrailingZeros(result, maximum); + } + + /** Remove a bounded number of trailing decimal zeros with one coefficient division. */ + private static Value stripTrailingZeros(Value operand, long maximumExponent) { + BigInteger coefficient = operand.coefficient(); + // Every factor of ten requires a factor of two. This bounds both the search and + // the size of its powers of ten by the coefficient, even for huge exponent limits. + long high = Math.min(maximumExponent - operand.exponent(), coefficient.getLowestSetBit()); + if (high <= 0 || coefficient.remainder(BigInteger.TEN).signum() != 0) { + return operand; } - return finite(result.negative(), coefficient, exponent); + long low = 1; + BigInteger divisor = BigInteger.TEN; + BigInteger candidate = pow10(high); + if (coefficient.remainder(candidate).signum() == 0) { + low = high; + divisor = candidate; + } else { + high--; + while (low < high) { + long middle = low + (high - low + 1) / 2; + candidate = pow10(middle); + if (coefficient.remainder(candidate).signum() == 0) { + low = middle; + divisor = candidate; + } else { + high = middle - 1; + } + } + } + return finite(operand.negative(), coefficient.divide(divisor), operand.exponent() + low); } private Value integral(Value a, boolean exact) { @@ -1281,10 +1306,7 @@ private Value divideFinite(Value a, Value b, boolean negative, long idealExponen coefficient = coefficient.add(BigInteger.ONE); } } else { - while (exponent < idealExponent && coefficient.remainder(BigInteger.TEN).signum() == 0) { - coefficient = coefficient.divide(BigInteger.TEN); - exponent++; - } + return finish(stripTrailingZeros(finite(negative, coefficient, exponent), idealExponent)); } return finish(finite(negative, coefficient, exponent)); } @@ -1321,7 +1343,7 @@ private ResultWithRemainder divmod(Value a, Value b, boolean quotientNeeded, boo BigInteger r; if (isZero(a) || expdiff < 0 && compareMagnitude(a, b) < 0) { q = BigInteger.ZERO; - if (remainderNeeded) { + if (remainderNeeded && !isZero(a)) { r = a.coefficient().multiply(pow10(a.exponent() - exponent)); } else { r = BigInteger.ZERO; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java index 46f5d87c28..52e6a893d5 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java @@ -109,6 +109,9 @@ public final class DecimalNodes { private static final int ROUNDED = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Rounded); private static final int SUBNORMAL = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Subnormal); private static final int CLAMPED = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Clamped); + private static final int UNDERFLOW = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Underflow); + private static final int OVERFLOW = DecimalContextUtils.conditionMask(DecimalExceptionTypes.Overflow); + private static final int DIVISION_IMPOSSIBLE = DecimalContextUtils.conditionMask(DecimalExceptionTypes.DivisionImpossible); private static final long HALF_TEN_TO_NINETEEN = 5000000000000000000L; private DecimalNodes() { @@ -644,7 +647,7 @@ static PDecimal plus(VirtualFrame frame, Node inliningTarget, PDecimal self, static PDecimal doPlus(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, InlinedBranchProfile nonCompactProfile, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { PythonLanguage language = PythonLanguage.get(inliningTarget); - PDecimal fast = compactUnary(language, self, context, self.isNegative()); + PDecimal fast = compactUnary(frame, language, self, context, self.isNegative(), raiseSignal); if (fast != null) { return fast; } @@ -678,7 +681,7 @@ static PDecimal minus(VirtualFrame frame, Node inliningTarget, PDecimal self, static PDecimal doMinus(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, InlinedBranchProfile nonCompactProfile, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { PythonLanguage language = PythonLanguage.get(inliningTarget); - PDecimal fast = compactUnary(language, self, context, !self.isNegative()); + PDecimal fast = compactUnary(frame, language, self, context, !self.isNegative(), raiseSignal); if (fast != null) { return fast; } @@ -712,7 +715,7 @@ static PDecimal abs(VirtualFrame frame, Node inliningTarget, PDecimal self, static PDecimal doAbs(VirtualFrame frame, Node inliningTarget, PDecimal self, PDecimalContext context, InlinedBranchProfile nonCompactProfile, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { PythonLanguage language = PythonLanguage.get(inliningTarget); - PDecimal fast = compactUnary(language, self, context, false); + PDecimal fast = compactUnary(frame, language, self, context, false, raiseSignal); if (fast != null) { return fast; } @@ -754,7 +757,7 @@ static Object add(VirtualFrame frame, Node inliningTarget, Object left, Object r static PDecimal doAdd(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimalContext context, InlinedBranchProfile nonCompactProfile, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { PythonLanguage language = PythonLanguage.get(inliningTarget); - PDecimal fast = compactAdd(language, a, b, context, b.isNegative()); + PDecimal fast = compactAdd(frame, language, a, b, context, b.isNegative(), raiseSignal); if (fast != null) { return fast; } @@ -795,7 +798,7 @@ static Object subtract(VirtualFrame frame, Node inliningTarget, Object left, Obj static PDecimal doSubtract(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimalContext context, InlinedBranchProfile nonCompactProfile, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { PythonLanguage language = PythonLanguage.get(inliningTarget); - PDecimal fast = compactAdd(language, a, b, context, !b.isNegative()); + PDecimal fast = compactAdd(frame, language, a, b, context, !b.isNegative(), raiseSignal); if (fast != null) { return fast; } @@ -836,7 +839,7 @@ static Object multiply(VirtualFrame frame, Node inliningTarget, Object left, Obj static PDecimal doMultiply(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimalContext context, InlinedBranchProfile nonCompactProfile, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { PythonLanguage language = PythonLanguage.get(inliningTarget); - PDecimal fast = compactMultiply(language, a, b, context); + PDecimal fast = compactMultiply(frame, language, a, b, context, raiseSignal); if (fast != null) { return fast; } @@ -874,6 +877,13 @@ static Object divide(VirtualFrame frame, Node inliningTarget, Object left, Objec } static PDecimal doDivide(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { + if (isCompactFinite(a) && isCompactFinite(b) && b.getCompactCoefficient() != 0 && a.getCompactCoefficient() % b.getCompactCoefficient() == 0) { + PDecimal fast = finishCompact(frame, PythonLanguage.get(inliningTarget), a.isNegative() ^ b.isNegative(), + a.getCompactCoefficient() / b.getCompactCoefficient(), a.getExponent() - b.getExponent(), context, raiseSignal); + if (fast != null) { + return fast; + } + } DecimalArithmetic.Result result; try { result = DecimalArithmetic.divide(a, b, context); @@ -907,6 +917,10 @@ static Object divideInt(VirtualFrame frame, Node inliningTarget, Object left, Ob } static PDecimal doDivideInt(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { + Object fast = compactDivmod(frame, PythonLanguage.get(inliningTarget), a, b, context, true, false, raiseSignal); + if (fast != null) { + return (PDecimal) fast; + } DecimalArithmetic.Result result; try { result = DecimalArithmetic.divideInt(a, b, context); @@ -940,6 +954,10 @@ static Object remainder(VirtualFrame frame, Node inliningTarget, Object left, Ob } static PDecimal doRemainder(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { + Object fast = compactDivmod(frame, PythonLanguage.get(inliningTarget), a, b, context, false, true, raiseSignal); + if (fast != null) { + return (PDecimal) fast; + } DecimalArithmetic.Result result; try { result = DecimalArithmetic.remainder(a, b, context); @@ -973,6 +991,10 @@ static Object divmod(VirtualFrame frame, Node inliningTarget, Object left, Objec } static Object doDivmod(VirtualFrame frame, Node inliningTarget, PDecimal a, PDecimal b, PDecimalContext context, RaiseSignalNode raiseSignal, PRaiseNode raiseNode) { + Object fast = compactDivmod(frame, PythonLanguage.get(inliningTarget), a, b, context, true, true, raiseSignal); + if (fast != null) { + return fast; + } DecimalArithmetic.ResultWithRemainder result; try { result = DecimalArithmetic.divmod(a, b, context); @@ -996,14 +1018,158 @@ static boolean isCompactFinite(PDecimal value) { return value.getKind() == Kind.FINITE && value.isCompact(); } - private static PDecimal compactUnary(PythonLanguage language, PDecimal self, PDecimalContext context, boolean negative) { - if (!isCompactFinite(self) || !DecimalArithmetic.fitsContext(self.getCompactCoefficient() == 0, self.getExponent(), DecimalArithmetic.digits(self.getCompactCoefficient()), context)) { + private static PDecimal compactUnary(VirtualFrame frame, PythonLanguage language, PDecimal self, PDecimalContext context, boolean negative, RaiseSignalNode raiseSignal) { + if (!isCompactFinite(self)) { return null; } if (self.getCompactCoefficient() == 0 && context.getRounding() != DecimalRounding.ROUND_FLOOR) { negative = false; } - return compact(language, negative, self.getCompactCoefficient(), self.getExponent()); + return finishCompact(frame, language, negative, self.getCompactCoefficient(), self.getExponent(), context, raiseSignal); + } + + private record CompactResult(long coefficient, long exponent, Kind kind, int status) { + } + + private static PDecimal createCompact(PythonLanguage language, boolean negative, CompactResult result) { + return PFactory.createDecimal(language, negative, result.coefficient(), result.exponent(), result.kind()); + } + + private static PDecimal finishCompact(VirtualFrame frame, PythonLanguage language, boolean negative, long coefficient, long exponent, + PDecimalContext context, RaiseSignalNode raiseSignal) { + if (DecimalArithmetic.fitsContext(coefficient == 0, exponent, DecimalArithmetic.digits(coefficient), context)) { + return compact(language, negative, coefficient, exponent); + } + CompactResult result = finalizeCompact(negative, coefficient, exponent, context); + if (result == null) { + return null; + } + DecimalContextUtils.addStatus(frame, context, result.status(), raiseSignal); + return createCompact(language, negative, result); + } + + /** Finalize without changing the context, so divmod can combine both results' signals. */ + private static CompactResult finalizeCompact(boolean negative, long coefficient, long exponent, PDecimalContext context) { + long precision = context.getPrecision(); + long emin = context.getEmin(); + long emax = context.getEmax(); + long etiny = emin - precision + 1; + long etop = emax - precision + 1; + if (coefficient == 0) { + long maximum = context.getClamp() == 0 ? emax : etop; + long clamped = Math.min(Math.max(exponent, etiny), maximum); + return new CompactResult(0, clamped, Kind.FINITE, clamped == exponent ? 0 : CLAMPED); + } + int length = DecimalArithmetic.digits(coefficient); + long adjusted = exponent + length - 1; + if (adjusted > emax) { + return compactOverflow(negative, context, 0); + } + boolean subnormal = adjusted < emin; + int status = subnormal ? SUBNORMAL : 0; + long targetExponent = Math.max(exponent + length - precision, etiny); + if (exponent < targetExponent) { + long places = targetExponent - exponent; + long divisor = compactDivisor(places); + long quotient = 0; + long remainder = coefficient; + if (divisor != 0) { + quotient = coefficient / divisor; + remainder = coefficient % divisor; + } + status |= ROUNDED; + if (remainder != 0) { + status |= INEXACT; + if (subnormal) { + status |= UNDERFLOW; + } + } + if (incrementCompact(negative, quotient, remainder, divisor, places, context.getRounding())) { + quotient++; + } + coefficient = quotient; + exponent = targetExponent; + length = DecimalArithmetic.digits(coefficient); + if (length > precision) { + coefficient /= 10; + exponent++; + length--; + } + if (exponent + length - 1 > emax) { + return compactOverflow(negative, context, status); + } + if (coefficient == 0) { + status |= CLAMPED; + } + } + if (context.getClamp() != 0 && exponent > etop) { + coefficient = scaleCompactCoefficient(coefficient, exponent - etop); + if (coefficient < 0) { + return null; + } + exponent = etop; + status |= CLAMPED; + } + return new CompactResult(coefficient, exponent, Kind.FINITE, status); + } + + private static CompactResult compactOverflow(boolean negative, PDecimalContext context, int status) { + status |= OVERFLOW | INEXACT | ROUNDED; + boolean finite = switch (context.getRounding()) { + case ROUND_DOWN, ROUND_05UP -> true; + case ROUND_CEILING -> negative; + case ROUND_FLOOR -> !negative; + case ROUND_UP, ROUND_HALF_UP, ROUND_HALF_DOWN, ROUND_HALF_EVEN -> false; + }; + if (finite) { + long precision = context.getPrecision(); + if (precision > 18) { + return null; + } + return new CompactResult(DecimalUtils.LONG_POWERS_OF_TEN[(int) precision] - 1, context.getEmax() - precision + 1, Kind.FINITE, status); + } + return new CompactResult(0, 0, Kind.INFINITY, status); + } + + private static Object compactDivmod(VirtualFrame frame, PythonLanguage language, PDecimal a, PDecimal b, PDecimalContext context, + boolean quotientNeeded, boolean remainderNeeded, RaiseSignalNode raiseSignal) { + if (!isCompactFinite(a) || !isCompactFinite(b) || b.getCompactCoefficient() == 0) { + return null; + } + long exponent = Math.min(a.getExponent(), b.getExponent()); + long quotient = 0; + long remainder = 0; + if (a.getCompactCoefficient() != 0) { + long numerator = scaleCompactCoefficient(a.getCompactCoefficient(), a.getExponent() - exponent); + long denominator = scaleCompactCoefficient(b.getCompactCoefficient(), b.getExponent() - exponent); + if (numerator < 0 || denominator < 0) { + return null; + } + quotient = numerator / denominator; + if (remainderNeeded) { + remainder = numerator % denominator; + } + } + if (DecimalArithmetic.digits(quotient) > context.getPrecision()) { + DecimalContextUtils.addStatus(frame, context, DIVISION_IMPOSSIBLE, raiseSignal); + PDecimal nan = PFactory.createDecimal(language, false, 0L, 0, Kind.NAN); + return quotientNeeded && remainderNeeded ? PFactory.createTuple(language, new Object[]{nan, nan}) : nan; + } + boolean negative = a.isNegative() ^ b.isNegative(); + CompactResult q = quotientNeeded ? finalizeCompact(negative, quotient, 0, context) : null; + CompactResult r = remainderNeeded ? finalizeCompact(a.isNegative(), remainder, exponent, context) : null; + if (quotientNeeded && q == null || remainderNeeded && r == null) { + return null; + } + DecimalContextUtils.addStatus(frame, context, (q == null ? 0 : q.status()) | (r == null ? 0 : r.status()), raiseSignal); + if (!quotientNeeded) { + return createCompact(language, a.isNegative(), r); + } + PDecimal result = createCompact(language, negative, q); + if (!remainderNeeded) { + return result; + } + return PFactory.createTuple(language, new Object[]{result, createCompact(language, a.isNegative(), r)}); } /** Scale a nonnegative coefficient, or return -1 if it no longer fits a signed long. */ @@ -1023,7 +1189,7 @@ static long scaleCompactCoefficient(long coefficient, long shift) { return coefficient * DecimalUtils.LONG_POWERS_OF_TEN[index]; } - private static PDecimal compactAdd(PythonLanguage language, PDecimal a, PDecimal b, PDecimalContext context, boolean rightNegative) { + private static PDecimal compactAdd(VirtualFrame frame, PythonLanguage language, PDecimal a, PDecimal b, PDecimalContext context, boolean rightNegative, RaiseSignalNode raiseSignal) { if (!isCompactFinite(a) || !isCompactFinite(b)) { return null; } @@ -1066,13 +1232,10 @@ private static PDecimal compactAdd(PythonLanguage language, PDecimal a, PDecimal } else { negative = sum < 0; } - if (DecimalArithmetic.fitsContext(coefficient == 0, exponent, DecimalArithmetic.digits(coefficient), context)) { - return compact(language, negative, coefficient, exponent); - } - return null; + return finishCompact(frame, language, negative, coefficient, exponent, context, raiseSignal); } - private static PDecimal compactMultiply(PythonLanguage language, PDecimal a, PDecimal b, PDecimalContext context) { + private static PDecimal compactMultiply(VirtualFrame frame, PythonLanguage language, PDecimal a, PDecimal b, PDecimalContext context, RaiseSignalNode raiseSignal) { if (!isCompactFinite(a) || !isCompactFinite(b)) { return null; } @@ -1084,10 +1247,7 @@ private static PDecimal compactMultiply(PythonLanguage language, PDecimal a, PDe } long exponent = a.getExponent() + b.getExponent(); boolean negative = a.isNegative() ^ b.isNegative(); - if (DecimalArithmetic.fitsContext(coefficient == 0, exponent, DecimalArithmetic.digits(coefficient), context)) { - return compact(language, negative, coefficient, exponent); - } - return null; + return finishCompact(frame, language, negative, coefficient, exponent, context, raiseSignal); } /** Apply quantize's precision, exponent limits and flags without BigInteger arithmetic. */ From f20e0094b7ea032626d7e67a2b3d9d52ba588ad3 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Fri, 16 Oct 2026 14:49:28 +0200 Subject: [PATCH 26/31] Fix decimal grouping in C locale in float and decimal formatting --- .../src/tests/test_decimal.py | 26 ++++++++++++++ .../src/tests/test_locale.py | 35 ++++++++++++++++++- .../modules/LocaleModuleBuiltins.java | 8 ++--- .../objects/decimal/DecimalFormatting.java | 14 +++++--- .../runtime/formatting/FloatFormatter.java | 2 +- .../runtime/formatting/InternalFormat.java | 3 +- .../python/runtime/locale/LocaleUtils.java | 14 +++++++- 7 files changed, 90 insertions(+), 12 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py b/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py index 776a7da89e..a8a696765a 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_decimal.py @@ -44,6 +44,7 @@ import decimal as public_decimal import gc import inspect +import locale import math import numbers import operator @@ -1369,6 +1370,21 @@ def test_format(self): spec = f"{rng.randrange(5, 50)}.{rng.randrange(0, 20)}{rng.choice('eEfFgG%')}" self.assertEqual(format(decimal.Decimal(text), spec), format(reference.Decimal(text), spec), (text, spec)) + def test_format_c_locale(self): + saved_locale = locale.setlocale(locale.LC_NUMERIC) + try: + locale.setlocale(locale.LC_NUMERIC, "C") + value = decimal.Decimal("1234567.89") + for spec, expected in (("n", "1234567.89"), ("020n", "00000000001234567.89"), + (".8n", "1234567.9"), (",f", "1,234,567.89")): + self.assertEqual(format(value, spec), expected, spec) + self.assertEqual(value.__format__("n", {"decimal_point": "@", "thousands_sep": "_", "grouping": "\3\2\0"}), + "12_34_567@89") + self.assertEqual(value.__format__("n", {"thousands_sep": "_"}), "1234567.89") + self.assertEqual(value.__format__("n", {"grouping": "\3\0"}), "1234567.89") + finally: + locale.setlocale(locale.LC_NUMERIC, saved_locale) + def test_format_errors_and_overrides(self): value = decimal.Decimal("1234567.89") self.assertEqual(value.__format__("n", {"decimal_point": "@", "thousands_sep": "_", "grouping": "\3\2\0"}), @@ -1393,6 +1409,16 @@ def test_format_errors_and_overrides(self): with self.assertRaises(ValueError): format(value, f"{decimal.MAX_PREC + 1}f") + def test_format_override_defaults(self): + value = decimal.Decimal("1234567.89") + for spec in ("", "f", "g", "e", "%"): + for override in ({}, {"thousands_sep": "_"}, {"grouping": "\3\0"}): + with self.assertRaises(ValueError): + value.__format__(spec, override) + self.assertEqual(value.__format__("f", {"decimal_point": "@", "thousands_sep": "_"}), "1234567@89") + self.assertEqual(value.__format__(",f", {}), "1,234,567.89") + self.assertEqual(value.__format__(",f", {"thousands_sep": "_"}), "1_234_567.89") + def test_rational_attribute_validation(self): class Index: def __index__(self): diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_locale.py b/graalpython/com.oracle.graal.python.test/src/tests/test_locale.py index f7fd51af24..9d58cb3af0 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_locale.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_locale.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 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 @@ -69,3 +69,36 @@ def test_get_setlocale(): assert locale.getlocale(0) == new_locale finally: locale.setlocale(0, current_locale) + + +def test_numeric_locale_formatting(): + import locale + from decimal import Decimal + + saved_locale = locale.setlocale(locale.LC_NUMERIC) + try: + for name, separator, point in (("C", "", "."), ("en_US.UTF-8", ",", "."), + ("de_DE.UTF-8", ".", ","), ("C", "", ".")): + try: + locale.setlocale(locale.LC_NUMERIC, name) + except locale.Error: + if name == "C": + raise + continue # These locales are not installed on every test host. + conv = locale.localeconv() + assert conv["decimal_point"] == point + assert conv["thousands_sep"] == separator + if name == "C": + assert conv["grouping"] == [] + integer = separator.join(("1", "234", "567")) + number = integer + point + "89" + for sign in ("", "-"): + assert format(int(sign + "1234567"), "n") == sign + integer + assert format(float(sign + "1234567.89"), ".9n") == sign + number + assert format(Decimal(sign + "1234567.89"), "n") == sign + number + assert format(complex(float(sign + "1234567.89"), 1234567.89), ".9n") == sign + number + "+" + number + "j" + assert locale.format_string("%.2f", 1234567.89, grouping=True) == number + assert format(1234567.89, ",.2f") == "1,234,567.89" + assert format(1234567, ",d") == "1,234,567" + finally: + locale.setlocale(locale.LC_NUMERIC, saved_locale) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/LocaleModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/LocaleModuleBuiltins.java index 05acd0da36..9c17091882 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/LocaleModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/LocaleModuleBuiltins.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -124,11 +124,11 @@ static PDict localeconv() { // LC_NUMERIC Locale numericLocale = currentPythonLocale.category(LC_NUMERIC); - NumberFormat numericLocaleNumFormat = NumberFormat.getInstance(numericLocale); + NumberFormat numericLocaleNumFormat = LocaleUtils.getNumberFormat(numericLocale); DecimalFormatSymbols decimalFormatSymbols = getDecimalFormatSymbols(numericLocale, numericLocaleNumFormat); dict.put("decimal_point", TruffleString.fromCodePointUncached(decimalFormatSymbols.getDecimalSeparator(), TS_ENCODING)); - dict.put("thousands_sep", TruffleString.fromCodePointUncached(decimalFormatSymbols.getGroupingSeparator(), TS_ENCODING)); + dict.put("thousands_sep", toTruffleStringUncached(numericLocaleNumFormat.isGroupingUsed() ? String.valueOf(decimalFormatSymbols.getGroupingSeparator()) : "")); dict.put("grouping", getDecimalFormatGrouping(language, numericLocaleNumFormat)); // LC_MONETARY @@ -162,7 +162,7 @@ private static DecimalFormatSymbols getDecimalFormatSymbols(Locale locale, Numbe } private static PList getDecimalFormatGrouping(PythonLanguage language, NumberFormat numberFormat) { - if (numberFormat instanceof DecimalFormat decimalFormat) { + if (numberFormat instanceof DecimalFormat decimalFormat && decimalFormat.isGroupingUsed() && decimalFormat.getGroupingSize() > 0) { // TODO: this does not support groupings with variable size groups like in India // Possible approach: decimalFormat.toPattern() gives a generic pattern (e.g., // #,#00.0#) that would have to be parsed to extract the group sizes from it diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalFormatting.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalFormatting.java index b4e081eb9c..072041b34e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalFormatting.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalFormatting.java @@ -67,6 +67,7 @@ import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.util.CastToJavaStringNode; import com.oracle.graal.python.runtime.PythonContext; +import com.oracle.graal.python.runtime.locale.LocaleUtils; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.strings.TruffleString; @@ -112,20 +113,22 @@ static String format(PDecimal self, String format, Object override, PDecimalCont if (precision == 0 && (lowerType == 'g' || lowerType == 'n')) { precision = 1; } - String dot = "."; String separator = groupOrDefault(match, "sep", ""); - String grouping = "\003\000"; + // libmpdec leaves locale settings empty unless grouping or 'n' is requested. + String dot = separator.isEmpty() ? "" : "."; + String grouping = separator.isEmpty() ? "" : "\003\000"; if (lowerType == 'n') { if (!separator.isEmpty()) { throw PRaiseNode.getUncached().raise(null, ValueError, ErrorMessages.DECIMAL_FORMAT_INVALID); } Locale locale = PythonContext.get(null).getCurrentLocale().category(LC_NUMERIC); - NumberFormat numberFormat = NumberFormat.getInstance(locale); + NumberFormat numberFormat = LocaleUtils.getNumberFormat(locale); DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale); dot = String.valueOf(symbols.getDecimalSeparator()); - separator = String.valueOf(symbols.getGroupingSeparator()); + separator = ""; grouping = ""; if (numberFormat instanceof DecimalFormat df && df.isGroupingUsed()) { + separator = String.valueOf(symbols.getGroupingSeparator()); grouping = String.valueOf((char) df.getGroupingSize()) + '\000'; } type = type == 'N' ? 'G' : 'g'; @@ -143,6 +146,9 @@ static String format(PDecimal self, String format, Object override, PDecimalCont throw PRaiseNode.getUncached().raise(null, ValueError, ErrorMessages.DECIMAL_FORMAT_OVERRIDE); } } + if (dot.isEmpty()) { + dot = "."; + } if (width > DecimalUtils.MAX_PREC || self.getKind() == Kind.FINITE && precision > DecimalUtils.MAX_PREC) { throw PRaiseNode.getUncached().raise(null, ValueError, ErrorMessages.DECIMAL_FORMAT_LIMITS); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/formatting/FloatFormatter.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/formatting/FloatFormatter.java index 332cf7dbdc..2c7ada9d77 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/formatting/FloatFormatter.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/formatting/FloatFormatter.java @@ -221,7 +221,7 @@ public FloatFormatter format(double value, String positivePrefixString) { setGroupingAndGroupSize(format); DecimalFormatSymbols symbols = format.getDecimalFormatSymbols(); if (lenPoint > 0) { - result.setCharAt(lenWhole, symbols.getDecimalSeparator()); + result.setCharAt(start + lenSign + lenWhole, symbols.getDecimalSeparator()); } } break; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/formatting/InternalFormat.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/formatting/InternalFormat.java index 1d2c8153f3..629f02cb7b 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/formatting/InternalFormat.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/formatting/InternalFormat.java @@ -21,6 +21,7 @@ import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.runtime.PythonContext; import com.oracle.graal.python.runtime.exception.PException; +import com.oracle.graal.python.runtime.locale.LocaleUtils; import com.oracle.graal.python.runtime.locale.PythonLocale; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.nodes.Node; @@ -214,7 +215,7 @@ public String toString() { protected static DecimalFormat getCurrentDecimalFormat() { Locale currLocale = PythonContext.get(null).getCurrentLocale().category(PythonLocale.LC_NUMERIC); - NumberFormat numberFormat = NumberFormat.getInstance(currLocale); + NumberFormat numberFormat = LocaleUtils.getNumberFormat(currLocale); return numberFormat instanceof DecimalFormat ? (DecimalFormat) numberFormat : null; } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/locale/LocaleUtils.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/locale/LocaleUtils.java index 86e6de440a..682f8f80c8 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/locale/LocaleUtils.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/locale/LocaleUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -41,6 +41,7 @@ package com.oracle.graal.python.runtime.locale; import java.nio.charset.Charset; +import java.text.NumberFormat; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -60,6 +61,17 @@ public final class LocaleUtils { private LocaleUtils() { } + /** Returns a number format with POSIX numeric locale conventions applied. */ + public static NumberFormat getNumberFormat(Locale locale) { + CompilerAsserts.neverPartOfCompilation(); + NumberFormat format = NumberFormat.getInstance(locale); + // Java's root locale groups by thousands, but the POSIX C locale never groups. + if (locale.equals(Locale.ROOT)) { + format.setGroupingUsed(false); + } + return format; + } + public static Locale fromPosix(String posixLocaleId, Locale defaultLocale) { CompilerAsserts.neverPartOfCompilation(); if (posixLocaleId == null) { From b4d760daef8289780d99fd354086cdd90b04650c Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Fri, 16 Oct 2026 15:08:20 +0200 Subject: [PATCH 27/31] Optimize decimal formatting --- .../objects/decimal/DecimalNodes.java | 49 +++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java index 52e6a893d5..5316608c7f 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalNodes.java @@ -165,45 +165,55 @@ public abstract static class DecimalToStringNode extends Node { @Specialization static TruffleString format(Node inliningTarget, PDecimal self, boolean capitals, @Cached InlinedConditionProfile fixedProfile, - @Cached TruffleString.FromLongNode fromLong, @Cached TruffleStringBuilder.AppendCodePointNode appendCodePoint, - @Cached TruffleStringBuilder.AppendSubstringByteIndexNode appendSubstring, @Cached TruffleStringBuilder.AppendLongNumberNode appendLong, @Cached TruffleStringBuilder.ToStringNode toString, @Cached TruffleString.FromJavaStringNode fromJavaStringNode) { // The node specialization profiles if (self.isCompact() && self.getKind() == Kind.FINITE) { - TruffleString digits = fromLong.execute(self.getCompactCoefficient(), TS_ENCODING, false); - int length = digits.byteLength(TS_ENCODING) / Integer.BYTES; + long coefficient = self.getCompactCoefficient(); + int length = DecimalUtils.digits(coefficient); long exponent = self.getExponent(); long adjustedExponent = exponent + length - 1; - // Compact coefficients have at most 19 digits. Both fixed and scientific - // notation fit in 48 characters, even with an exponent of magnitude 10^18. - TruffleStringBuilderUTF32 builder = TruffleStringBuilder.createUTF32(48); + boolean fixed = fixedProfile.profile(inliningTarget, exponent <= 0 && adjustedExponent >= -6); + int point = 0; + int resultLength; + if (fixed) { + point = (int) (length + exponent); + resultLength = point <= 0 ? 2 - point + length : length + (point < length ? 1 : 0); + } else { + resultLength = length + (length > 1 ? 1 : 0) + 2 + DecimalUtils.digits(Math.abs(adjustedExponent)); + } + // Exact capacity lets ToStringNode reuse the buffer without trimming it. + TruffleStringBuilderUTF32 builder = TruffleStringBuilder.createUTF32(resultLength + (self.isNegative() ? 1 : 0)); if (self.isNegative()) { appendCodePoint.execute(builder, '-'); } - if (fixedProfile.profile(inliningTarget, exponent <= 0 && adjustedExponent >= -6)) { - int point = (int) (length + exponent); + if (fixed) { if (point <= 0) { appendCodePoint.execute(builder, '0'); appendCodePoint.execute(builder, '.'); if (point < 0) { appendCodePoint.execute(builder, '0', -point, false); } - appendSubstring.execute(builder, digits, 0, length * Integer.BYTES); + appendLong.execute(builder, coefficient); } else if (point < length) { - appendSubstring.execute(builder, digits, 0, point * Integer.BYTES); + int fractionalDigits = length - point; + long divisor = DecimalUtils.LONG_POWERS_OF_TEN[fractionalDigits]; + appendLong.execute(builder, coefficient / divisor); appendCodePoint.execute(builder, '.'); - appendSubstring.execute(builder, digits, point * Integer.BYTES, (length - point) * Integer.BYTES); + appendFraction(builder, coefficient % divisor, fractionalDigits, appendCodePoint, appendLong); } else { - appendSubstring.execute(builder, digits, 0, length * Integer.BYTES); + appendLong.execute(builder, coefficient); } } else { - appendSubstring.execute(builder, digits, 0, Integer.BYTES); if (length > 1) { + long divisor = DecimalUtils.LONG_POWERS_OF_TEN[length - 1]; + appendLong.execute(builder, coefficient / divisor); appendCodePoint.execute(builder, '.'); - appendSubstring.execute(builder, digits, Integer.BYTES, (length - 1) * Integer.BYTES); + appendFraction(builder, coefficient % divisor, length - 1, appendCodePoint, appendLong); + } else { + appendLong.execute(builder, coefficient); } appendCodePoint.execute(builder, capitals ? 'E' : 'e'); if (adjustedExponent >= 0) { @@ -216,6 +226,15 @@ static TruffleString format(Node inliningTarget, PDecimal self, boolean capitals return fromJavaStringNode.execute(self.toScientificString(capitals), TS_ENCODING); } } + + private static void appendFraction(TruffleStringBuilderUTF32 builder, long coefficient, int width, + TruffleStringBuilder.AppendCodePointNode appendCodePoint, TruffleStringBuilder.AppendLongNumberNode appendLong) { + int padding = width - DecimalUtils.digits(coefficient); + if (padding > 0) { + appendCodePoint.execute(builder, '0', padding, false); + } + appendLong.execute(builder, coefficient); + } } /** Numeric slots accept Decimal and int operands, without invoking conversion protocols. */ From 98333a1e640161ffc8a340d908e641d6ecadd325 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Fri, 16 Oct 2026 17:03:49 +0200 Subject: [PATCH 28/31] Add changelog entry --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5e9ab3f7f..a8bee2902f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ This changelog summarizes major changes between GraalVM versions of the Python language runtime. The main focus is on user-observable behavior of the engine. +## Version 25.6 +- New Java-based `decimal` module with significanlty better performance + +## Version 25.5 +- Added `select.poll()` on Linux and Mac OS +- Better Windows console support and Windows version reporting + ## Version 25.4 * Remove BouncyCastle entirely. BouncyCastle was only needed for legacy RSA, DSA, and EC private keys versions 0 and 1. To support these from Python embeddings, BouncyCastle must now be explicitly enabled by adding the `org.graalvm.python:python-bouncycastle-support` Maven _and_ adding `bcprov-jdk18on`, `bcpkix-jdk18on`, and `bcutil-jdk18on` dependencies. From acb83a79ab7f60af83206d09ab158ad946511a79 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Mon, 19 Oct 2026 10:28:20 +0200 Subject: [PATCH 29/31] Fix invalid native class in tests --- .../src/tests/cpyext/test_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py index 43240045a9..bcbe8f8cc2 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_object.py @@ -327,7 +327,7 @@ def test_inherit_slots_with_managed_class(self): tp_alloc="test_alloc", tp_traverse="test_traverse", tp_methods='{"call_tp_alloc", (PyCFunction)testslots_tp_alloc, METH_O | METH_STATIC, ""}', - tp_flags='Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC', + tp_flags='Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC', tp_free='PyObject_GC_Del', ready_code="ClassWithTpAllocType.tp_new = PyBaseObject_Type.tp_new;" ) From 9c63e71b0ffa6e91a2717a045447fc78745bf1a5 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Mon, 19 Oct 2026 13:16:44 +0200 Subject: [PATCH 30/31] Fix C context type mismatch --- graalpython/com.oracle.graal.python.cext/src/capi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python.cext/src/capi.h b/graalpython/com.oracle.graal.python.cext/src/capi.h index e789c315ac..f3bb8d18c5 100644 --- a/graalpython/com.oracle.graal.python.cext/src/capi.h +++ b/graalpython/com.oracle.graal.python.cext/src/capi.h @@ -335,7 +335,7 @@ PY_TRUFFLE_TYPE(PyClassMethodDescr_Type, "classmethod_descriptor", & PY_TRUFFLE_TYPE(PyCode_Type, "code", &PyType_Type, sizeof(PyTypeObject)) \ PY_TRUFFLE_TYPE(PyCmpWrapper_Type, "KeyWrapper", &PyType_Type, sizeof(PyObject)) \ PY_TRUFFLE_TYPE(PyComplex_Type, "complex", &PyType_Type, sizeof(PyComplexObject)) \ -PY_TRUFFLE_TYPE(PyContext_Type, "Context", &PyType_Type, sizeof(PyObject)) \ +PY_TRUFFLE_TYPE(PyContext_Type, "_contextvars.Context", &PyType_Type, sizeof(PyObject)) \ PY_TRUFFLE_TYPE(PyContextToken_Type, "Token", &PyType_Type, sizeof(PyObject)) \ PY_TRUFFLE_TYPE(PyContextVar_Type, "ContextVar", &PyType_Type, sizeof(PyObject)) \ PY_TRUFFLE_TYPE(_PyCoroWrapper_Type, "coroutine_wrapper", &PyType_Type, sizeof(PyObject)) \ From 4ca7e173b6d0aed9bc01124dc8b681b5c3b35b73 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Mon, 19 Oct 2026 19:02:38 +0200 Subject: [PATCH 31/31] Add missing BoundaryCallData --- .../modules/DecimalModuleBuiltins.java | 13 ++++++-- .../objects/decimal/DecimalBuiltins.java | 6 ++++ .../decimal/DecimalContextBuiltins.java | 32 +++++++++++++++---- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java index 4bb9922a57..6da87d55f3 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/DecimalModuleBuiltins.java @@ -80,12 +80,15 @@ import com.oracle.graal.python.nodes.function.PythonBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; import com.oracle.graal.python.nodes.statement.AbstractImportNode; +import com.oracle.graal.python.runtime.ExecutionContext.BoundaryCallContext; +import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.GenerateNodeFactory; import com.oracle.truffle.api.dsl.NodeFactory; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.strings.TruffleString; @@ -216,11 +219,12 @@ static Object set(Object context, @GenerateNodeFactory public abstract static class LocalcontextNode extends PythonBuiltinNode { @Specialization - static PDecimalContextManager local(Object ctx, Object prec, Object rounding, Object emin, Object emax, Object capitals, Object clamp, Object flags, Object traps, + static PDecimalContextManager local(VirtualFrame frame, Object ctx, Object prec, Object rounding, Object emin, Object emax, Object capitals, Object clamp, Object flags, Object traps, PKeyword[] kwargs, @Bind Node inliningTarget, @Bind PythonLanguage language, @Cached PRaiseNode raiseNode, + @Cached("createFor($node)") BoundaryCallData boundaryCallData, @Cached GetCurrentContextNode getCurrentContext) { if (kwargs.length != 0) { throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_INVALID_LOCALCONTEXT_KEYWORD, kwargs[0].getName()); @@ -235,7 +239,12 @@ static PDecimalContextManager local(Object ctx, Object prec, Object rounding, Ob source = decimalContext; } PDecimalContext local = state.copyContext(language, source); - DecimalContextUtils.setAttributes(inliningTarget, local, prec, rounding, emin, emax, capitals, clamp, flags, traps); + Object saved = BoundaryCallContext.enter(frame, boundaryCallData); + try { + DecimalContextUtils.setAttributes(inliningTarget, local, prec, rounding, emin, emax, capitals, clamp, flags, traps); + } finally { + BoundaryCallContext.exit(frame, boundaryCallData, saved); + } return PFactory.createDecimalContextManager(language, outer, local); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java index 449ba2c25e..f6faa42a74 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalBuiltins.java @@ -115,6 +115,8 @@ import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.nodes.util.CastToJavaBigIntegerNode; import com.oracle.graal.python.nodes.util.CastToJavaStringNode; +import com.oracle.graal.python.runtime.ExecutionContext.BoundaryCallContext; +import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Bind; @@ -1403,6 +1405,7 @@ static Object format(VirtualFrame frame, PDecimal self, Object specifier, Object @Cached TruffleString.FromJavaStringNode fromJavaString, @Cached WarnNode warnNode, @Cached PRaiseNode raiseNode, + @Cached("createFor($node)") BoundaryCallData boundaryCallData, @Cached GetCurrentContextNode getCurrentContext) { if (!unicodeCheck.execute(inliningTarget, specifier)) { throw raiseNode.raise(inliningTarget, TypeError, ErrorMessages.DECIMAL_FORMAT_STRING); @@ -1412,10 +1415,13 @@ static Object format(VirtualFrame frame, PDecimal self, Object specifier, Object warnNode.warnEx(frame, DeprecationWarning, ErrorMessages.DECIMAL_FORMAT_N_DEPRECATED, 1); } PDecimalContext context = getCurrentContext.execute(inliningTarget); + Object state = BoundaryCallContext.enter(frame, boundaryCallData); try { return fromJavaString.execute(DecimalFormatting.format(self, format, override, context), TS_ENCODING); } catch (ArithmeticException e) { throw raiseNode.raise(inliningTarget, MemoryError); + } finally { + BoundaryCallContext.exit(frame, boundaryCallData, state); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java index e8a2dd6076..cd727fba63 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/decimal/DecimalContextBuiltins.java @@ -87,6 +87,8 @@ import com.oracle.graal.python.nodes.function.builtins.PythonVarargsBuiltinNode; import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.graal.python.nodes.util.CastToTruffleStringNode; +import com.oracle.graal.python.runtime.ExecutionContext.BoundaryCallContext; +import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; @@ -1278,9 +1280,15 @@ static PDecimalContext construct(Object cls, @SuppressWarnings("unused") Object[ @GenerateNodeFactory public abstract static class TpInitNode extends PythonBuiltinNode { @Specialization - static Object initialize(PDecimalContext self, Object prec, Object rounding, Object emin, Object emax, Object capitals, Object clamp, Object flags, Object traps, - @Bind Node inliningTarget) { - DecimalContextUtils.setAttributes(inliningTarget, self, prec, rounding, emin, emax, capitals, clamp, flags, traps); + static Object initialize(VirtualFrame frame, PDecimalContext self, Object prec, Object rounding, Object emin, Object emax, Object capitals, Object clamp, Object flags, Object traps, + @Bind Node inliningTarget, + @Cached("createFor($node)") BoundaryCallData boundaryCallData) { + Object state = BoundaryCallContext.enter(frame, boundaryCallData); + try { + DecimalContextUtils.setAttributes(inliningTarget, self, prec, rounding, emin, emax, capitals, clamp, flags, traps); + } finally { + BoundaryCallContext.exit(frame, boundaryCallData, state); + } return PNone.NONE; } } @@ -1324,14 +1332,24 @@ static void set(VirtualFrame frame, PDecimalContext self, Object name, Object va @Cached CastToTruffleStringChecked1Node castName, @Cached TruffleString.EqualNode equal, @Cached PRaiseNode raiseNode, + @Cached("createFor($node)") BoundaryCallData boundaryCallData, @Cached ObjectBuiltins.SetattrNode genericSet) { TruffleString key = castName.cast(inliningTarget, name, ATTR_NAME_MUST_BE_STRING, name); if (value == PNone.NO_VALUE) { throw raiseNode.raise(inliningTarget, AttributeError, ErrorMessages.CANT_DELETE_ATTRIBUTE); - } else if (equal.execute(key, T_FLAGS, TS_ENCODING)) { - self.setFlags(DecimalContextUtils.signalsFromObject(inliningTarget, value, false)); - } else if (equal.execute(key, T_TRAPS, TS_ENCODING)) { - self.setTraps(DecimalContextUtils.signalsFromObject(inliningTarget, value, false)); + } else if (equal.execute(key, T_FLAGS, TS_ENCODING) || equal.execute(key, T_TRAPS, TS_ENCODING)) { + Object state = BoundaryCallContext.enter(frame, boundaryCallData); + int signals; + try { + signals = DecimalContextUtils.signalsFromObject(inliningTarget, value, false); + } finally { + BoundaryCallContext.exit(frame, boundaryCallData, state); + } + if (equal.execute(key, T_FLAGS, TS_ENCODING)) { + self.setFlags(signals); + } else { + self.setTraps(signals); + } } else { genericSet.execute(frame, self, key, value); }