|
1 | 1 | package com.cleanroommc.modularui.utils; |
2 | 2 |
|
3 | | -import org.apache.commons.lang3.StringUtils; |
4 | | - |
| 3 | +import java.math.RoundingMode; |
5 | 4 | import java.text.DecimalFormat; |
| 5 | +import java.text.DecimalFormatSymbols; |
6 | 6 |
|
| 7 | +/** |
| 8 | + * A number formatter for large, small, positive, negative, integers and decimals with adjustable format parameters. |
| 9 | + * Test results can be seen at test/java/com.cleanroommc.modularui.FormatTest. |
| 10 | + */ |
7 | 11 | public class NumberFormat { |
8 | 12 |
|
9 | | - public static final DecimalFormat FORMAT = new DecimalFormat("0.###"); |
| 13 | + public static final Params DEFAULT = paramsBuilder() |
| 14 | + .roundingMode(RoundingMode.HALF_UP) |
| 15 | + .maxLength(4) |
| 16 | + .considerMinusForLength(false) |
| 17 | + .considerDecimalSeparatorForLength(false) |
| 18 | + .considerOnlyDecimalsForLength(false) |
| 19 | + .considerSuffixForLength(true) |
| 20 | + .build(); |
| 21 | + |
| 22 | + public static final Params AMOUNT_TEXT = DEFAULT.copyToBuilder() |
| 23 | + .roundingMode(RoundingMode.DOWN) |
| 24 | + .build(); |
10 | 25 |
|
11 | 26 | private static final double[] FACTORS_HIGH = {1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21, 1e24}; |
12 | 27 | private static final double[] FACTORS_LOW = {1e-3, 1e-6, 1e-9, 1e-12, 1e-15, 1e-18, 1e-21, 1e-24}; |
| 28 | + //private static final double[] ALL_FACTORS = new double[FACTORS_HIGH.length + FACTORS_LOW.length]; |
13 | 29 | private static final String[] SUFFIX_HIGH = {"k", "M", "G", "T", "P", "E", "Z", "Y"}; |
14 | | - private static final String[] SUFFIX_LOW = {"m", "u", "n", "p", "f", "a", "z", "y"}; |
| 30 | + private static final String[] SUFFIX_LOW = {"m", "µ", "n", "p", "f", "a", "z", "y"}; |
| 31 | + private static final String NO_SUFFIX = ""; |
| 32 | + |
| 33 | + /*static { |
| 34 | + for (int i = 0; i < FACTORS_LOW.length; i++) { |
| 35 | + ALL_FACTORS[i] = FACTORS_LOW[FACTORS_LOW.length - 1 - i]; |
| 36 | + } |
| 37 | + System.arraycopy(FACTORS_HIGH, 0, ALL_FACTORS, FACTORS_LOW.length, FACTORS_HIGH.length); |
| 38 | + }*/ |
15 | 39 |
|
16 | | - public static String formatWithMaxDigits(double value) { |
17 | | - return format(value, 4, true); |
| 40 | + public static Params params(DecimalFormat format, int maxLength, boolean considerOnlyDecimalsForLength, boolean considerDecimalSeparatorForLength, boolean considerMinusForLength, boolean considerSuffixForLength) { |
| 41 | + return new Params(format, maxLength, considerOnlyDecimalsForLength, considerDecimalSeparatorForLength, considerMinusForLength, considerSuffixForLength); |
18 | 42 | } |
19 | 43 |
|
20 | | - public static String formatWithMaxDigits(double value, int maxDigits) { |
21 | | - return format(value, maxDigits, true); |
| 44 | + public static ParamsBuilder paramsBuilder() { |
| 45 | + return new ParamsBuilder(); |
22 | 46 | } |
23 | 47 |
|
24 | | - public static String formatWithMaxDecimals(double value, int decimals) { |
25 | | - return format(value, decimals, false); |
| 48 | + public static class Params { |
| 49 | + public final DecimalFormat format; |
| 50 | + public final int maxLength; |
| 51 | + public final boolean considerOnlyDecimalsForLength; |
| 52 | + public final boolean considerDecimalSeparatorForLength; |
| 53 | + public final boolean considerMinusForLength; |
| 54 | + public final boolean considerSuffixForLength; |
| 55 | + |
| 56 | + public Params(DecimalFormat format, int maxLength, boolean considerOnlyDecimalsForLength, boolean considerDecimalSeparatorForLength, boolean considerMinusForLength, boolean considerSuffixForLength) { |
| 57 | + this.format = format; |
| 58 | + this.maxLength = maxLength; |
| 59 | + this.considerOnlyDecimalsForLength = considerOnlyDecimalsForLength; |
| 60 | + this.considerDecimalSeparatorForLength = considerDecimalSeparatorForLength; |
| 61 | + this.considerMinusForLength = considerMinusForLength; |
| 62 | + this.considerSuffixForLength = considerSuffixForLength; |
| 63 | + if (!this.considerOnlyDecimalsForLength && this.maxLength < 4) { |
| 64 | + throw new IllegalArgumentException("Max length must be at least 4 characters"); |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + public ParamsBuilder copyToBuilder() { |
| 69 | + DecimalFormat format = (DecimalFormat) this.format.clone(); |
| 70 | + format.setMinimumFractionDigits(this.format.getMinimumFractionDigits()); |
| 71 | + format.setMaximumFractionDigits(this.format.getMaximumFractionDigits()); |
| 72 | + format.setMinimumIntegerDigits(this.format.getMinimumIntegerDigits()); |
| 73 | + format.setMaximumIntegerDigits(this.format.getMaximumIntegerDigits()); |
| 74 | + format.setRoundingMode(this.format.getRoundingMode()); |
| 75 | + format.setGroupingSize(this.format.getGroupingSize()); |
| 76 | + format.setGroupingUsed(this.format.isGroupingUsed()); |
| 77 | + return new ParamsBuilder() |
| 78 | + .format(format) |
| 79 | + .maxLength(this.maxLength) |
| 80 | + .considerOnlyDecimalsForLength(this.considerOnlyDecimalsForLength) |
| 81 | + .considerDecimalSeparatorForLength(this.considerDecimalSeparatorForLength) |
| 82 | + .considerMinusForLength(this.considerMinusForLength) |
| 83 | + .considerSuffixForLength(this.considerSuffixForLength); |
| 84 | + } |
26 | 85 | } |
27 | 86 |
|
28 | | - private static String format(double value, int precision, boolean maxDigits) { |
| 87 | + public static class ParamsBuilder { |
| 88 | + |
| 89 | + private DecimalFormat format; |
| 90 | + private int maxLength; |
| 91 | + private boolean considerOnlyDecimalsForLength; |
| 92 | + private boolean considerDecimalSeparatorForLength; |
| 93 | + private boolean considerMinusForLength; |
| 94 | + private boolean considerSuffixForLength; |
| 95 | + |
| 96 | + private DecimalFormat checkFormat() { |
| 97 | + if (this.format == null) { |
| 98 | + this.format = new DecimalFormat("0.###"); |
| 99 | + } |
| 100 | + return this.format; |
| 101 | + } |
| 102 | + |
| 103 | + public ParamsBuilder format(DecimalFormat format) { |
| 104 | + this.format = format; |
| 105 | + return this; |
| 106 | + } |
| 107 | + |
| 108 | + public ParamsBuilder roundingMode(RoundingMode roundingMode) { |
| 109 | + checkFormat().setRoundingMode(roundingMode); |
| 110 | + return this; |
| 111 | + } |
| 112 | + |
| 113 | + public ParamsBuilder decimalFormatSymbols(DecimalFormatSymbols symbols) { |
| 114 | + checkFormat().setDecimalFormatSymbols(symbols); |
| 115 | + return this; |
| 116 | + } |
| 117 | + |
| 118 | + public ParamsBuilder decimalSeparator(char c) { |
| 119 | + DecimalFormatSymbols symbols = checkFormat().getDecimalFormatSymbols(); |
| 120 | + symbols.setDecimalSeparator('.'); |
| 121 | + checkFormat().setDecimalFormatSymbols(symbols); |
| 122 | + return this; |
| 123 | + } |
| 124 | + |
| 125 | + public ParamsBuilder maxLength(int maxLength) { |
| 126 | + this.maxLength = maxLength; |
| 127 | + return this; |
| 128 | + } |
| 129 | + |
| 130 | + public ParamsBuilder considerOnlyDecimalsForLength(boolean considerOnlyDecimalsForLength) { |
| 131 | + this.considerOnlyDecimalsForLength = considerOnlyDecimalsForLength; |
| 132 | + return this; |
| 133 | + } |
| 134 | + |
| 135 | + public ParamsBuilder considerDecimalSeparatorForLength(boolean considerDecimalSeparatorForLength) { |
| 136 | + this.considerDecimalSeparatorForLength = considerDecimalSeparatorForLength; |
| 137 | + return this; |
| 138 | + } |
| 139 | + |
| 140 | + public ParamsBuilder considerMinusForLength(boolean considerMinusForLength) { |
| 141 | + this.considerMinusForLength = considerMinusForLength; |
| 142 | + return this; |
| 143 | + } |
| 144 | + |
| 145 | + public ParamsBuilder considerSuffixForLength(boolean considerSuffixForLength) { |
| 146 | + this.considerSuffixForLength = considerSuffixForLength; |
| 147 | + return this; |
| 148 | + } |
| 149 | + |
| 150 | + public Params build() { |
| 151 | + return new Params(checkFormat(), this.maxLength, this.considerOnlyDecimalsForLength, this.considerDecimalSeparatorForLength, this.considerMinusForLength, this.considerSuffixForLength); |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + public static String format(double number, Params params) { |
| 156 | + boolean negative = number < 0; |
| 157 | + int maxLength = params.maxLength; |
| 158 | + if (negative) { |
| 159 | + number = -number; |
| 160 | + if (params.considerMinusForLength) maxLength--; |
| 161 | + } |
| 162 | + String formattedNumber = formatInternal(number, maxLength, params); |
| 163 | + if (negative) formattedNumber = "-" + formattedNumber; |
| 164 | + return formattedNumber; |
| 165 | + } |
| 166 | + |
| 167 | + private static String formatInternal(double number, int maxLength, Params params) { |
29 | 168 | int n = FACTORS_HIGH.length - 1; |
30 | | - if (value >= 1000) { |
| 169 | + String suffix = NO_SUFFIX; |
| 170 | + if (number > 9999) { |
31 | 171 | int index; |
32 | 172 | for (index = 0; index < n; index++) { |
33 | | - if (value < FACTORS_HIGH[index + 1]) { |
| 173 | + if (number < FACTORS_HIGH[index + 1]) { |
34 | 174 | break; |
35 | 175 | } |
36 | 176 | } |
37 | | - return formatToString(value / FACTORS_HIGH[index], precision, maxDigits, SUFFIX_HIGH[index]); |
38 | | - } |
39 | | - if (value < 1) { |
| 177 | + number /= FACTORS_HIGH[index]; |
| 178 | + suffix = SUFFIX_HIGH[index]; |
| 179 | + } else if (number < 1) { |
40 | 180 | int index; |
41 | 181 | for (index = 0; index < n; index++) { |
42 | | - if (value >= FACTORS_LOW[index]) { |
| 182 | + if (number >= FACTORS_LOW[index]) { |
43 | 183 | break; |
44 | 184 | } |
45 | 185 | } |
46 | | - return formatToString(value / FACTORS_LOW[index], precision, maxDigits, SUFFIX_LOW[index]); |
| 186 | + number /= FACTORS_LOW[index]; |
| 187 | + suffix = SUFFIX_LOW[index]; |
47 | 188 | } |
48 | | - return formatToString(value, precision, maxDigits, StringUtils.EMPTY); |
| 189 | + return formatToString(number, suffix, maxLength, params); |
49 | 190 | } |
50 | 191 |
|
51 | | - private static String formatToString(double value, int precision, boolean maxDigits, String suffix) { |
52 | | - if (maxDigits) { |
53 | | - String[] parts = String.valueOf(value).split("\\."); |
54 | | - if (parts.length > 1) { |
55 | | - precision -= parts[0].length(); |
56 | | - } |
| 192 | + private static String formatToString(double value, String suffix, int maxLength, Params params) { |
| 193 | + if (params.considerSuffixForLength && !suffix.isEmpty()) { |
| 194 | + maxLength -= suffix.length(); |
57 | 195 | } |
58 | | - FORMAT.setMaximumFractionDigits(precision); |
59 | | - return FORMAT.format(value) + suffix; |
60 | | - } |
61 | | - |
62 | | - public static double getFactorForSuffix(char suffix) { |
63 | | - for (int i = 0; i < SUFFIX_HIGH.length; i++) { |
64 | | - String s = SUFFIX_HIGH[i]; |
65 | | - if (s.charAt(0) == suffix) return FACTORS_HIGH[i]; |
| 196 | + if (params.considerDecimalSeparatorForLength) { |
| 197 | + maxLength--; |
66 | 198 | } |
67 | | - for (int i = 0; i < SUFFIX_LOW.length; i++) { |
68 | | - String s = SUFFIX_LOW[i]; |
69 | | - if (s.charAt(0) == suffix) return FACTORS_LOW[i]; |
| 199 | + if (!params.considerOnlyDecimalsForLength) { |
| 200 | + if (value % 1 > 0) { |
| 201 | + int intDigits = (int) (Math.log10(Math.floor(value)) + 1); |
| 202 | + maxLength -= intDigits; |
| 203 | + } |
70 | 204 | } |
71 | | - return 0; |
72 | | - } |
73 | | - |
74 | | - public static double getFactorForSuffix(String suffix) { |
75 | | - return getFactorForSuffix(suffix.charAt(0)); |
| 205 | + int m1 = params.format.getMaximumFractionDigits(); |
| 206 | + int m2 = params.format.getMinimumFractionDigits(); |
| 207 | + params.format.setMaximumFractionDigits(maxLength); |
| 208 | + String s = params.format.format(value) + suffix; |
| 209 | + params.format.setMaximumFractionDigits(m1); |
| 210 | + params.format.setMinimumFractionDigits(m2); |
| 211 | + return s; |
76 | 212 | } |
77 | 213 | } |
0 commit comments