From 41251e11f61562ae537319f5fe2815eea21bdfb8 Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Sun, 18 Oct 2020 03:25:41 +1100 Subject: [PATCH] Implement fixed-point arithmetic --- pyRCV2/method/STVCCounter.py | 9 ++++-- pyRCV2/numbers/__init__.py | 2 ++ pyRCV2/numbers/fixed_js.py | 50 +++++++++++++++++++++++++++++++++ pyRCV2/numbers/fixed_py.py | 53 +++++++++++++++++++++++++++++++++++ pyRCV2/numbers/native_js.py | 5 ++++ pyRCV2/numbers/native_py.py | 4 +++ pyRCV2/numbers/rational_js.py | 5 ++++ pyRCV2/numbers/rational_py.py | 4 +++ test.html | 3 +- worker.js | 5 +++- 10 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 pyRCV2/numbers/fixed_js.py create mode 100644 pyRCV2/numbers/fixed_py.py diff --git a/pyRCV2/method/STVCCounter.py b/pyRCV2/method/STVCCounter.py index 7c141f3..6ebf713 100644 --- a/pyRCV2/method/STVCCounter.py +++ b/pyRCV2/method/STVCCounter.py @@ -102,14 +102,13 @@ class STVCCounter: __pragma__('opov') surplus = count_card.votes - self.quota - transfer_value = surplus / count_card.votes - count_card.transfers -= surplus + #transfer_value = surplus / count_card.votes # Do not do this yet to avoid rounding errors __pragma__('noopov') # Transfer surplus for ballot, ballot_value in count_card.ballots: __pragma__('opov') - new_value = ballot_value * transfer_value + new_value = (ballot_value * surplus) / count_card.votes candidate = next((c for c in ballot.preferences if self.candidates[c].state == CandidateState.HOPEFUL), None) if candidate is not None: @@ -120,6 +119,10 @@ class STVCCounter: self.exhausted.ballots.append((ballot, new_value)) __pragma__('noopov') + __pragma__('opov') + count_card.transfers -= surplus + __pragma__('noopov') + # Declare any candidates meeting the quota as a result of surpluses self.compute_quota() self.elect_meeting_quota() diff --git a/pyRCV2/numbers/__init__.py b/pyRCV2/numbers/__init__.py index 05ad195..e9a8daf 100644 --- a/pyRCV2/numbers/__init__.py +++ b/pyRCV2/numbers/__init__.py @@ -22,10 +22,12 @@ __pragma__('noskip') if is_py: __pragma__('skip') + from pyRCV2.numbers.fixed_py import Fixed, set_dps from pyRCV2.numbers.native_py import Native from pyRCV2.numbers.rational_py import Rational __pragma__('noskip') else: + from pyRCV2.numbers.fixed_js import Fixed, set_dps from pyRCV2.numbers.native_js import Native from pyRCV2.numbers.rational_js import Rational diff --git a/pyRCV2/numbers/fixed_js.py b/pyRCV2/numbers/fixed_js.py new file mode 100644 index 0000000..77e11ca --- /dev/null +++ b/pyRCV2/numbers/fixed_js.py @@ -0,0 +1,50 @@ +# pyRCV2: Preferential vote counting +# Copyright © 2020 Lee Yingtong Li (RunasSudo) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +Big.DP = 6 + +def set_dps(dps): + Big.DP = dps + +class Fixed: + """ + Wrapper for big.js (fixed-point arithmetic) + """ + + def __init__(self, val): + self.impl = Big(val) + + def pp(self, dp): + """Pretty print to specified number of decimal places""" + return self.impl.toFixed(dp) + + def __add__(self, other): + return Fixed(self.impl.plus(other.impl)) + def __sub__(self, other): + return Fixed(self.impl.minus(other.impl)) + def __mul__(self, other): + return Fixed(self.impl.times(other.impl)) + def __div__(self, other): + return Fixed(self.impl.div(other.impl)) + + def __gt__(self, other): + return self.impl.gt(other.impl) + def __ge__(self, other): + return self.impl.gte(other.impl) + def __lt__(self, other): + return self.impl.lt(other.impl) + def __le__(self, other): + return self.impl.lte(other.impl) diff --git a/pyRCV2/numbers/fixed_py.py b/pyRCV2/numbers/fixed_py.py new file mode 100644 index 0000000..52530de --- /dev/null +++ b/pyRCV2/numbers/fixed_py.py @@ -0,0 +1,53 @@ +# pyRCV2: Preferential vote counting +# Copyright © 2020 Lee Yingtong Li (RunasSudo) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from decimal import Decimal + +_quantize_exp = 6 + +def set_dps(dps): + global _quantize_exp + _quantize_exp = Decimal('10') ** -dps + +class Fixed: + """ + Wrapper for Python Decimal (for fixed-point arithmetic) + """ + + def __init__(self, val): + self.impl = Decimal(val).quantize(_quantize_exp) + + def pp(self, dp): + """Pretty print to specified number of decimal places""" + return format(self.impl, '.{}f'.format(dp)) + + def __add__(self, other): + return Fixed((self.impl + other.impl).quantize(_quantize_exp)) + def __sub__(self, other): + return Fixed((self.impl - other.impl).quantize(_quantize_exp)) + def __mul__(self, other): + return Fixed((self.impl * other.impl).quantize(_quantize_exp)) + def __div__(self, other): + return Fixed((self.impl / other.impl).quantize(_quantize_exp)) + + def __gt__(self, other): + return self.impl > other.impl + def __ge__(self, other): + return self.impl >= other.impl + def __lt__(self, other): + return self.impl < other.impl + def __le__(self, other): + return self.impl <= other.impl diff --git a/pyRCV2/numbers/native_js.py b/pyRCV2/numbers/native_js.py index a18f066..4ebd0d3 100644 --- a/pyRCV2/numbers/native_js.py +++ b/pyRCV2/numbers/native_js.py @@ -15,10 +15,15 @@ # along with this program. If not, see . class Native: + """ + Wrapper for JS numbers (naive floating-point arithmetic) + """ + def __init__(self, val): self.impl = parseFloat(val) def pp(self, dp): + """Pretty print to specified number of decimal places""" return self.impl.toFixed(dp) def __add__(self, other): diff --git a/pyRCV2/numbers/native_py.py b/pyRCV2/numbers/native_py.py index 9624176..8af6f4f 100644 --- a/pyRCV2/numbers/native_py.py +++ b/pyRCV2/numbers/native_py.py @@ -15,6 +15,10 @@ # along with this program. If not, see . class Native: + """ + Wrapper for Python float (naive floating-point arithmetic) + """ + def __init__(self, val): self.impl = float(val) diff --git a/pyRCV2/numbers/rational_js.py b/pyRCV2/numbers/rational_js.py index bfb942c..9109cf2 100644 --- a/pyRCV2/numbers/rational_js.py +++ b/pyRCV2/numbers/rational_js.py @@ -15,10 +15,15 @@ # along with this program. If not, see . class Rational: + """ + Wrapper for BigRational.js (rational arithmetic) + """ + def __init__(self, val): self.impl = bigRat(val) def pp(self, dp): + """Pretty print to specified number of decimal places""" return self.impl.valueOf().toFixed(dp) def __add__(self, other): diff --git a/pyRCV2/numbers/rational_py.py b/pyRCV2/numbers/rational_py.py index ee6072b..c161e85 100644 --- a/pyRCV2/numbers/rational_py.py +++ b/pyRCV2/numbers/rational_py.py @@ -17,6 +17,10 @@ from fractions import Fraction class Rational: + """ + Wrapper for Python Fraction (rational arithmetic) + """ + def __init__(self, val): self.impl = Fraction(val) diff --git a/test.html b/test.html index 9411d9a..66f77c0 100644 --- a/test.html +++ b/test.html @@ -33,11 +33,12 @@ - +
+