From 2847c14f0645594d2546328ac1f35519d74373fa Mon Sep 17 00:00:00 2001 From: Josh Rickmar Date: Fri, 20 Jun 2014 15:17:48 -0500 Subject: [PATCH] Add Amount.MulF64. ok @davecgh, @jcvernaleo --- amount.go | 30 +++++++++----- amount_test.go | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ const.go | 7 +++- 3 files changed, 133 insertions(+), 11 deletions(-) diff --git a/amount.go b/amount.go index 6383aa2..4beab95 100644 --- a/amount.go +++ b/amount.go @@ -53,6 +53,17 @@ func (u AmountUnit) String() string { // to as a `Satoshi'). A single Amount is equal to 1e-8 of a bitcoin. type Amount int64 +// round converts a floating point number, which may or may not be representable +// as an integer, to the Amount integer type by rounding to the nearest integer. +// This is performed by adding or subtracting 0.5 depending on the sign, and +// relying on integer truncation to round the value to the nearest Amount. +func round(f float64) Amount { + if f < 0 { + return Amount(f - 0.5) + } + return Amount(f + 0.5) +} + // NewAmount creates an Amount from a floating point value representing // some value in bitcoin. NewAmount errors if f is NaN or +-Infinity, but // does not check that the amount is within the total amount of bitcoin @@ -69,16 +80,7 @@ func NewAmount(f float64) (Amount, error) { return 0, errors.New("invalid bitcoin amount") } - a := f * float64(SatoshiPerBitcoin) - - // Depending on the sign, add or subtract 0.5 and rely on integer - // truncation to correctly round the value up or down. - if a < 0 { - a = a - 0.5 - } else { - a = a + 0.5 - } - return Amount(a), nil + return round(f * satoshiPerBitcoin), nil } // ToUnit converts a monetary amount counted in bitcoin base units to a @@ -100,3 +102,11 @@ func (a Amount) Format(u AmountUnit) string { func (a Amount) String() string { return a.Format(AmountBTC) } + +// MulF64 multiplies an Amount by a floating point value. While this is not +// an operation that must typically be done by a full node or wallet, it is +// useful for services that build on top of bitcoin (for example, calculating +// a fee by multiplying by a percentage). +func (a Amount) MulF64(f float64) Amount { + return round(float64(a) * f) +} diff --git a/amount_test.go b/amount_test.go index 7361ddb..4792265 100644 --- a/amount_test.go +++ b/amount_test.go @@ -192,3 +192,110 @@ func TestAmountUnitConversions(t *testing.T) { } } } + +func TestAmountMulF64(t *testing.T) { + tests := []struct { + name string + amt Amount + mul float64 + res Amount + }{ + { + name: "Multiply 0.1 BTC by 2", + amt: 100e5, // 0.1 BTC + mul: 2, + res: 200e5, // 0.2 BTC + }, + { + name: "Multiply 0.2 BTC by 0.02", + amt: 200e5, // 0.2 BTC + mul: 1.02, + res: 204e5, // 0.204 BTC + }, + { + name: "Multiply 0.1 BTC by -2", + amt: 100e5, // 0.1 BTC + mul: -2, + res: -200e5, // -0.2 BTC + }, + { + name: "Multiply 0.2 BTC by -0.02", + amt: 200e5, // 0.2 BTC + mul: -1.02, + res: -204e5, // -0.204 BTC + }, + { + name: "Multiply -0.1 BTC by 2", + amt: -100e5, // -0.1 BTC + mul: 2, + res: -200e5, // -0.2 BTC + }, + { + name: "Multiply -0.2 BTC by 0.02", + amt: -200e5, // -0.2 BTC + mul: 1.02, + res: -204e5, // -0.204 BTC + }, + { + name: "Multiply -0.1 BTC by -2", + amt: -100e5, // -0.1 BTC + mul: -2, + res: 200e5, // 0.2 BTC + }, + { + name: "Multiply -0.2 BTC by -0.02", + amt: -200e5, // -0.2 BTC + mul: -1.02, + res: 204e5, // 0.204 BTC + }, + { + name: "Round down", + amt: 49, // 49 Satoshis + mul: 0.01, + res: 0, + }, + { + name: "Round up", + amt: 50, // 50 Satoshis + mul: 0.01, + res: 1, // 1 Satoshi + }, + { + name: "Multiply by 0.", + amt: 1e8, // 1 BTC + mul: 0, + res: 0, // 0 BTC + }, + { + name: "Multiply 1 by 0.5.", + amt: 1, // 1 Satoshi + mul: 0.5, + res: 1, // 1 Satoshi + }, + { + name: "Multiply 100 by 66%.", + amt: 100, // 100 Satoshis + mul: 0.66, + res: 66, // 66 Satoshis + }, + { + name: "Multiply 100 by 66.6%.", + amt: 100, // 100 Satoshis + mul: 0.666, + res: 67, // 67 Satoshis + }, + { + name: "Multiply 100 by 2/3.", + amt: 100, // 100 Satoshis + mul: 2.0 / 3, + res: 67, // 67 Satoshis + }, + } + + for _, test := range tests { + a := test.amt.MulF64(test.mul) + if a != test.res { + t.Errorf("%v: expected %v got %v", test.name, test.res, a) + } + } +} diff --git a/const.go b/const.go index a18b28d..e4947de 100644 --- a/const.go +++ b/const.go @@ -5,11 +5,16 @@ package btcutil const ( + // satoshiPerBitcoin is the untyped version of SatoshiPerBitcoin. + // + // TODO(jrick): Switch the exported consts below to be untyped. + satoshiPerBitcoin = 1e8 + // SatoshiPerBitcent is the number of satoshi in one bitcoin cent. SatoshiPerBitcent int64 = 1e6 // SatoshiPerBitcoin is the number of satoshi in one bitcoin (1 BTC). - SatoshiPerBitcoin int64 = 1e8 + SatoshiPerBitcoin int64 = satoshiPerBitcoin // MaxSatoshi is the maximum transaction amount allowed in satoshi. MaxSatoshi int64 = 21e6 * SatoshiPerBitcoin