From 5c80293696681c811610a0caaa84a1a2854b2697 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 20 Jul 2021 20:02:45 +0200 Subject: [PATCH] util.format_satoshis: fix for amounts with higher than sat precision Previously, msat precision was leaking through format_satoshis if the user's base unit was set to "sat". This was a bug. Some features of format_satoshis did not work well with such values, such as the "whitespaces" param. Old code: >>> util.format_satoshis(Decimal('45831275.748'), decimal_point=2) '458312.76' >>> util.format_satoshis(Decimal('45831275.748'), decimal_point=0) '45831275.748' New code: >>> util.format_satoshis(Decimal('45831275.748'), decimal_point=2) '458312.76' >>> util.format_satoshis(Decimal('45831275.748'), decimal_point=0) '45831276.' --- electrum/tests/test_util.py | 21 +++++++++++++++++++-- electrum/util.py | 26 +++++++++++++------------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/electrum/tests/test_util.py b/electrum/tests/test_util.py index 9441c30a5..c39f8ce37 100644 --- a/electrum/tests/test_util.py +++ b/electrum/tests/test_util.py @@ -23,6 +23,22 @@ class TestUtil(ElectrumTestCase): def test_format_satoshis_decimal(self): self.assertEqual("0.00001234", format_satoshis(Decimal(1234))) + def test_format_satoshis_msat_resolution(self): + self.assertEqual("45831276.", format_satoshis(Decimal("45831276"), decimal_point=0)) + self.assertEqual("45831276.", format_satoshis(Decimal("45831275.748"), decimal_point=0)) + self.assertEqual("45831275.75", format_satoshis(Decimal("45831275.748"), decimal_point=0, precision=2)) + self.assertEqual("45831275.748", format_satoshis(Decimal("45831275.748"), decimal_point=0, precision=3)) + + self.assertEqual("458312.76", format_satoshis(Decimal("45831276"), decimal_point=2)) + self.assertEqual("458312.76", format_satoshis(Decimal("45831275.748"), decimal_point=2)) + self.assertEqual("458312.7575", format_satoshis(Decimal("45831275.748"), decimal_point=2, precision=2)) + self.assertEqual("458312.75748", format_satoshis(Decimal("45831275.748"), decimal_point=2, precision=3)) + + self.assertEqual("458.31276", format_satoshis(Decimal("45831276"), decimal_point=5)) + self.assertEqual("458.31276", format_satoshis(Decimal("45831275.748"), decimal_point=5)) + self.assertEqual("458.3127575", format_satoshis(Decimal("45831275.748"), decimal_point=5, precision=2)) + self.assertEqual("458.31275748", format_satoshis(Decimal("45831275.748"), decimal_point=5, precision=3)) + def test_format_fee_float(self): self.assertEqual("1.7", format_fee_satoshis(1700/1000)) @@ -48,11 +64,12 @@ class TestUtil(ElectrumTestCase): format_satoshis(-1234, whitespaces=True)) def test_format_satoshis_diff_positive(self): - self.assertEqual("+0.00001234", - format_satoshis(1234, is_diff=True)) + self.assertEqual("+0.00001234", format_satoshis(1234, is_diff=True)) + self.assertEqual("+456789.00001234", format_satoshis(45678900001234, is_diff=True)) def test_format_satoshis_diff_negative(self): self.assertEqual("-0.00001234", format_satoshis(-1234, is_diff=True)) + self.assertEqual("-456789.00001234", format_satoshis(-45678900001234, is_diff=True)) def test_format_satoshis_plain(self): self.assertEqual("0.00001234", format_satoshis_plain(1234)) diff --git a/electrum/util.py b/electrum/util.py index 1c13f664e..eb9ce24be 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -647,37 +647,37 @@ DECIMAL_POINT = localeconv()['decimal_point'] # type: str def format_satoshis( - x, # in satoshis + x: Union[int, float, Decimal, str, None], # amount in satoshis *, - num_zeros=0, - decimal_point=8, - precision=None, - is_diff=False, - whitespaces=False, + num_zeros: int = 0, + decimal_point: int = 8, # how much to shift decimal point to left (default: sat->BTC) + precision: int = 0, # extra digits after satoshi precision + is_diff: bool = False, # if True, enforce a leading sign (+/-) + whitespaces: bool = False, # if True, add whitespaces, to align numbers in a column ) -> str: if x is None: return 'unknown' if x == '!': return 'max' - if precision is None: - precision = decimal_point + assert isinstance(x, (int, float, Decimal)), f"{x!r} should be a number" + # lose redundant precision + x = Decimal(x).quantize(Decimal(10) ** (-precision)) # format string - decimal_format = "." + str(precision) if precision > 0 else "" + overall_precision = decimal_point + precision # max digits after final decimal point + decimal_format = "." + str(overall_precision) if overall_precision > 0 else "" if is_diff: decimal_format = '+' + decimal_format # initial result scale_factor = pow(10, decimal_point) - if not isinstance(x, Decimal): - x = Decimal(x).quantize(Decimal('1E-8')) result = ("{:" + decimal_format + "f}").format(x / scale_factor) if "." not in result: result += "." result = result.rstrip('0') - # extra decimal places + # add extra decimal places (zeros) integer_part, fract_part = result.split(".") if len(fract_part) < num_zeros: fract_part += "0" * (num_zeros - len(fract_part)) result = integer_part + DECIMAL_POINT + fract_part - # leading/trailing whitespaces + # add leading/trailing whitespaces if whitespaces: result += " " * (decimal_point - len(fract_part)) result = " " * (15 - len(result)) + result