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