Coin: satoshi/long <-> btc/BigDecimal

* Add static utility methods: btcToSatoshi(), satoshiToBtc()
* Add static factory method: ofBtc()
* Add instance method: toBtc()
* Add hamcrest-library to testImplementation for BigDecimal comparison
This commit is contained in:
Sean Gilligan 2020-06-28 16:52:01 -07:00 committed by Andreas Schildbach
parent 6d15df682a
commit f6c8b0dbe3
3 changed files with 133 additions and 4 deletions

View File

@ -22,6 +22,7 @@ dependencies {
testImplementation 'com.h2database:h2:1.3.167'
testImplementation 'org.fusesource.leveldbjni:leveldbjni-all:1.8'
testImplementation 'nl.jqno.equalsverifier:equalsverifier:2.5.2'
testImplementation 'org.hamcrest:hamcrest-library:1.3'
}
sourceCompatibility = 1.7

View File

@ -86,6 +86,12 @@ public final class Coin implements Monetary, Comparable<Coin>, Serializable {
this.value = satoshis;
}
/**
* Create a {@code Coin} from a long integer number of satoshis.
*
* @param satoshis number of satoshis
* @return {@code Coin} object containing value in satoshis
*/
public static Coin valueOf(final long satoshis) {
return new Coin(satoshis);
}
@ -104,7 +110,7 @@ public final class Coin implements Monetary, Comparable<Coin>, Serializable {
}
/**
* Convert an amount expressed in the way humans are used to into satoshis.
* Create a {@code Coin} from an amount expressed in "the way humans are used to".
*
* @param coins Number of bitcoins
* @param cents Number of bitcents (0.01 bitcoin)
@ -119,7 +125,47 @@ public final class Coin implements Monetary, Comparable<Coin>, Serializable {
}
/**
* Parses an amount expressed in the way humans are used to.
* Convert a decimal amount of BTC into satoshis.
*
* @param coins number of coins
* @return number of satoshis
*/
public static long btcToSatoshi(BigDecimal coins) {
return coins.movePointRight(SMALLEST_UNIT_EXPONENT).longValueExact();
}
/**
* Convert an amount in satoshis to an amount in BTC.
*
* @param satoshis number of satoshis
* @return number of bitcoins (in BTC)
*/
public static BigDecimal satoshiToBtc(long satoshis) {
return new BigDecimal(satoshis).movePointLeft(SMALLEST_UNIT_EXPONENT);
}
/**
* Create a {@code Coin} from a decimal amount of BTC.
*
* @param coins number of coins (in BTC)
* @return {@code Coin} object containing value in satoshis
*/
public static Coin ofBtc(BigDecimal coins) {
return Coin.valueOf(btcToSatoshi(coins));
}
/**
* Create a {@code Coin} from a long integer number of satoshis.
*
* @param satoshis number of satoshis
* @return {@code Coin} object containing value in satoshis
*/
public static Coin ofSat(long satoshis) {
return Coin.valueOf(satoshis);
}
/**
* Create a {@code Coin} by parsing a {@code String} amount expressed in "the way humans are used to".
*
* @param str string in a format understood by {@link BigDecimal#BigDecimal(String)}, for example "0", "1", "0.10",
* * "1.23E3", "1234.5E-5".
@ -129,7 +175,7 @@ public final class Coin implements Monetary, Comparable<Coin>, Serializable {
*/
public static Coin parseCoin(final String str) {
try {
long satoshis = new BigDecimal(str).movePointRight(SMALLEST_UNIT_EXPONENT).longValueExact();
long satoshis = btcToSatoshi(new BigDecimal(str));
return Coin.valueOf(satoshis);
} catch (ArithmeticException e) {
throw new IllegalArgumentException(e); // Repackage exception to honor method contract
@ -137,7 +183,8 @@ public final class Coin implements Monetary, Comparable<Coin>, Serializable {
}
/**
* Parses an amount expressed in the way humans are used to. The amount is cut to satoshi precision.
* Create a {@code Coin} by parsing a {@code String} amount expressed in "the way humans are used to".
* The amount is cut to satoshi precision.
*
* @param str string in a format understood by {@link BigDecimal#BigDecimal(String)}, for example "0", "1", "0.10",
* * "1.23E3", "1234.5E-5".
@ -275,6 +322,24 @@ public final class Coin implements Monetary, Comparable<Coin>, Serializable {
return this.value;
}
/**
* Convert to number of satoshis
*
* @return decimal number of satoshis
*/
public long toSat() {
return this.value;
}
/**
* Convert to number of bitcoin (in BTC)
*
* @return decimal number of bitcoin (in BTC)
*/
public BigDecimal toBtc() {
return satoshiToBtc(this.value);
}
private static final MonetaryFormat FRIENDLY_FORMAT = MonetaryFormat.BTC.minDecimals(2).repeatOptionalDecimals(1, 6).postfixCode();
/**

View File

@ -19,13 +19,17 @@ package org.bitcoinj.core;
import static org.bitcoinj.core.Coin.*;
import static org.bitcoinj.core.NetworkParameters.MAX_MONEY;
import static org.hamcrest.MatcherAssert.assertThat;
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.hamcrest.Matchers;
import org.junit.Test;
import java.math.BigDecimal;
public class CoinTest {
@Test
@ -80,6 +84,54 @@ public class CoinTest {
} catch (IllegalArgumentException e) {}
}
@Test
public void testBtcToSatoshi() {
assertEquals(Long.MIN_VALUE, btcToSatoshi(new BigDecimal("-92233720368.54775808")));
assertEquals(0L, btcToSatoshi(BigDecimal.ZERO));
assertEquals(COIN.value, btcToSatoshi(BigDecimal.ONE));
assertEquals(Long.MAX_VALUE, btcToSatoshi(new BigDecimal("92233720368.54775807")));
}
@Test(expected = ArithmeticException.class)
public void testBtcToSatoshi_tooSmall() {
btcToSatoshi(new BigDecimal("-92233720368.54775809")); // .00000001 less than minimum value
}
@Test(expected = ArithmeticException.class)
public void testBtcToSatoshi_tooBig() {
btcToSatoshi(new BigDecimal("92233720368.54775808")); // .00000001 more than maximum value
}
@Test(expected = ArithmeticException.class)
public void testBtcToSatoshi_tooPrecise1() {
btcToSatoshi(new BigDecimal("0.000000001")); // More than SMALLEST_UNIT_EXPONENT precision
}
@Test(expected = ArithmeticException.class)
public void testBtcToSatoshi_tooPrecise2() {
btcToSatoshi(new BigDecimal("92233720368.547758079")); // More than SMALLEST_UNIT_EXPONENT precision
}
@Test
public void testSatoshiToBtc() {
assertThat(new BigDecimal("-92233720368.54775808"), Matchers.comparesEqualTo(satoshiToBtc(Long.MIN_VALUE)));
assertThat(new BigDecimal("-0.00000001"), Matchers.comparesEqualTo(satoshiToBtc(NEGATIVE_SATOSHI.value)));
assertThat(BigDecimal.ZERO, Matchers.comparesEqualTo(satoshiToBtc(0L)));
assertThat(new BigDecimal("0.00000001"), Matchers.comparesEqualTo(satoshiToBtc(SATOSHI.value)));
assertThat(BigDecimal.ONE, Matchers.comparesEqualTo(satoshiToBtc(COIN.value)));
assertThat(new BigDecimal(50), Matchers.comparesEqualTo(satoshiToBtc(FIFTY_COINS.value)));
assertThat(new BigDecimal("92233720368.54775807"), Matchers.comparesEqualTo(satoshiToBtc(Long.MAX_VALUE)));
}
@Test
public void testOfBtc() {
assertEquals(Coin.valueOf(Long.MIN_VALUE), Coin.ofBtc(new BigDecimal("-92233720368.54775808")));
assertEquals(ZERO, Coin.ofBtc(BigDecimal.ZERO));
assertEquals(COIN, Coin.ofBtc(BigDecimal.ONE));
assertEquals(Coin.valueOf(Long.MAX_VALUE), Coin.ofBtc(new BigDecimal("92233720368.54775807")));
}
@Test
public void testOperators() {
assertTrue(SATOSHI.isPositive());
@ -120,6 +172,17 @@ public class CoinTest {
Coin.valueOf(Long.MIN_VALUE).subtract(Coin.SATOSHI);
}
@Test
public void testToBtc() {
assertThat(new BigDecimal("-92233720368.54775808"), Matchers.comparesEqualTo(Coin.valueOf(Long.MIN_VALUE).toBtc()));
assertThat(new BigDecimal("-0.00000001"), Matchers.comparesEqualTo(NEGATIVE_SATOSHI.toBtc()));
assertThat(BigDecimal.ZERO, Matchers.comparesEqualTo(ZERO.toBtc()));
assertThat(new BigDecimal("0.00000001"), Matchers.comparesEqualTo(SATOSHI.toBtc()));
assertThat(BigDecimal.ONE, Matchers.comparesEqualTo(COIN.toBtc()));
assertThat(new BigDecimal(50), Matchers.comparesEqualTo(FIFTY_COINS.toBtc()));
assertThat(new BigDecimal("92233720368.54775807"), Matchers.comparesEqualTo(Coin.valueOf(Long.MAX_VALUE).toBtc()));
}
@Test
public void testToFriendlyString() {
assertEquals("1.00 BTC", COIN.toFriendlyString());