Skip to content

Commit

Permalink
Implement some comparisons and arithmetics for GncNumeric
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
c-holtermann committed Mar 20, 2022
1 parent 5388cc8 commit 567df27
Showing 1 changed file with 134 additions and 3 deletions.
137 changes: 134 additions & 3 deletions bindings/python/gnucash_core.py
Expand Up @@ -28,6 +28,8 @@
# @author Jeff Green, ParIT Worker Co-operative <jeff@parit.ca>
# @ingroup python_bindings

import operator

from enum import IntEnum
from urllib.parse import urlparse

Expand Down Expand Up @@ -426,16 +428,18 @@ def __args_to_instance(args):
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):
instance = gnc_numeric_zero()
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)
Expand All @@ -451,6 +455,133 @@ def __args_to_instance(args):
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())
Expand Down Expand Up @@ -673,7 +804,7 @@ def one_arg_default_none(function):
# 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 \
Expand Down

0 comments on commit 567df27

Please sign in to comment.