Skip to content

Commit

Permalink
MonetaryFormat: Support satoshi denomination.
Browse files Browse the repository at this point in the history
  • Loading branch information
Andreas Schildbach authored and ripcurlx committed Aug 23, 2021
1 parent d515266 commit 57ea47f
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 8 deletions.
33 changes: 25 additions & 8 deletions core/src/main/java/org/bitcoinj/utils/MonetaryFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public final class MonetaryFormat {
public static final MonetaryFormat MBTC = new MonetaryFormat().shift(3).minDecimals(2).optionalDecimals(2);
/** Standard format for the µBTC denomination. */
public static final MonetaryFormat UBTC = new MonetaryFormat().shift(6).minDecimals(0).optionalDecimals(2);
/** Standard format for the satoshi denomination. */
public static final MonetaryFormat SAT = new MonetaryFormat().shift(8).minDecimals(0).optionalDecimals(0);
/** Standard format for fiat amounts. */
public static final MonetaryFormat FIAT = new MonetaryFormat().shift(0).minDecimals(2).repeatOptionalDecimals(2, 1);
/** Currency code for base 1 Bitcoin. */
Expand All @@ -60,12 +62,16 @@ public final class MonetaryFormat {
public static final String CODE_MBTC = "mBTC";
/** Currency code for base 1/1000000 Bitcoin. */
public static final String CODE_UBTC = "µBTC";
/** Currency code for base 1 satoshi. */
public static final String CODE_SAT = "sat";
/** Currency symbol for base 1 Bitcoin. */
public static final String SYMBOL_BTC = "\u20bf";
/** Currency symbol for base 1/1000 Bitcoin. */
public static final String SYMBOL_MBTC = "m" + SYMBOL_BTC;
/** Currency symbol for base 1/1000000 Bitcoin. */
public static final String SYMBOL_UBTC = "µ" + SYMBOL_BTC;
/** Currency symbol for base 1 satoshi. */
public static final String SYMBOL_SAT = "\u0219";

public static final int MAX_DECIMALS = 8;

Expand Down Expand Up @@ -297,11 +303,19 @@ public MonetaryFormat withLocale(Locale locale) {
shift, roundingMode, codes, codeSeparator, codePrefixed);
}

/**
* Construct a MonetaryFormat with the default configuration.
*/
public MonetaryFormat() {
this(false);
}

public MonetaryFormat(boolean useBitcoinSymbol) {
/**
* Construct a MonetaryFormat with the default configuration.
*
* @param useSymbol use unicode symbols instead of the BTC and sat codes
*/
public MonetaryFormat(boolean useSymbol) {
// defaults
this.negativeSign = '-';
this.positiveSign = 0; // none
Expand All @@ -311,10 +325,11 @@ public MonetaryFormat(boolean useBitcoinSymbol) {
this.decimalGroups = null;
this.shift = 0;
this.roundingMode = RoundingMode.HALF_UP;
this.codes = new String[MAX_DECIMALS];
this.codes[0] = useBitcoinSymbol ? SYMBOL_BTC : CODE_BTC;
this.codes[3] = useBitcoinSymbol ? SYMBOL_MBTC : CODE_MBTC;
this.codes[6] = useBitcoinSymbol ? SYMBOL_UBTC : CODE_UBTC;
this.codes = new String[MAX_DECIMALS + 1];
this.codes[0] = useSymbol ? SYMBOL_BTC : CODE_BTC;
this.codes[3] = useSymbol ? SYMBOL_MBTC : CODE_MBTC;
this.codes[6] = useSymbol ? SYMBOL_UBTC : CODE_UBTC;
this.codes[8] = useSymbol ? SYMBOL_SAT : CODE_SAT;
this.codeSeparator = ' ';
this.codePrefixed = true;
}
Expand Down Expand Up @@ -350,16 +365,18 @@ public CharSequence format(Monetary monetary) {

// rounding
long satoshis = Math.abs(monetary.getValue());
long precisionDivisor = checkedPow(10, smallestUnitExponent - shift - maxDecimals);
int potentialDecimals = smallestUnitExponent - shift;
long precisionDivisor = checkedPow(10, potentialDecimals - maxDecimals);
satoshis = checkedMultiply(divide(satoshis, precisionDivisor, roundingMode), precisionDivisor);

// shifting
long shiftDivisor = checkedPow(10, smallestUnitExponent - shift);
long shiftDivisor = checkedPow(10, potentialDecimals);
long numbers = satoshis / shiftDivisor;
long decimals = satoshis % shiftDivisor;

// formatting
String decimalsStr = String.format(Locale.US, "%0" + (smallestUnitExponent - shift) + "d", decimals);
String decimalsStr = potentialDecimals > 0 ? String.format(Locale.US,
"%0" + Integer.toString(potentialDecimals) + "d", decimals) : "";
StringBuilder str = new StringBuilder(decimalsStr);
while (str.length() > minDecimals && str.charAt(str.length() - 1) == '0')
str.setLength(str.length() - 1); // trim trailing zero
Expand Down
14 changes: 14 additions & 0 deletions core/src/test/java/org/bitcoinj/utils/MonetaryFormatTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.junit.Test;

import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;

public class MonetaryFormatTest {

Expand Down Expand Up @@ -185,6 +186,13 @@ public void uBtcRounding() throws Exception {
assertEquals("11223344556677.88", format(value, 6, 2));
}

@Test
public void sat() throws Exception {
assertEquals("0", format(ZERO, 8, 0));
assertEquals("100000000", format(COIN, 8, 0));
assertEquals("2100000000000000", format(NetworkParameters.MAX_MONEY, 8, 0));
}

private String format(Coin coin, int shift, int minDecimals, int... decimalGroups) {
return NO_CODE.shift(shift).minDecimals(minDecimals).optionalDecimals(decimalGroups).format(coin).toString();
}
Expand Down Expand Up @@ -214,6 +222,7 @@ public void standardCodes() throws Exception {
assertEquals("BTC 0.00", MonetaryFormat.BTC.format(Coin.ZERO).toString());
assertEquals("mBTC 0.00", MonetaryFormat.MBTC.format(Coin.ZERO).toString());
assertEquals("µBTC 0", MonetaryFormat.UBTC.format(Coin.ZERO).toString());
assertEquals("sat 0", MonetaryFormat.SAT.format(Coin.ZERO).toString());
}

@Test
Expand Down Expand Up @@ -293,6 +302,11 @@ public void parse() throws Exception {
assertEquals(Coin.MICROCOIN.negate(), MonetaryFormat.UBTC.parse("-1"));
assertEquals(Coin.MICROCOIN.negate(), MonetaryFormat.UBTC.parse("-1.0"));

assertEquals(Coin.SATOSHI, MonetaryFormat.SAT.parse("1"));
assertEquals(Coin.SATOSHI, MonetaryFormat.SAT.parse("01"));
assertEquals(Coin.SATOSHI, MonetaryFormat.SAT.positiveSign('+').parse("+1"));
assertEquals(Coin.SATOSHI.negate(), MonetaryFormat.SAT.parse("-1"));

assertEquals(Coin.CENT, NO_CODE.withLocale(new Locale("hi", "IN")).parse(".०१")); // Devanagari
}

Expand Down

0 comments on commit 57ea47f

Please sign in to comment.