Changes to the Coin class: some convenience comparison operators;

`parseCoin()` now accepts negative values; the check for an excessive
value is moved to the constructor from `parseCoin()` and uses
`checkArgument()`; some `Coin`-type constants broken out into one
`long` one `Coin` in order to be usable in the constructor.
Corresponding tests included.  The `BitcoinURI` class constructor
throws exception on parsing a negative amount, which is needed now
that `Coin` class accepts negative amounts.
This commit is contained in:
Adam Mackler 2014-06-15 06:16:51 -04:00 committed by Mike Hearn
parent e2ebe69aae
commit e8048cb672
6 changed files with 108 additions and 17 deletions

View file

@ -35,6 +35,11 @@ public final class Coin implements Comparable<Coin>, Serializable {
*/
public static final int NUM_COIN_DECIMALS = 8;
/**
* The number of satoshis equal to one bitcoin.
*/
private static final long COIN_VALUE = LongMath.pow(10, NUM_COIN_DECIMALS);
/**
* Zero Bitcoins.
*/
@ -43,7 +48,7 @@ public final class Coin implements Comparable<Coin>, Serializable {
/**
* One Bitcoin.
*/
public static final Coin COIN = Coin.valueOf(LongMath.pow(10, NUM_COIN_DECIMALS));
public static final Coin COIN = Coin.valueOf(COIN_VALUE);
/**
* 0.01 Bitcoins. This unit is not really used much.
@ -66,7 +71,11 @@ public final class Coin implements Comparable<Coin>, Serializable {
public static final Coin SATOSHI = Coin.valueOf(1);
public static final Coin FIFTY_COINS = COIN.multiply(50);
public static final Coin NEGATIVE_ONE = Coin.valueOf(-1);
/**
* Represents a monetary value of minus one satoshi.
*/
public static final Coin NEGATIVE_SATOSHI = Coin.valueOf(-1);
/**
* The number of satoshis of this monetary value.
@ -74,6 +83,8 @@ public final class Coin implements Comparable<Coin>, Serializable {
public final long value;
private Coin(final long satoshis) {
checkArgument(Math.abs(satoshis) <= COIN_VALUE * NetworkParameters.MAX_COINS,
"%s satoshis exceeds maximum possible quantity of Bitcoin.", satoshis);
this.value = satoshis;
}
@ -99,15 +110,10 @@ public final class Coin implements Comparable<Coin>, Serializable {
* This takes string in a format understood by {@link BigDecimal#BigDecimal(String)},
* for example "0", "1", "0.10", "1.23E3", "1234.5E-5".
*
* @throws ArithmeticException if you try to specify fractional satoshis, or a value out of range.
* @throws IllegalArgumentException if you try to specify fractional satoshis, or a value out of range.
*/
public static Coin parseCoin(final String str) {
Coin coin = Coin.valueOf(new BigDecimal(str).movePointRight(8).toBigIntegerExact().longValue());
if (coin.signum() < 0)
throw new ArithmeticException("Negative coins specified");
if (coin.compareTo(NetworkParameters.MAX_MONEY) > 0)
throw new ArithmeticException("Amount larger than the total quantity of Bitcoins possible specified.");
return coin;
return Coin.valueOf(new BigDecimal(str).movePointRight(8).toBigIntegerExact().longValue());
}
public Coin add(final Coin value) {
@ -134,6 +140,46 @@ public final class Coin implements Comparable<Coin>, Serializable {
return this.value / divisor.value;
}
/**
* Returns true if and only if this instance represents a monetary value greater than zero,
* otherwise false.
*/
public boolean isPositive() {
return signum() == 1;
}
/**
* Returns true if and only if this instance represents a monetary value less than zero,
* otherwise false.
*/
public boolean isNegative() {
return signum() == -1;
}
/**
* Returns true if and only if this instance represents zero monetary value,
* otherwise false.
*/
public boolean isZero() {
return signum() == 0;
}
/**
* Returns true if the monetary value represented by this instance is greater than that
* of the given other Coin, otherwise false.
*/
public boolean isGreaterThan(Coin other) {
return compareTo(other) > 0;
}
/**
* Returns true if the monetary value represented by this instance is less than that
* of the given other Coin, otherwise false.
*/
public boolean isLessThan(Coin other) {
return compareTo(other) < 0;
}
public Coin shiftLeft(final int n) {
return new Coin(this.value << n);
}

View file

@ -130,10 +130,15 @@ public abstract class NetworkParameters implements Serializable {
*/
public static final int BIP16_ENFORCE_TIME = 1333238400;
/**
* The maximum number of coins to be generated
*/
public static final long MAX_COINS = 21000000;
/**
* The maximum money to be generated
*/
public static final Coin MAX_MONEY = COIN.multiply(21000000);
public static final Coin MAX_MONEY = COIN.multiply(MAX_COINS);
/** Alias for TestNet3Params.get(), use that instead. */
@Deprecated

View file

@ -1093,7 +1093,7 @@ public class Transaction extends ChildMessage implements Serializable {
// that position are "nulled out". Unintuitively, the value in a "null" transaction is set to -1.
this.outputs = new ArrayList<TransactionOutput>(this.outputs.subList(0, inputIndex + 1));
for (int i = 0; i < inputIndex; i++)
this.outputs.set(i, new TransactionOutput(params, this, Coin.NEGATIVE_ONE, new byte[] {}));
this.outputs.set(i, new TransactionOutput(params, this, Coin.NEGATIVE_SATOSHI, new byte[] {}));
// The signature isn't broken by new versions of the transaction issued by other parties.
for (int i = 0; i < inputs.size(); i++)
if (i != inputIndex)

View file

@ -112,7 +112,7 @@ public class TransactionOutput extends ChildMessage implements Serializable {
super(params);
// Negative values obviously make no sense, except for -1 which is used as a sentinel value when calculating
// SIGHASH_SINGLE signatures, so unfortunately we have to allow that here.
checkArgument(value.signum() >= 0 || value.equals(Coin.NEGATIVE_ONE), "Negative values not allowed");
checkArgument(value.signum() >= 0 || value.equals(Coin.NEGATIVE_SATOSHI), "Negative values not allowed");
checkArgument(value.compareTo(NetworkParameters.MAX_MONEY) < 0, "Values larger than MAX_MONEY not allowed");
this.value = value;
this.scriptBytes = scriptBytes;

View file

@ -209,8 +209,10 @@ public class BitcoinURI {
// Decode the amount (contains an optional decimal component to 8dp).
try {
Coin amount = Coin.parseCoin(valueToken);
if (amount.signum() < 0)
throw new ArithmeticException("Negative coins specified");
putWithValidation(FIELD_AMOUNT, amount);
} catch (NumberFormatException e) {
} catch (IllegalArgumentException e) {
throw new OptionalFieldValidationException(String.format("'%s' is not a valid amount", valueToken), e);
} catch (ArithmeticException e) {
throw new OptionalFieldValidationException(String.format("'%s' has too many decimal places", valueToken), e);

View file

@ -17,7 +17,11 @@
package com.google.bitcoin.core;
import static com.google.bitcoin.core.Coin.*;
import static com.google.bitcoin.core.NetworkParameters.MAX_MONEY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import org.junit.Assert;
@ -31,14 +35,32 @@ public class CoinTest {
assertEquals(CENT, parseCoin("0.01"));
assertEquals(CENT, parseCoin("1E-2"));
assertEquals(COIN.add(CENT), parseCoin("1.01"));
assertEquals(COIN.negate(), parseCoin("-1"));
try {
parseCoin("2E-20");
org.junit.Assert.fail("should not have accepted fractional satoshis");
} catch (ArithmeticException e) {
}
}
@Test
public void testValueOf() {
// int version
assertEquals(CENT, valueOf(0, 1));
assertEquals(SATOSHI, valueOf(1));
assertEquals(NEGATIVE_SATOSHI, valueOf(-1));
assertEquals(MAX_MONEY, valueOf(MAX_MONEY.value));
assertEquals(MAX_MONEY.negate(), valueOf(MAX_MONEY.value * -1));
try {
valueOf(MAX_MONEY.value + 1);
org.junit.Assert.fail("should not have accepted too-great a monetary value");
} catch (IllegalArgumentException e) {
}
try {
valueOf( (MAX_MONEY.value * -1) - 1);
org.junit.Assert.fail("should not have accepted too-little a monetary value");
} catch (IllegalArgumentException e) {
}
try {
valueOf(1, -1);
@ -48,10 +70,26 @@ public class CoinTest {
valueOf(-1, 0);
fail();
} catch (IllegalArgumentException e) {}
try {
parseCoin("-1");
fail();
} catch (ArithmeticException e) {}
}
@Test
public void testOperators() {
assertTrue(SATOSHI.isPositive());
assertFalse(SATOSHI.isNegative());
assertFalse(SATOSHI.isZero());
assertFalse(NEGATIVE_SATOSHI.isPositive());
assertTrue(NEGATIVE_SATOSHI.isNegative());
assertFalse(NEGATIVE_SATOSHI.isZero());
assertFalse(ZERO.isPositive());
assertFalse(ZERO.isNegative());
assertTrue(ZERO.isZero());
assertTrue(valueOf(2).isGreaterThan(valueOf(1)));
assertFalse(valueOf(2).isGreaterThan(valueOf(2)));
assertFalse(valueOf(1).isGreaterThan(valueOf(2)));
assertTrue(valueOf(1).isLessThan(valueOf(2)));
assertFalse(valueOf(2).isLessThan(valueOf(2)));
assertFalse(valueOf(2).isLessThan(valueOf(1)));
}
@Test