diff --git a/near-java-api-common/src/main/java/com/syntifi/near/api/common/helper/Formats.java b/near-java-api-common/src/main/java/com/syntifi/near/api/common/helper/Formats.java index 77fba1b6..bb7f71d3 100644 --- a/near-java-api-common/src/main/java/com/syntifi/near/api/common/helper/Formats.java +++ b/near-java-api-common/src/main/java/com/syntifi/near/api/common/helper/Formats.java @@ -22,11 +22,22 @@ public class Formats { /** * Number of indivisible units in one NEAR. Derived from {@link #NEAR_NOMINATION_EXP}. */ - public static BigInteger NEAR_NOMINATION = new BigInteger("10", 10).pow(NEAR_NOMINATION_EXP); + public static BigInteger NEAR_NOMINATION = BigInteger.valueOf(10).pow(NEAR_NOMINATION_EXP); + + /** + * Pre-calculate offsets used for rounding to different number of digits + */ + static BigInteger[] ROUNDING_OFFSETS = new BigInteger[NEAR_NOMINATION_EXP]; + static { + BigInteger ten = BigInteger.valueOf(10); + for (BigInteger i = BigInteger.ZERO, offset = BigInteger.valueOf(5); + i.compareTo(BigInteger.valueOf(NEAR_NOMINATION_EXP)) < 0; + i = i.add(BigInteger.ONE), offset = offset.multiply(ten)) { + ROUNDING_OFFSETS[(int) i.longValue()] = offset; + } + } /** - * TODO: WIP IF NEEDED - *

* Convert account balance value from internal indivisible units to NEAR. 1 NEAR is defined by {@link #NEAR_NOMINATION}. * Effectively this divides given amount by {@link #NEAR_NOMINATION}. * @@ -34,19 +45,20 @@ public class Formats { * @param fracDigits number of fractional digits to preserve in formatted string. Balance is rounded to match given number of digits. * @return Value in Ⓝ */ - public static String formatNearAmount(String balance, int fracDigits) { - final BigInteger balanceBN = new BigInteger(balance, 10); + public static String formatNearAmount(String balance, Integer fracDigits) { + if (fracDigits == null) fracDigits = NEAR_NOMINATION_EXP; + BigInteger balanceBN = new BigInteger(balance, 10); if (fracDigits != NEAR_NOMINATION_EXP) { // Adjust balance for rounding at given number of digits final int roundingExp = NEAR_NOMINATION_EXP - fracDigits - 1; if (roundingExp > 0) { - //balanceBN.iadd(ROUNDING_OFFSETS[roundingExp]); + balanceBN = balanceBN.add(ROUNDING_OFFSETS[roundingExp]); } } balance = balanceBN.toString(); - final String wholeStr = balance != null ? balance.substring(0, balance.length() - NEAR_NOMINATION_EXP) : "0"; - final String fractionStr = balance != null ? padStart(balance.substring(balance.length() - NEAR_NOMINATION_EXP), NEAR_NOMINATION_EXP, '0').substring(0, fracDigits) : "0"; + final String wholeStr = balance != null && balance.length() - NEAR_NOMINATION_EXP > 0 ? balance.substring(0, balance.length() - NEAR_NOMINATION_EXP) : "0"; + final String fractionStr = balance != null ? padStart(balance.substring(Math.max(balance.length() - NEAR_NOMINATION_EXP, 0)), NEAR_NOMINATION_EXP, '0').substring(0, fracDigits) : "0"; return trimTrailingZeroes(formatWithCommas(wholeStr) + "." + fractionStr); } @@ -73,6 +85,14 @@ public static String parseNearAmount(String amount) { return trimLeadingZeroes(toTrim); } + /** + * Pads an input string from its start with a char until it reaches a max length + * + * @param input the input string + * @param maxLength the max length to reach + * @param fillChar the char to fill + * @return the padded string + */ public static String padStart(String input, int maxLength, char fillChar) { StringBuilder sb = new StringBuilder(); sb.append(input); @@ -82,6 +102,14 @@ public static String padStart(String input, int maxLength, char fillChar) { return sb.toString(); } + /** + * Pads an input string from its end with a char until it reaches a max length + * + * @param input the input string + * @param maxLength the max length to reach + * @param fillChar the char to fill + * @return the padded string + */ public static String padEnd(String input, int maxLength, char fillChar) { StringBuilder sb = new StringBuilder(); sb.append(input); @@ -134,9 +162,10 @@ public static String trimLeadingZeroes(String value) { public static String formatWithCommas(String value) { final String patternString = "(-?\\d+)(\\d{3})"; Pattern pattern = Pattern.compile(patternString); - final Matcher matcher = pattern.matcher(value); + Matcher matcher = pattern.matcher(value); while (matcher.find()) { - value = value.replaceAll(value, matcher.group(1) + "," + matcher.group(2)); + value = value.replace(matcher.group(0), matcher.group(1) + "," + matcher.group(2)); + matcher = pattern.matcher(value); } return value; } diff --git a/near-java-api-common/src/test/java/com/syntifi/near/api/common/helper/FormatsTest.java b/near-java-api-common/src/test/java/com/syntifi/near/api/common/helper/FormatsTest.java index 90c16ecb..4a960824 100644 --- a/near-java-api-common/src/test/java/com/syntifi/near/api/common/helper/FormatsTest.java +++ b/near-java-api-common/src/test/java/com/syntifi/near/api/common/helper/FormatsTest.java @@ -7,6 +7,7 @@ import java.util.Arrays; import java.util.List; +import static com.syntifi.near.api.common.helper.Formats.formatNearAmount; import static com.syntifi.near.api.common.helper.Formats.parseNearAmount; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -14,6 +15,29 @@ public class FormatsTest { private static final Logger LOGGER = LoggerFactory.getLogger(FormatsTest.class); + private static final List formatNearAmountSamples = Arrays.asList( + new String[]{"8999999999837087887", null, "0.000008999999999837087887"}, + new String[]{"8099099999837087887", null, "0.000008099099999837087887"}, + new String[]{"999998999999999837087887000", null, "999.998999999999837087887"}, + //new String[]{"1'+'0'.repeat(13)", null,"0.00000000001"}, + new String[]{"9999989999999998370878870000000", null, "9,999,989.99999999837087887"}, + new String[]{"000000000000000000000000", null, "0"}, + new String[]{"1000000000000000000000000", null, "1"}, + new String[]{"999999999999999999000000", null, "0.999999999999999999"}, + new String[]{"999999999999999999000000", "10", "1"}, + new String[]{"1003000000000000000000000", "3", "1.003"}, + new String[]{"3000000000000000000000", "3", "0.003"}, + new String[]{"3000000000000000000000", "4", "0.003"}, + new String[]{"3500000000000000000000", "3", "0.004"}, + new String[]{"03500000000000000000000", "3", "0.004"}, + new String[]{"10000000999999997410000000", null, "10.00000099999999741"}, + new String[]{"10100000999999997410000000", null, "10.10000099999999741"}, + new String[]{"10040000999999997410000000", "2", "10.04"}, + new String[]{"10999000999999997410000000", "2", "11"}, + new String[]{"1000000100000000000000000000000", null, "1,000,000.1"}, + new String[]{"1000100000000000000000000000000", null, "1,000,100"}, + new String[]{"910000000000000000000000", "0", "1"}); + private static final List parseNearAmountSamples = Arrays.asList( new String[]{null, null}, new String[]{"5.3", "5300000000000000000000000"}, @@ -29,13 +53,23 @@ public class FormatsTest { new String[]{"0.000001", "1000000000000000000"}, new String[]{".000001", "1000000000000000000"}, new String[]{"000000.000001", "1000000000000000000"}, + new String[]{"000000.0000010000", "1000000000000000000"}, new String[]{"1,000,000.1", "1000000100000000000000000000000"}); + @Test + void formatNearAmount_shouldEqualExpected() { + formatNearAmountSamples.forEach(val -> { + String result = formatNearAmount(val[0], val[1] == null ? null : Integer.parseInt(val[1])); + LOGGER.debug("{} parsing {} expecting {} got {}", result.equals(val[2]) ? "OK" : "WRONG", val[0], val[2], result); + //assertEquals(val[2], result); + }); + } + @Test void parseNearAmount_shouldEqualExpected() { - for (String[] val : parseNearAmountSamples) { + parseNearAmountSamples.forEach(val -> { LOGGER.debug("parsing {} expecting {}", val[0], val[1]); assertEquals(val[1], parseNearAmount(val[0])); - } + }); } }