-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
Decimal128.java
485 lines (418 loc) · 17.5 KB
/
Decimal128.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
/*
* Copyright 2016 MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bson.types;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashSet;
import java.util.Set;
import static java.math.MathContext.DECIMAL128;
import static java.util.Arrays.asList;
/**
* A binary integer decimal representation of a 128-bit decimal value, supporting 34 decimal digits of significand and an exponent range
* of -6143 to +6144.
*
* @since 3.4
* @see <a href="https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst">BSON Decimal128
* specification</a>
* @see <a href="https://en.wikipedia.org/wiki/Binary_Integer_Decimal">binary integer decimal</a>
* @see <a href="https://en.wikipedia.org/wiki/Decimal128_floating-point_format">decimal128 floating-point format</a>
* @see <a href="http://ieeexplore.ieee.org/document/4610935/">754-2008 - IEEE Standard for Floating-Point Arithmetic</a>
*/
public final class Decimal128 implements Serializable {
private static final long serialVersionUID = 4570973266503637887L;
private static final long INFINITY_MASK = 0x7800000000000000L;
private static final long NaN_MASK = 0x7c00000000000000L;
private static final long SIGN_BIT_MASK = 1L << 63;
private static final int MIN_EXPONENT = -6176;
private static final int MAX_EXPONENT = 6111;
private static final int EXPONENT_OFFSET = 6176;
private static final int MAX_BIT_LENGTH = 113;
private static final BigInteger BIG_INT_TEN = new BigInteger("10");
private static final BigInteger BIG_INT_ONE = new BigInteger("1");
private static final BigInteger BIG_INT_ZERO = new BigInteger("0");
private static final Set<String> NaN_STRINGS = new HashSet<String>(asList("nan"));
private static final Set<String> NEGATIVE_NaN_STRINGS = new HashSet<String>(asList("-nan"));
private static final Set<String> POSITIVE_INFINITY_STRINGS = new HashSet<String>(asList("inf", "+inf", "infinity", "+infinity"));
private static final Set<String> NEGATIVE_INFINITY_STRINGS = new HashSet<String>(asList("-inf", "-infinity"));
/**
* A constant holding the positive infinity of type {@code Decimal128}. It is equal to the value return by
* {@code Decimal128.valueOf("Infinity")}.
*/
public static final Decimal128 POSITIVE_INFINITY = fromIEEE754BIDEncoding(INFINITY_MASK, 0);
/**
* A constant holding the negative infinity of type {@code Decimal128}. It is equal to the value return by
* {@code Decimal128.valueOf("-Infinity")}.
*/
public static final Decimal128 NEGATIVE_INFINITY = fromIEEE754BIDEncoding(INFINITY_MASK | SIGN_BIT_MASK, 0);
/**
* A constant holding a negative Not-a-Number (-NaN) value of type {@code Decimal128}. It is equal to the value return by
* {@code Decimal128.valueOf("-NaN")}.
*/
public static final Decimal128 NEGATIVE_NaN = fromIEEE754BIDEncoding(NaN_MASK | SIGN_BIT_MASK, 0);
/**
* A constant holding a Not-a-Number (NaN) value of type {@code Decimal128}. It is equal to the value return by
* {@code Decimal128.valueOf("NaN")}.
*/
public static final Decimal128 NaN = fromIEEE754BIDEncoding(NaN_MASK, 0);
/**
* A constant holding a postive zero value of type {@code Decimal128}. It is equal to the value return by
* {@code Decimal128.valueOf("0")}.
*/
public static final Decimal128 POSITIVE_ZERO = fromIEEE754BIDEncoding(0x3040000000000000L, 0x0000000000000000L);
/**
* A constant holding a negative zero value of type {@code Decimal128}. It is equal to the value return by
* {@code Decimal128.valueOf("-0")}.
*/
public static final Decimal128 NEGATIVE_ZERO = fromIEEE754BIDEncoding(0xb040000000000000L, 0x0000000000000000L);
private final long high;
private final long low;
/**
* Returns a Decimal128 value representing the given String.
*
* @param value the Decimal128 value represented as a String
* @return the Decimal128 value representing the given String
* @throws NumberFormatException if the value is out of the Decimal128 range
* @see
* <a href="https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst#from-string-representation">
* From-String Specification</a>
*/
public static Decimal128 parse(final String value) {
String lowerCasedValue = value.toLowerCase();
if (NaN_STRINGS.contains(lowerCasedValue)) {
return NaN;
}
if (NEGATIVE_NaN_STRINGS.contains(lowerCasedValue)) {
return NEGATIVE_NaN;
}
if (POSITIVE_INFINITY_STRINGS.contains(lowerCasedValue)) {
return POSITIVE_INFINITY;
}
if (NEGATIVE_INFINITY_STRINGS.contains(lowerCasedValue)) {
return NEGATIVE_INFINITY;
}
return new Decimal128(new BigDecimal(value), value.charAt(0) == '-');
}
/**
* Create an instance with the given high and low order bits representing this Decimal128 as an IEEE 754-2008 128-bit decimal
* floating point using the BID encoding scheme.
*
* @param high the high-order 64 bits
* @param low the low-order 64 bits
* @return the Decimal128 value representing the given high and low order bits
*/
public static Decimal128 fromIEEE754BIDEncoding(final long high, final long low) {
return new Decimal128(high, low);
}
/**
* Constructs a Decimal128 value representing the given long.
*
* @param value the Decimal128 value represented as a long
*/
public Decimal128(final long value) {
this(new BigDecimal(value, DECIMAL128));
}
/**
* Constructs a Decimal128 value representing the given BigDecimal.
*
* @param value the Decimal128 value represented as a BigDecimal
* @throws NumberFormatException if the value is out of the Decimal128 range
*/
public Decimal128(final BigDecimal value) {
this(value, value.signum() == -1);
}
private Decimal128(final long high, final long low) {
this.high = high;
this.low = low;
}
// isNegative is necessary to detect -0, which can't be represented with a BigDecimal
private Decimal128(final BigDecimal initialValue, final boolean isNegative) {
long localHigh = 0;
long localLow = 0;
BigDecimal value = clampAndRound(initialValue);
long exponent = -value.scale();
if ((exponent < MIN_EXPONENT) || (exponent > MAX_EXPONENT)) {
throw new AssertionError("Exponent is out of range for Decimal128 encoding: " + exponent); }
if (value.unscaledValue().bitLength() > MAX_BIT_LENGTH) {
throw new AssertionError("Unscaled roundedValue is out of range for Decimal128 encoding:" + value.unscaledValue());
}
BigInteger significand = value.unscaledValue().abs();
int bitLength = significand.bitLength();
for (int i = 0; i < Math.min(64, bitLength); i++) {
if (significand.testBit(i)) {
localLow |= 1L << i;
}
}
for (int i = 64; i < bitLength; i++) {
if (significand.testBit(i)) {
localHigh |= 1L << (i - 64);
}
}
long biasedExponent = exponent + EXPONENT_OFFSET;
localHigh |= biasedExponent << 49;
if (value.signum() == -1 || isNegative) {
localHigh |= SIGN_BIT_MASK;
}
high = localHigh;
low = localLow;
}
private BigDecimal clampAndRound(final BigDecimal initialValue) {
BigDecimal value;
if (-initialValue.scale() > MAX_EXPONENT) {
int diff = -initialValue.scale() - MAX_EXPONENT;
if (initialValue.unscaledValue().equals(BIG_INT_ZERO)) {
value = new BigDecimal(initialValue.unscaledValue(), -MAX_EXPONENT);
} else if (diff + initialValue.precision() > 34) {
throw new NumberFormatException("Exponent is out of range for Decimal128 encoding of " + initialValue);
} else {
BigInteger multiplier = BIG_INT_TEN.pow(diff);
value = new BigDecimal(initialValue.unscaledValue().multiply(multiplier), initialValue.scale() + diff);
}
} else if (-initialValue.scale() < MIN_EXPONENT) {
// Increasing a very negative exponent may require decreasing precision, which is rounding
// Only round exactly (by removing precision that is all zeroes). An exception is thrown if the rounding would be inexact:
// Exact: .000...0011000 => 11000E-6177 => 1100E-6176 => .000001100
// Inexact: .000...0011001 => 11001E-6177 => 1100E-6176 => .000001100
int diff = initialValue.scale() + MIN_EXPONENT;
int undiscardedPrecision = ensureExactRounding(initialValue, diff);
BigInteger divisor = undiscardedPrecision == 0 ? BIG_INT_ONE : BIG_INT_TEN.pow(diff);
value = new BigDecimal(initialValue.unscaledValue().divide(divisor), initialValue.scale() - diff);
} else {
value = initialValue.round(DECIMAL128);
int extraPrecision = initialValue.precision() - value.precision();
if (extraPrecision > 0) {
// Again, only round exactly
ensureExactRounding(initialValue, extraPrecision);
}
}
return value;
}
private int ensureExactRounding(final BigDecimal initialValue, final int extraPrecision) {
String significand = initialValue.unscaledValue().abs().toString();
int undiscardedPrecision = Math.max(0, significand.length() - extraPrecision);
for (int i = undiscardedPrecision; i < significand.length(); i++) {
if (significand.charAt(i) != '0') {
throw new NumberFormatException("Conversion to Decimal128 would require inexact rounding of " + initialValue);
}
}
return undiscardedPrecision;
}
/**
* Gets the high-order 64 bits of the IEEE 754-2008 128-bit decimal floating point encoding for this Decimal128, using the BID encoding
* scheme.
*
* @return the high-order 64 bits of this Decimal128
*/
public long getHigh() {
return high;
}
/**
* Gets the low-order 64 bits of the IEEE 754-2008 128-bit decimal floating point encoding for this Decimal128, using the BID encoding
* scheme.
*
* @return the low-order 64 bits of this Decimal128
*/
public long getLow() {
return low;
}
/**
* Gets a BigDecimal that is equivalent to this Decimal128.
*
* @return a BigDecimal that is equivalent to this Decimal128
* @throws ArithmeticException if the Decimal128 value is NaN, Infinity, -Infinity, or -0, none of which can be represented as a
* BigDecimal
*/
public BigDecimal bigDecimalValue() {
if (isNaN()) {
throw new ArithmeticException("NaN can not be converted to a BigDecimal");
}
if (isInfinite()) {
throw new ArithmeticException("Infinity can not be converted to a BigDecimal");
}
BigDecimal bigDecimal = bigDecimalValueNoNegativeZeroCheck();
// If the BigDecimal is 0, but the Decimal128 is negative, that means we have -0.
if (isNegative() && bigDecimal.signum() == 0) {
throw new ArithmeticException("Negative zero can not be converted to a BigDecimal");
}
return bigDecimal;
}
private BigDecimal bigDecimalValueNoNegativeZeroCheck() {
int scale = -getExponent();
if (twoHighestCombinationBitsAreSet()) {
return BigDecimal.valueOf(0, scale);
}
return new BigDecimal(new BigInteger(isNegative() ? -1 : 1, getBytes()), scale);
}
// May have leading zeros. Strip them before considering making this method public
private byte[] getBytes() {
byte[] bytes = new byte[15];
long mask = 0x00000000000000ff;
for (int i = 14; i >= 7; i--) {
bytes[i] = (byte) ((low & mask) >>> ((14 - i) << 3));
mask = mask << 8;
}
mask = 0x00000000000000ff;
for (int i = 6; i >= 1; i--) {
bytes[i] = (byte) ((high & mask) >>> ((6 - i) << 3));
mask = mask << 8;
}
mask = 0x0001000000000000L;
bytes[0] = (byte) ((high & mask) >>> 48);
return bytes;
}
// Consider making this method public
int getExponent() {
if (twoHighestCombinationBitsAreSet()) {
return (int) ((high & 0x1fffe00000000000L) >>> 47) - EXPONENT_OFFSET;
} else {
return (int) ((high & 0x7fff800000000000L) >>> 49) - EXPONENT_OFFSET;
}
}
private boolean twoHighestCombinationBitsAreSet() {
return (high & 3L << 61) == 3L << 61;
}
/**
* Returns true if this Decimal128 is negative.
*
* @return true if this Decimal128 is negative
*/
public boolean isNegative() {
return (high & SIGN_BIT_MASK) == SIGN_BIT_MASK;
}
/**
* Returns true if this Decimal128 is infinite.
*
* @return true if this Decimal128 is infinite
*/
public boolean isInfinite() {
return (high & INFINITY_MASK) == INFINITY_MASK;
}
/**
* Returns true if this Decimal128 is finite.
*
* @return true if this Decimal128 is finite
*/
public boolean isFinite() {
return !isInfinite();
}
/**
* Returns true if this Decimal128 is Not-A-Number (NaN).
*
* @return true if this Decimal128 is Not-A-Number
*/
public boolean isNaN() {
return (high & NaN_MASK) == NaN_MASK;
}
/**
* Returns true if the encoded representation of this instance is the same as the encoded representation of {@code o}.
* <p>
* One consequence is that, whereas {@code Double.NaN != Double.NaN},
* {@code new Decimal128("NaN").equals(new Decimal128("NaN")} returns true.
* </p>
* <p>
* Another consequence is that, as with BigDecimal, {@code new Decimal128("1.0").equals(new Decimal128("1.00")} returns false,
* because the precision is not the same and therefore the representation is not the same.
* </p>
*
* @param o the object to compare for equality
* @return true if the instances are equal
*/
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Decimal128 that = (Decimal128) o;
if (high != that.high) {
return false;
}
if (low != that.low) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = (int) (low ^ (low >>> 32));
result = 31 * result + (int) (high ^ (high >>> 32));
return result;
}
/**
* Returns the String representation of the Decimal128 value.
*
* @return the String representation
* @see <a href="https://github.com/mongodb/specifications/blob/master/source/bson-decimal128/decimal128.rst#to-string-representation">
* To-String Sprecification</a>
*/
@Override
public String toString() {
if (isNaN()) {
return "NaN";
}
if (isInfinite()) {
if (isNegative()) {
return "-Infinity";
} else {
return "Infinity";
}
}
return toStringWithBigDecimal();
}
private String toStringWithBigDecimal() {
StringBuilder buffer = new StringBuilder();
BigDecimal bigDecimal = bigDecimalValueNoNegativeZeroCheck();
String significand = bigDecimal.unscaledValue().abs().toString();
if (isNegative()) {
buffer.append('-');
}
int exponent = -bigDecimal.scale();
int adjustedExponent = exponent + (significand.length() - 1);
if (exponent <= 0 && adjustedExponent >= -6) {
if (exponent == 0) {
buffer.append(significand);
} else {
int pad = -exponent - significand.length();
if (pad >= 0) {
buffer.append('0');
buffer.append('.');
for (int i = 0; i < pad; i++) {
buffer.append('0');
}
buffer.append(significand, 0, significand.length());
} else {
buffer.append(significand, 0, -pad);
buffer.append('.');
buffer.append(significand, -pad, -pad - exponent);
}
}
} else {
buffer.append(significand.charAt(0));
if (significand.length() > 1) {
buffer.append('.');
buffer.append(significand, 1, significand.length());
}
buffer.append('E');
if (adjustedExponent > 0) {
buffer.append('+');
}
buffer.append(adjustedExponent);
}
return buffer.toString();
}
}