util.format_satoshis: floating-point paranoia
This commit is contained in:
@@ -101,7 +101,7 @@ def satoshis(amount):
|
|||||||
return int(COIN*to_decimal(amount)) if amount is not None else None
|
return int(COIN*to_decimal(amount)) if amount is not None else None
|
||||||
|
|
||||||
|
|
||||||
def format_satoshis(x: Union[str, float, int, Decimal, None]) -> Optional[str]:
|
def format_satoshis(x: Union[float, int, Decimal, None]) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
input: satoshis as a Number
|
input: satoshis as a Number
|
||||||
output: str formatted as bitcoin amount
|
output: str formatted as bitcoin amount
|
||||||
|
|||||||
@@ -234,6 +234,8 @@ def to_decimal(x: Union[str, float, int, Decimal]) -> Decimal:
|
|||||||
# Decimal('41754.681')
|
# Decimal('41754.681')
|
||||||
if isinstance(x, Decimal):
|
if isinstance(x, Decimal):
|
||||||
return x
|
return x
|
||||||
|
if isinstance(x, int):
|
||||||
|
return Decimal(x)
|
||||||
return Decimal(str(x))
|
return Decimal(str(x))
|
||||||
|
|
||||||
|
|
||||||
@@ -800,8 +802,10 @@ def format_satoshis_plain(
|
|||||||
if is_max_allowed and parse_max_spend(x):
|
if is_max_allowed and parse_max_spend(x):
|
||||||
return f'max({x})'
|
return f'max({x})'
|
||||||
assert isinstance(x, (int, float, Decimal)), f"{x!r} should be a number"
|
assert isinstance(x, (int, float, Decimal)), f"{x!r} should be a number"
|
||||||
|
# TODO(ghost43) just hard-fail if x is a float. do we even use floats for money anywhere?
|
||||||
|
x = to_decimal(x)
|
||||||
scale_factor = pow(10, decimal_point)
|
scale_factor = pow(10, decimal_point)
|
||||||
return "{:.8f}".format(Decimal(x) / scale_factor).rstrip('0').rstrip('.')
|
return "{:.8f}".format(x / scale_factor).rstrip('0').rstrip('.')
|
||||||
|
|
||||||
|
|
||||||
# Check that Decimal precision is sufficient.
|
# Check that Decimal precision is sufficient.
|
||||||
@@ -833,8 +837,10 @@ def format_satoshis(
|
|||||||
if parse_max_spend(x):
|
if parse_max_spend(x):
|
||||||
return f'max({x})'
|
return f'max({x})'
|
||||||
assert isinstance(x, (int, float, Decimal)), f"{x!r} should be a number"
|
assert isinstance(x, (int, float, Decimal)), f"{x!r} should be a number"
|
||||||
|
# TODO(ghost43) just hard-fail if x is a float. do we even use floats for money anywhere?
|
||||||
|
x = to_decimal(x)
|
||||||
# lose redundant precision
|
# lose redundant precision
|
||||||
x = Decimal(x).quantize(Decimal(10) ** (-precision))
|
x = x.quantize(Decimal(10) ** (-precision))
|
||||||
# format string
|
# format string
|
||||||
overall_precision = decimal_point + precision # max digits after final decimal point
|
overall_precision = decimal_point + precision # max digits after final decimal point
|
||||||
decimal_format = "." + str(overall_precision) if overall_precision > 0 else ""
|
decimal_format = "." + str(overall_precision) if overall_precision > 0 else ""
|
||||||
|
|||||||
@@ -1759,7 +1759,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
|||||||
fee = self.adb.get_tx_fee(tx_hash)
|
fee = self.adb.get_tx_fee(tx_hash)
|
||||||
if fee is not None:
|
if fee is not None:
|
||||||
size = tx.estimated_size()
|
size = tx.estimated_size()
|
||||||
fee_per_byte = fee / size
|
fee_per_byte = Decimal(fee) / size
|
||||||
extra.append(format_fee_satoshis(fee_per_byte) + f" {util.UI_UNIT_NAME_FEERATE_SAT_PER_VB}")
|
extra.append(format_fee_satoshis(fee_per_byte) + f" {util.UI_UNIT_NAME_FEERATE_SAT_PER_VB}")
|
||||||
if fee is not None and height in (TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED) \
|
if fee is not None and height in (TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED) \
|
||||||
and self.network and self.network.has_fee_mempool():
|
and self.network and self.network.has_fee_mempool():
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ class TestCommands(ElectrumTestCase):
|
|||||||
self.assertEqual(format_satoshis(Decimal(123.456)), "0.00000123")
|
self.assertEqual(format_satoshis(Decimal(123.456)), "0.00000123")
|
||||||
self.assertEqual(format_satoshis(Decimal(123.5)), "0.00000124")
|
self.assertEqual(format_satoshis(Decimal(123.5)), "0.00000124")
|
||||||
self.assertEqual(format_satoshis(Decimal(123.789)), "0.00000124")
|
self.assertEqual(format_satoshis(Decimal(123.789)), "0.00000124")
|
||||||
|
self.assertEqual(format_satoshis(41754.681), "0.00041755")
|
||||||
|
|
||||||
|
|
||||||
class TestCommandsTestnet(ElectrumTestCase):
|
class TestCommandsTestnet(ElectrumTestCase):
|
||||||
|
|||||||
Reference in New Issue
Block a user