1
0

util.format_satoshis: floating-point paranoia

This commit is contained in:
SomberNight
2025-08-22 13:30:26 +00:00
parent 44a95bafbe
commit 8d8d1dba0f
4 changed files with 11 additions and 4 deletions

View File

@@ -101,7 +101,7 @@ def satoshis(amount):
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
output: str formatted as bitcoin amount

View File

@@ -234,6 +234,8 @@ def to_decimal(x: Union[str, float, int, Decimal]) -> Decimal:
# Decimal('41754.681')
if isinstance(x, Decimal):
return x
if isinstance(x, int):
return Decimal(x)
return Decimal(str(x))
@@ -800,8 +802,10 @@ def format_satoshis_plain(
if is_max_allowed and parse_max_spend(x):
return f'max({x})'
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)
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.
@@ -833,8 +837,10 @@ def format_satoshis(
if parse_max_spend(x):
return f'max({x})'
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
x = Decimal(x).quantize(Decimal(10) ** (-precision))
x = x.quantize(Decimal(10) ** (-precision))
# format string
overall_precision = decimal_point + precision # max digits after final decimal point
decimal_format = "." + str(overall_precision) if overall_precision > 0 else ""

View File

@@ -1759,7 +1759,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
fee = self.adb.get_tx_fee(tx_hash)
if fee is not None:
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}")
if fee is not None and height in (TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED) \
and self.network and self.network.has_fee_mempool():

View File

@@ -185,6 +185,7 @@ class TestCommands(ElectrumTestCase):
self.assertEqual(format_satoshis(Decimal(123.456)), "0.00000123")
self.assertEqual(format_satoshis(Decimal(123.5)), "0.00000124")
self.assertEqual(format_satoshis(Decimal(123.789)), "0.00000124")
self.assertEqual(format_satoshis(41754.681), "0.00041755")
class TestCommandsTestnet(ElectrumTestCase):