From 567df27cc79f423c76d552bb202634ebc1c7e18d Mon Sep 17 00:00:00 2001 From: Christoph Holtermann Date: Sat, 19 Mar 2022 13:10:20 +0100 Subject: [PATCH 1/2] Implement some comparisons and arithmetics for GncNumeric Derive some numeric comparisons and arithmetics from Fraction class https://github.com/python/cpython/blob/3.7/Lib/fractions.py and general information from https://docs.python.org/3/library/numbers.html#numbers.Integral These methods are bound closer to the gnucash-C-api than those from the fraction class. This is not the full set of comparisons and arithmetics, needs to be extended. It would be good to extend tests to cover this. --- bindings/python/gnucash_core.py | 137 +++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 3 deletions(-) diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py index 5147f7ae38..784b714789 100644 --- a/bindings/python/gnucash_core.py +++ b/bindings/python/gnucash_core.py @@ -28,6 +28,8 @@ # @author Jeff Green, ParIT Worker Co-operative # @ingroup python_bindings +import operator + from enum import IntEnum from urllib.parse import urlparse @@ -426,7 +428,7 @@ class GncNumeric(GnuCashCoreClass): elif len(args) == 1: arg = args[0] if isinstance(arg, int): - return gnc_numeric_create(arg ,1) + return gnc_numeric_create(arg, 1) elif isinstance(arg, float): return double_to_gnc_numeric(arg, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED | GNC_HOW_RND_NEVER) elif isinstance(arg, str): @@ -434,8 +436,10 @@ class GncNumeric(GnuCashCoreClass): if not string_to_gnc_numeric(arg, instance): raise TypeError('Failed to convert to GncNumeric: ' + str(args)) return instance + elif isinstance(arg, GncNumeric): + return arg.instance else: - raise TypeError('Only single int/float/str allowed: ' + str(args)) + raise TypeError('Only single int/float/str/GncNumeric allowed: ' + str(args)) elif len(args) == 2: if isinstance(args[0], int) and isinstance(args[1], int): return gnc_numeric_create(*args) @@ -451,6 +455,133 @@ class GncNumeric(GnuCashCoreClass): else: raise TypeError('Required single int/float/str or two ints: ' + str(args)) + # from https://docs.python.org/3/library/numbers.html#numbers.Integral + # and https://github.com/python/cpython/blob/3.7/Lib/fractions.py + + def _operator_fallbacks(monomorphic_operator, fallback_operator): + """fallbacks are not needed except for method name, + keep for possible later use""" + def forward(a, b): + if isinstance(b, GncNumeric): + return monomorphic_operator(a, b) + if isinstance(b, (int, float)): + return monomorphic_operator(a, GncNumeric(b)) + else: + return NotImplemented + forward.__name__ = '__' + fallback_operator.__name__ + '__' + forward.__doc__ = monomorphic_operator.__doc__ + + def reverse(b, a): + if isinstance(a, (GncNumeric, int, float)): + return forward(b, a) + else: + return NotImplemented + reverse.__name__ = '__r' + fallback_operator.__name__ + '__' + reverse.__doc__ = monomorphic_operator.__doc__ + + return forward, reverse + + def _add(a, b): + return a.add(b, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND) + + def _sub(a, b): + return a.sub(b, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND) + + def _mul(a, b): + return a.mul(b, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND) + + def _div(a, b): + return a.div(b, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND) + + def _floordiv(a, b): + return a.div(b, 1, GNC_HOW_RND_TRUNC) + + __add__, __radd__ = _operator_fallbacks(_add, operator.add) + __iadd__ = __add__ + __sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub) + __isub__ = __sub__ + __mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul) + __imul__ = __mul__ + __truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv) + __itruediv__ = __truediv__ + __floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv) + __ifloordiv__ = __floordiv__ + + # Comparisons derived from https://github.com/python/cpython/blob/3.7/Lib/fractions.py + def _lt(a, b): + return a.compare(b) == -1 + + def _gt(a, b): + return a.compare(b) == 1 + + def _le(a, b): + return a.compare(b) in (0,-1) + + def _ge(a, b): + return a.compare(b) in (0,1) + + def _eq(a, b): + return a.compare(b) == 0 + + def _richcmp(self, other, op): + """Helper for comparison operators, for internal use only. + Implement comparison between a GncNumeric instance `self`, + and either another GncNumeric instance, an int or a float + `other`. If `other` is not an instance of that kind, return + NotImplemented. `op` should be one of the six standard + comparison operators. The comparisons are based on + GncNumeric.compare(). + """ + import math + if isinstance(other, GncNumeric): + return op(other) + elif isinstance(other, (int, float)): + return op(GncNumeric(other)) + else: + return NotImplemented + + def __lt__(a, b): + """a < b""" + return a._richcmp(b, a._lt) + + def __gt__(a, b): + """a > b""" + return a._richcmp(b, a._gt) + + def __le__(a, b): + """a <= b""" + return a._richcmp(b, a._le) + + def __ge__(a, b): + """a >= b""" + return a._richcmp(b, a._ge) + + def __eq__(a, b): + """a == b""" + return a._richcmp(b, a._eq) + + def __bool__(a): + """a != 0""" + return bool(a.num()) + + def __float__(self): + return self.to_double() + + def __int__(self): + return int(self.to_double()) + + def __pos__(a): + """+a""" + return GncNumeric(a.num(), a.denom()) + + def __neg__(a): + """-a""" + return a.neg() + + def __abs__(a): + """abs(a)""" + return a.abs() + def to_fraction(self): from fractions import Fraction return Fraction(self.num(), self.denom()) @@ -673,7 +804,7 @@ from gnucash.gnucash_core_c import \ # used for the how argument in arithmetic functions like GncNumeric.add from gnucash.gnucash_core_c import \ GNC_HOW_DENOM_EXACT, GNC_HOW_DENOM_REDUCE, GNC_HOW_DENOM_LCD, \ - GNC_HOW_DENOM_FIXED + GNC_HOW_DENOM_FIXED, GNC_HOW_DENOM_SIGFIG # import account types from gnucash.gnucash_core_c import \ From 6a015c87448b2ab405e12e543d1805c315bd8094 Mon Sep 17 00:00:00 2001 From: Christoph Holtermann Date: Sat, 19 Mar 2022 15:57:57 +0100 Subject: [PATCH 2/2] make GncNumeric.invert() return GncNumeric --- bindings/python/gnucash_core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py index 784b714789..c688097f4b 100644 --- a/bindings/python/gnucash_core.py +++ b/bindings/python/gnucash_core.py @@ -845,7 +845,8 @@ gncnumeric_dict = { 'add_fixed' : GncNumeric, 'sub_fixed' : GncNumeric, 'convert' : GncNumeric, - 'reduce' : GncNumeric + 'reduce' : GncNumeric, + 'invert' : GncNumeric } methods_return_instance(GncNumeric, gncnumeric_dict)