Add Amount.MulF64.

ok @davecgh, @jcvernaleo
This commit is contained in:
Josh Rickmar 2014-06-20 15:17:48 -05:00
parent fb4c64910d
commit 2847c14f06
3 changed files with 133 additions and 11 deletions

View file

@ -53,6 +53,17 @@ func (u AmountUnit) String() string {
// to as a `Satoshi'). A single Amount is equal to 1e-8 of a bitcoin. // to as a `Satoshi'). A single Amount is equal to 1e-8 of a bitcoin.
type Amount int64 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 // NewAmount creates an Amount from a floating point value representing
// some value in bitcoin. NewAmount errors if f is NaN or +-Infinity, but // 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 // 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") return 0, errors.New("invalid bitcoin amount")
} }
a := f * float64(SatoshiPerBitcoin) return round(f * satoshiPerBitcoin), nil
// 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
} }
// ToUnit converts a monetary amount counted in bitcoin base units to a // 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 { func (a Amount) String() string {
return a.Format(AmountBTC) 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)
}

View file

@ -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)
}
}
}

View file

@ -5,11 +5,16 @@
package btcutil package btcutil
const ( 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 is the number of satoshi in one bitcoin cent.
SatoshiPerBitcent int64 = 1e6 SatoshiPerBitcent int64 = 1e6
// SatoshiPerBitcoin is the number of satoshi in one bitcoin (1 BTC). // 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 is the maximum transaction amount allowed in satoshi.
MaxSatoshi int64 = 21e6 * SatoshiPerBitcoin MaxSatoshi int64 = 21e6 * SatoshiPerBitcoin