1// © 2017 and later: Unicode, Inc. and others. 2// License & terms of use: http://www.unicode.org/copyright.html#License 3package com.ibm.icu.impl.number; 4 5import java.math.BigDecimal; 6import java.math.BigInteger; 7 8/** 9 * A DecimalQuantity with internal storage as a 64-bit BCD, with fallback to a byte array 10 * for numbers that don't fit into the standard BCD. 11 */ 12public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_AbstractBCD { 13 14 /** 15 * The BCD of the 16 digits of the number represented by this object. Every 4 bits of the long map 16 * to one digit. For example, the number "12345" in BCD is "0x12345". 17 * 18 * <p>Whenever bcd changes internally, {@link #compact()} must be called, except in special cases 19 * like setting the digit to zero. 20 */ 21 private byte[] bcdBytes; 22 23 private long bcdLong = 0L; 24 25 private boolean usingBytes = false; 26 27 @Override 28 public int maxRepresentableDigits() { 29 return Integer.MAX_VALUE; 30 } 31 32 public DecimalQuantity_DualStorageBCD() { 33 setBcdToZero(); 34 flags = 0; 35 } 36 37 public DecimalQuantity_DualStorageBCD(long input) { 38 setToLong(input); 39 } 40 41 public DecimalQuantity_DualStorageBCD(int input) { 42 setToInt(input); 43 } 44 45 public DecimalQuantity_DualStorageBCD(double input) { 46 setToDouble(input); 47 } 48 49 public DecimalQuantity_DualStorageBCD(BigInteger input) { 50 setToBigInteger(input); 51 } 52 53 public DecimalQuantity_DualStorageBCD(BigDecimal input) { 54 setToBigDecimal(input); 55 } 56 57 public DecimalQuantity_DualStorageBCD(DecimalQuantity_DualStorageBCD other) { 58 copyFrom(other); 59 } 60 61 public DecimalQuantity_DualStorageBCD(Number number) { 62 if (number instanceof Long) { 63 setToLong(number.longValue()); 64 } else if (number instanceof Integer) { 65 setToInt(number.intValue()); 66 } else if (number instanceof Double) { 67 setToDouble(number.doubleValue()); 68 } else if (number instanceof BigInteger) { 69 setToBigInteger((BigInteger) number); 70 } else if (number instanceof BigDecimal) { 71 setToBigDecimal((BigDecimal) number); 72 } else if (number instanceof com.ibm.icu.math.BigDecimal) { 73 setToBigDecimal(((com.ibm.icu.math.BigDecimal) number).toBigDecimal()); 74 } else { 75 throw new IllegalArgumentException( 76 "Number is of an unsupported type: " + number.getClass().getName()); 77 } 78 } 79 80 @Override 81 public DecimalQuantity createCopy() { 82 return new DecimalQuantity_DualStorageBCD(this); 83 } 84 85 @Override 86 protected byte getDigitPos(int position) { 87 if (usingBytes) { 88 if (position < 0 || position > precision) return 0; 89 return bcdBytes[position]; 90 } else { 91 if (position < 0 || position >= 16) return 0; 92 return (byte) ((bcdLong >>> (position * 4)) & 0xf); 93 } 94 } 95 96 @Override 97 protected void setDigitPos(int position, byte value) { 98 assert position >= 0; 99 if (usingBytes) { 100 ensureCapacity(position + 1); 101 bcdBytes[position] = value; 102 } else if (position >= 16) { 103 switchStorage(); 104 ensureCapacity(position + 1); 105 bcdBytes[position] = value; 106 } else { 107 int shift = position * 4; 108 bcdLong = bcdLong & ~(0xfL << shift) | ((long) value << shift); 109 } 110 } 111 112 @Override 113 protected void shiftLeft(int numDigits) { 114 if (!usingBytes && precision + numDigits > 16) { 115 switchStorage(); 116 } 117 if (usingBytes) { 118 ensureCapacity(precision + numDigits); 119 int i = precision + numDigits - 1; 120 for (; i >= numDigits; i--) { 121 bcdBytes[i] = bcdBytes[i - numDigits]; 122 } 123 for (; i >= 0; i--) { 124 bcdBytes[i] = 0; 125 } 126 } else { 127 bcdLong <<= (numDigits * 4); 128 } 129 scale -= numDigits; 130 precision += numDigits; 131 } 132 133 @Override 134 protected void shiftRight(int numDigits) { 135 if (usingBytes) { 136 int i = 0; 137 for (; i < precision - numDigits; i++) { 138 bcdBytes[i] = bcdBytes[i + numDigits]; 139 } 140 for (; i < precision; i++) { 141 bcdBytes[i] = 0; 142 } 143 } else { 144 bcdLong >>>= (numDigits * 4); 145 } 146 scale += numDigits; 147 precision -= numDigits; 148 } 149 150 @Override 151 protected void setBcdToZero() { 152 if (usingBytes) { 153 bcdBytes = null; 154 usingBytes = false; 155 } 156 bcdLong = 0L; 157 scale = 0; 158 precision = 0; 159 isApproximate = false; 160 origDouble = 0; 161 origDelta = 0; 162 } 163 164 @Override 165 protected void readIntToBcd(int n) { 166 assert n != 0; 167 // ints always fit inside the long implementation. 168 long result = 0L; 169 int i = 16; 170 for (; n != 0; n /= 10, i--) { 171 result = (result >>> 4) + (((long) n % 10) << 60); 172 } 173 assert !usingBytes; 174 bcdLong = result >>> (i * 4); 175 scale = 0; 176 precision = 16 - i; 177 } 178 179 @Override 180 protected void readLongToBcd(long n) { 181 assert n != 0; 182 if (n >= 10000000000000000L) { 183 ensureCapacity(); 184 int i = 0; 185 for (; n != 0L; n /= 10L, i++) { 186 bcdBytes[i] = (byte) (n % 10); 187 } 188 assert usingBytes; 189 scale = 0; 190 precision = i; 191 } else { 192 long result = 0L; 193 int i = 16; 194 for (; n != 0L; n /= 10L, i--) { 195 result = (result >>> 4) + ((n % 10) << 60); 196 } 197 assert i >= 0; 198 assert !usingBytes; 199 bcdLong = result >>> (i * 4); 200 scale = 0; 201 precision = 16 - i; 202 } 203 } 204 205 @Override 206 protected void readBigIntegerToBcd(BigInteger n) { 207 assert n.signum() != 0; 208 ensureCapacity(); // allocate initial byte array 209 int i = 0; 210 for (; n.signum() != 0; i++) { 211 BigInteger[] temp = n.divideAndRemainder(BigInteger.TEN); 212 ensureCapacity(i + 1); 213 bcdBytes[i] = temp[1].byteValue(); 214 n = temp[0]; 215 } 216 scale = 0; 217 precision = i; 218 } 219 220 @Override 221 protected BigDecimal bcdToBigDecimal() { 222 if (usingBytes) { 223 // Converting to a string here is faster than doing BigInteger/BigDecimal arithmetic. 224 BigDecimal result = new BigDecimal(toNumberString()); 225 if (isNegative()) { 226 result = result.negate(); 227 } 228 return result; 229 } else { 230 long tempLong = 0L; 231 for (int shift = (precision - 1); shift >= 0; shift--) { 232 tempLong = tempLong * 10 + getDigitPos(shift); 233 } 234 BigDecimal result = BigDecimal.valueOf(tempLong); 235 result = result.scaleByPowerOfTen(scale); 236 if (isNegative()) result = result.negate(); 237 return result; 238 } 239 } 240 241 @Override 242 protected void compact() { 243 if (usingBytes) { 244 int delta = 0; 245 for (; delta < precision && bcdBytes[delta] == 0; delta++) ; 246 if (delta == precision) { 247 // Number is zero 248 setBcdToZero(); 249 return; 250 } else { 251 // Remove trailing zeros 252 shiftRight(delta); 253 } 254 255 // Compute precision 256 int leading = precision - 1; 257 for (; leading >= 0 && bcdBytes[leading] == 0; leading--) ; 258 precision = leading + 1; 259 260 // Switch storage mechanism if possible 261 if (precision <= 16) { 262 switchStorage(); 263 } 264 265 } else { 266 if (bcdLong == 0L) { 267 // Number is zero 268 setBcdToZero(); 269 return; 270 } 271 272 // Compact the number (remove trailing zeros) 273 int delta = Long.numberOfTrailingZeros(bcdLong) / 4; 274 bcdLong >>>= delta * 4; 275 scale += delta; 276 277 // Compute precision 278 precision = 16 - (Long.numberOfLeadingZeros(bcdLong) / 4); 279 } 280 } 281 282 /** Ensure that a byte array of at least 40 digits is allocated. */ 283 private void ensureCapacity() { 284 ensureCapacity(40); 285 } 286 287 private void ensureCapacity(int capacity) { 288 if (capacity == 0) return; 289 int oldCapacity = usingBytes ? bcdBytes.length : 0; 290 if (!usingBytes) { 291 bcdBytes = new byte[capacity]; 292 } else if (oldCapacity < capacity) { 293 byte[] bcd1 = new byte[capacity * 2]; 294 System.arraycopy(bcdBytes, 0, bcd1, 0, oldCapacity); 295 bcdBytes = bcd1; 296 } 297 usingBytes = true; 298 } 299 300 /** Switches the internal storage mechanism between the 64-bit long and the byte array. */ 301 private void switchStorage() { 302 if (usingBytes) { 303 // Change from bytes to long 304 bcdLong = 0L; 305 for (int i = precision - 1; i >= 0; i--) { 306 bcdLong <<= 4; 307 bcdLong |= bcdBytes[i]; 308 } 309 bcdBytes = null; 310 usingBytes = false; 311 } else { 312 // Change from long to bytes 313 ensureCapacity(); 314 for (int i = 0; i < precision; i++) { 315 bcdBytes[i] = (byte) (bcdLong & 0xf); 316 bcdLong >>>= 4; 317 } 318 assert usingBytes; 319 } 320 } 321 322 @Override 323 protected void copyBcdFrom(DecimalQuantity _other) { 324 DecimalQuantity_DualStorageBCD other = (DecimalQuantity_DualStorageBCD) _other; 325 setBcdToZero(); 326 if (other.usingBytes) { 327 ensureCapacity(other.precision); 328 System.arraycopy(other.bcdBytes, 0, bcdBytes, 0, other.precision); 329 } else { 330 bcdLong = other.bcdLong; 331 } 332 } 333 334 /** 335 * Checks whether the bytes stored in this instance are all valid. For internal unit testing only. 336 * 337 * @return An error message if this instance is invalid, or null if this instance is healthy. 338 * @internal 339 * @deprecated This API is for ICU internal use only. 340 */ 341 @Deprecated 342 public String checkHealth() { 343 if (usingBytes) { 344 if (bcdLong != 0) return "Value in bcdLong but we are in byte mode"; 345 if (precision == 0) return "Zero precision but we are in byte mode"; 346 if (precision > bcdBytes.length) return "Precision exceeds length of byte array"; 347 if (getDigitPos(precision - 1) == 0) return "Most significant digit is zero in byte mode"; 348 if (getDigitPos(0) == 0) return "Least significant digit is zero in long mode"; 349 for (int i = 0; i < precision; i++) { 350 if (getDigitPos(i) >= 10) return "Digit exceeding 10 in byte array"; 351 if (getDigitPos(i) < 0) return "Digit below 0 in byte array"; 352 } 353 for (int i = precision; i < bcdBytes.length; i++) { 354 if (getDigitPos(i) != 0) return "Nonzero digits outside of range in byte array"; 355 } 356 } else { 357 if (bcdBytes != null) { 358 for (int i = 0; i < bcdBytes.length; i++) { 359 if (bcdBytes[i] != 0) return "Nonzero digits in byte array but we are in long mode"; 360 } 361 } 362 if (precision == 0 && bcdLong != 0) return "Value in bcdLong even though precision is zero"; 363 if (precision > 16) return "Precision exceeds length of long"; 364 if (precision != 0 && getDigitPos(precision - 1) == 0) 365 return "Most significant digit is zero in long mode"; 366 if (precision != 0 && getDigitPos(0) == 0) 367 return "Least significant digit is zero in long mode"; 368 for (int i = 0; i < precision; i++) { 369 if (getDigitPos(i) >= 10) return "Digit exceeding 10 in long"; 370 if (getDigitPos(i) < 0) return "Digit below 0 in long (?!)"; 371 } 372 for (int i = precision; i < 16; i++) { 373 if (getDigitPos(i) != 0) return "Nonzero digits outside of range in long"; 374 } 375 } 376 377 return null; 378 } 379 380 /** 381 * Checks whether this {@link DecimalQuantity_DualStorageBCD} is using its internal byte array storage mechanism. 382 * 383 * @return true if an internal byte array is being used; false if a long is being used. 384 * @internal 385 * @deprecated This API is ICU internal only. 386 */ 387 @Deprecated 388 public boolean isUsingBytes() { 389 return usingBytes; 390 } 391 392 @Override 393 public String toString() { 394 return String.format( 395 "<DecimalQuantity %s:%d:%d:%s %s %s>", 396 (lOptPos > 1000 ? "999" : String.valueOf(lOptPos)), 397 lReqPos, 398 rReqPos, 399 (rOptPos < -1000 ? "-999" : String.valueOf(rOptPos)), 400 (usingBytes ? "bytes" : "long"), 401 toNumberString()); 402 } 403 404 public String toNumberString() { 405 StringBuilder sb = new StringBuilder(); 406 if (usingBytes) { 407 for (int i = precision - 1; i >= 0; i--) { 408 sb.append(bcdBytes[i]); 409 } 410 } else { 411 sb.append(Long.toHexString(bcdLong)); 412 } 413 sb.append("E"); 414 sb.append(scale); 415 return sb.toString(); 416 } 417} 418