• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

unitsofmeasurement / indriya / 2332

12 Jun 2025 09:48PM UTC coverage: 71.282% (+0.005%) from 71.277%
2332

push

circleci

keilw
Merge branch 'master' of https://github.com/unitsofmeasurement/indriya.git

8 of 8 new or added lines in 1 file covered. (100.0%)

91 existing lines in 2 files now uncovered.

3753 of 5265 relevant lines covered (71.28%)

0.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.85
/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java
1
/*
2
 * Units of Measurement Reference Implementation
3
 * Copyright (c) 2005-2025, Jean-Marie Dautelle, Werner Keil, Otavio Santana.
4
 *
5
 * All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without modification,
8
 * are permitted provided that the following conditions are met:
9
 *
10
 * 1. Redistributions of source code must retain the above copyright notice,
11
 *    this list of conditions and the following disclaimer.
12
 *
13
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
14
 *    and the following disclaimer in the documentation and/or other materials provided with the distribution.
15
 *
16
 * 3. Neither the name of JSR-385, Indriya nor the names of their contributors may be used to endorse or promote products
17
 *    derived from this software without specific prior written permission.
18
 *
19
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
 */
30
package tech.units.indriya.function;
31

32
import java.math.BigDecimal;
33
import java.math.BigInteger;
34
import java.math.MathContext;
35
import java.math.RoundingMode;
36
import java.util.concurrent.atomic.AtomicInteger;
37
import java.util.concurrent.atomic.AtomicLong;
38
import java.util.function.UnaryOperator;
39

40
import tech.units.indriya.spi.NumberSystem;
41

42
/**
43
 * {@link NumberSystem} implementation to support Java's built-in {@link Number}s and the
44
 * {@link RationalNumber} type.
45
 *
46
 * @author Andi Huber
47
 * @author Werner Keil
48
 * @since 2.0
49
 */
50
public class DefaultNumberSystem implements NumberSystem {
1✔
51

52
    static final double MAX_LONG_AS_DOUBLE = Long.MAX_VALUE;
53
    static final double MIN_LONG_AS_DOUBLE = Long.MIN_VALUE;
54

55
    /**
56
     *  In order of increasing number type 'widening'.
57
     */
58
    private enum NumberType {
1✔
59

60
        // integer types
61
        BYTE_BOXED(true, Byte.class, (byte)1, (byte)0),
1✔
62
        SHORT_BOXED(true, Short.class, (short)1, (short)0),
1✔
63
        INTEGER_BOXED(true, Integer.class, 1, 0),
1✔
64
        INTEGER_ATOMIC(true, AtomicInteger.class, 1, 0) {
1✔
65
            @Override boolean isZero(Number number) {
66
                return ((AtomicInteger) number).intValue() == 0;
1✔
67
            }
68
        },
69
        LONG_BOXED(true, Long.class, 1L, 0L),
1✔
70
        LONG_ATOMIC(true, AtomicLong.class, 1L, 0) {
1✔
71
            @Override boolean isZero(Number number) {
72
                return ((AtomicLong) number).longValue() == 0L;
1✔
73
            }
74
        },
75
        BIG_INTEGER(true, BigInteger.class, BigInteger.ONE, BigInteger.ZERO) {
1✔
76
            @Override boolean isZero(Number number) {
77
                return ((BigInteger) number).signum() == 0;
1✔
78
            }
79
        },
80

81
        // rational types
82
        RATIONAL(false, RationalNumber.class, RationalNumber.ONE, RationalNumber.ZERO) {
1✔
83
            @Override boolean isZero(Number number) {
84
                return ((RationalNumber) number).signum() == 0;
1✔
85
            }
86
        },
87

88
        // fractional types
89
        FLOAT_BOXED(false, Float.class, 1.f, 0.f),
1✔
90
        DOUBLE_BOXED(false, Double.class, 1.d, 0.d),
1✔
91
        BIG_DECIMAL(false, BigDecimal.class, BigDecimal.ONE, BigDecimal.ZERO) {
1✔
92
            @Override boolean isZero(Number number) {
93
                return ((BigDecimal) number).signum() == 0;
1✔
94
            }
95
        },
96

97
        ;
98
        private final boolean integerOnly;
99
        private final Class<? extends Number> type;
100
        private final Number one;
101
        private final Number zero;
102

103
        private NumberType(final boolean integerOnly, final Class<? extends Number> type,
104
                final Number one, final Number zero) {
1✔
105

106
            this.integerOnly = integerOnly;
1✔
107
            this.type = type;
1✔
108
            this.one = one;
1✔
109
            this.zero = zero;
1✔
110
        }
1✔
111

112
        /**
113
         * Whether the underlying number type can only represent integers.
114
         * <p>
115
         * If <code>false</code> it can also represent fractional numbers.
116
         */
117
        public boolean isIntegerOnly() {
118
            return integerOnly;
1✔
119
        }
120

121
        @SuppressWarnings("unused")
122
        public Class<? extends Number> getType() {
UNCOV
123
            return type;
×
124
        }
125

126
        // 'hardcoded' for performance reasons
127
        static NumberType valueOf(final Number number) {
128
            if(number instanceof Long) {
1✔
129
                return LONG_BOXED;
1✔
130
            }
131
            if(number instanceof AtomicLong) {
1✔
132
                return LONG_ATOMIC;
1✔
133
            }
134
            if(number instanceof Integer) {
1✔
135
                return INTEGER_BOXED;
1✔
136
            }
137
            if(number instanceof AtomicInteger) {
1✔
138
                return INTEGER_ATOMIC;
1✔
139
            }
140
            if(number instanceof Double) {
1✔
141
                return DOUBLE_BOXED;
1✔
142
            }
143
            if(number instanceof Short) {
1✔
144
                return SHORT_BOXED;
1✔
145
            }
146
            if(number instanceof Byte) {
1✔
147
                return BYTE_BOXED;
1✔
148
            }
149
            if(number instanceof Float) {
1✔
150
                return FLOAT_BOXED;
1✔
151
            }
152
            if(number instanceof BigDecimal) {
1✔
153
                return BIG_DECIMAL;
1✔
154
            }
155
            if(number instanceof BigInteger) {
1✔
156
                return BIG_INTEGER;
1✔
157
            }
158
            if(number instanceof RationalNumber) {
1✔
159
                return RATIONAL;
1✔
160
            }
161
            final String msg = String.format("Unsupported number type '%s'",
1✔
UNCOV
162
                    number.getClass().getName());
×
UNCOV
163
            throw new IllegalArgumentException(msg);
×
164
        }
165

166
        /**
167
         * Whether given {@link Number} is ZERO.
168
         * @param number - must be of type {@link #getType()}
169
         * @apiNote For class internal use only,
170
         *      such that we have control over the number's type that gets passed in.
171
         */
172
        boolean isZero(Number number) {
173
            return zero.equals(number);
1✔
174
        }
175

176
    }
177

178
    @Override
179
    public Number add(final Number x, final Number y) {
180

181
        final NumberType type_x = NumberType.valueOf(x);
1✔
182
        final NumberType type_y = NumberType.valueOf(y);
1✔
183

184
        final boolean reorder_args = type_y.ordinal()>type_x.ordinal();
1✔
185

186
        return reorder_args
1✔
187
                ? addWideAndNarrow(type_y, y, type_x, x)
1✔
188
                : addWideAndNarrow(type_x, x, type_y, y);
1✔
189
    }
190

191
    @Override
192
    public Number subtract(final Number x, final Number y) {
193
        return add(x, negate(y));
1✔
194
    }
195

196
    @Override
197
    public Number multiply(final Number x, final Number y) {
198

199
        final NumberType type_x = NumberType.valueOf(x);
1✔
200
        final NumberType type_y = NumberType.valueOf(y);
1✔
201

202
        final boolean reorder_args = type_y.ordinal()>type_x.ordinal();
1✔
203

204
        return reorder_args
1✔
205
                ? multiplyWideAndNarrow(type_y, y, type_x, x)
1✔
206
                : multiplyWideAndNarrow(type_x, x, type_y, y);
1✔
207
    }
208

209
    @Override
210
    public Number divide(final Number x, final Number y) {
211
        return multiply(x, reciprocal(y));
1✔
212
    }
213

214
    @Override
215
    public Number[] divideAndRemainder(final Number x, final Number y, final boolean roundRemainderTowardsZero) {
216

217
        final int sign_x = signum(x);
1✔
218
        final int sign_y = signum(y);
1✔
219

220
        final int sign = sign_x * sign_y;
1✔
221
        // handle corner cases when x or y are zero
222
        if(sign == 0) {
1✔
UNCOV
223
            if(sign_y == 0) {
×
UNCOV
224
                throw new ArithmeticException("division by zero");
×
225
            }
226
            if(sign_x==0) {
×
227
                return new Number[] {0, 0};
×
228
            }
229
        }
230

231
        final Number absX = abs(x);
1✔
232
        final Number absY = abs(y);
1✔
233

234
        final NumberType type_x = NumberType.valueOf(absX);
1✔
235
        final NumberType type_y = NumberType.valueOf(absY);
1✔
236

237
        // if x and y are both integer types than we can calculate integer results,
238
        // otherwise we resort to BigDecimal
239
        final boolean yieldIntegerResult = type_x.isIntegerOnly() && type_y.isIntegerOnly();
1✔
240

241
        if(yieldIntegerResult) {
1✔
242

243
            final BigInteger integer_x = integerToBigInteger(absX);
1✔
244
            final BigInteger integer_y = integerToBigInteger(absY);
1✔
245

246
            final BigInteger[] divAndRemainder = integer_x.divideAndRemainder(integer_y);
1✔
247

248
            return applyToArray(divAndRemainder, number->copySignTo(sign, (BigInteger)number));
1✔
249

250
        } else {
251

252
            final MathContext mathContext =
1✔
253
                    new MathContext(Calculus.MATH_CONTEXT.getPrecision(), RoundingMode.FLOOR);
1✔
254

255
            final BigDecimal decimal_x = (type_x == NumberType.RATIONAL)
1✔
UNCOV
256
                    ? ((RationalNumber) absX).bigDecimalValue()
×
257
                            : toBigDecimal(absX);
1✔
258
            final BigDecimal decimal_y = (type_y == NumberType.RATIONAL)
1✔
259
                    ? ((RationalNumber) absY).bigDecimalValue()
×
260
                            : toBigDecimal(absY);
1✔
261

262
            final BigDecimal[] divAndRemainder = decimal_x.divideAndRemainder(decimal_y, mathContext);
1✔
263

264
            if(roundRemainderTowardsZero) {
1✔
UNCOV
265
                return new Number[] {
×
UNCOV
266
                        copySignTo(sign, divAndRemainder[0]),
×
UNCOV
267
                        copySignTo(sign, divAndRemainder[1].toBigInteger())};
×
268

269
            } else {
270
                return applyToArray(divAndRemainder, number->copySignTo(sign, (BigDecimal)number));
1✔
271
            }
272

273
        }
274

275
    }
276

277
    @Override
278
    public Number reciprocal(final Number number) {
279
        if(isIntegerOnly(number)) {
1✔
280
            return RationalNumber.of(BigInteger.ONE, integerToBigInteger(number));
1✔
281
        }
282
        if(number instanceof BigDecimal) {
1✔
283
            return RationalNumber.of((BigDecimal) number).reciprocal();
1✔
284
        }
285
        if(number instanceof RationalNumber) {
1✔
286
            return ((RationalNumber) number).reciprocal();
1✔
287
        }
288
        if(number instanceof Double) {
1✔
289
            return RationalNumber.of((double)number).reciprocal();
1✔
290
        }
UNCOV
291
        if(number instanceof Float) {
×
UNCOV
292
            return RationalNumber.of(number.doubleValue()).reciprocal();
×
293
        }
294
        throw unsupportedNumberType(number);
×
295
    }
296

297
    @Override
298
    public int signum(final Number number) {
299
        if(number instanceof BigInteger) {
1✔
UNCOV
300
            return ((BigInteger) number).signum();
×
301
        }
302
        if(number instanceof BigDecimal) {
1✔
303
            return ((BigDecimal) number).signum();
1✔
304
        }
305
        if(number instanceof RationalNumber) {
1✔
UNCOV
306
            return ((RationalNumber) number).signum();
×
307
        }
308
        if(number instanceof Double) {
1✔
309
            return (int)Math.signum((double)number);
1✔
310
        }
311
        if(number instanceof Float) {
1✔
UNCOV
312
            return (int)Math.signum((float)number);
×
313
        }
314
        if(number instanceof Long || number instanceof AtomicLong) {
1✔
315
            final long longValue = number.longValue();
×
UNCOV
316
            return Long.signum(longValue);
×
317
        }
318
        if(number instanceof Integer || number instanceof AtomicInteger ||
1✔
319
                number instanceof Short || number instanceof Byte) {
320
            final int intValue = number.intValue();
1✔
321
            return Integer.signum(intValue);
1✔
322
        }
UNCOV
323
        throw unsupportedNumberType(number);
×
324
    }
325

326
    @Override
327
    public Number abs(final Number number) {
328
        if(number instanceof BigInteger) {
1✔
UNCOV
329
            return ((BigInteger) number).abs();
×
330
        }
331
        if(number instanceof BigDecimal) {
1✔
332
            return ((BigDecimal) number).abs();
1✔
333
        }
334
        if(number instanceof RationalNumber) {
1✔
UNCOV
335
            return ((RationalNumber) number).abs();
×
336
        }
337
        if(number instanceof Double) {
1✔
338
            return Math.abs((double)number);
1✔
339
        }
340
        if(number instanceof Float) {
1✔
UNCOV
341
            return Math.abs((float)number);
×
342
        }
343
        if(number instanceof Long || number instanceof AtomicLong) {
1✔
344
            final long longValue = number.longValue();
1✔
345
            if(longValue == Long.MIN_VALUE) {
1✔
UNCOV
346
                return BigInteger.valueOf(longValue).abs(); // widen to BigInteger
×
347
            }
348
            return Math.abs(longValue);
1✔
349
        }
350
        if(number instanceof Integer || number instanceof AtomicInteger) {
1✔
351
            final int intValue = number.intValue();
1✔
352
            if(intValue == Integer.MIN_VALUE) {
1✔
UNCOV
353
                return Math.abs(number.longValue()); // widen to long
×
354
            }
355
            return Math.abs(intValue);
1✔
356
        }
UNCOV
357
        if(number instanceof Short || number instanceof Byte) {
×
UNCOV
358
            return Math.abs(number.intValue()); // widen to int
×
359
        }
360
        throw unsupportedNumberType(number);
×
361
    }
362

363
    @Override
364
    public Number negate(final Number number) {
365
        if(number instanceof BigInteger) {
1✔
366
            return ((BigInteger) number).negate();
1✔
367
        }
368
        if(number instanceof BigDecimal) {
1✔
369
            return ((BigDecimal) number).negate();
1✔
370
        }
371
        if(number instanceof RationalNumber) {
1✔
372
            return ((RationalNumber) number).negate();
1✔
373
        }
374
        if(number instanceof Double) {
1✔
375
            return -((double)number);
1✔
376
        }
377
        if(number instanceof Float) {
1✔
378
            return -((float)number);
1✔
379
        }
380
        if(number instanceof Long || number instanceof AtomicLong) {
1✔
381
            final long longValue = number.longValue();
1✔
382
            if(longValue == Long.MIN_VALUE) {
1✔
UNCOV
383
                return BigInteger.valueOf(longValue).negate(); // widen to BigInteger
×
384
            }
385
            return -longValue;
1✔
386
        }
387
        if(number instanceof Integer || number instanceof AtomicInteger) {
1✔
388
            final int intValue = number.intValue();
1✔
389
            if(intValue == Integer.MIN_VALUE) {
1✔
UNCOV
390
                return -number.longValue(); // widen to long
×
391
            }
392
            return -intValue;
1✔
393
        }
394
        if(number instanceof Short) {
1✔
395
            final short shortValue = (short)number;
1✔
396
            if(shortValue == Short.MIN_VALUE) {
1✔
UNCOV
397
                return -number.intValue(); // widen to int
×
398
            }
399
            return -shortValue;
1✔
400
        }
401
        if(number instanceof Byte) {
1✔
402
            final short byteValue = (byte)number;
1✔
403
            if(byteValue == Byte.MIN_VALUE) {
1✔
UNCOV
404
                return -number.intValue(); // widen to int
×
405
            }
406
            return -byteValue;
1✔
407
        }
UNCOV
408
        throw unsupportedNumberType(number);
×
409
    }
410

411
    @Override
412
    public Number power(final Number number, final int exponent) {
413
        if(exponent==0) {
1✔
UNCOV
414
            if(isZero(number)) {
×
UNCOV
415
                throw new ArithmeticException("0^0 is not defined");
×
416
            }
417
            return 1; // x^0 == 1, for any x!=0
×
418
        }
419
        if(exponent==1) {
1✔
420
            return number; // x^1 == x, for any x
1✔
421
        }
422
        if(number instanceof BigInteger ||
1✔
423
                number instanceof Long || number instanceof AtomicLong ||
424
                number instanceof Integer || number instanceof AtomicInteger ||
425
                number instanceof Short || number instanceof Byte) {
UNCOV
426
            final BigInteger bigInt = integerToBigInteger(number);
×
UNCOV
427
            if(exponent>0) {
×
UNCOV
428
                return bigInt.pow(exponent);
×
429
            }
430
            return RationalNumber.ofInteger(bigInt).pow(exponent);
×
431

432
        }
433
        if(number instanceof BigDecimal) {
1✔
434
            return ((BigDecimal) number).pow(exponent, Calculus.MATH_CONTEXT);
1✔
435
        }
UNCOV
436
        if(number instanceof RationalNumber) {
×
UNCOV
437
            ((RationalNumber) number).pow(exponent);
×
438
        }
439
        if(number instanceof Double || number instanceof Float) {
×
440
            return toBigDecimal(number).pow(exponent, Calculus.MATH_CONTEXT);
×
441
        }
442
        throw unsupportedNumberType(number);
×
443
    }
444

445
    @Override
446
    public Number exp(final Number number) {
447
        //TODO[220] this is a poor implementation, certainly we can do better using BigDecimal
448
        return Math.exp(number.doubleValue());
1✔
449
    }
450

451
    @Override
452
    public Number log(final Number number) {
453
        //TODO[220] this is a poor implementation, certainly we can do better using BigDecimal
454
        return Math.log(number.doubleValue());
1✔
455
    }
456

457
    @Override
458
    public Number narrow(final Number number) {
459

460
        //Implementation Note: for performance we stop narrowing down at 'double' or 'integer' level
461

462
        if(number instanceof Integer || number instanceof AtomicInteger ||
1✔
463
                number instanceof Short || number instanceof Byte) {
464
            return number;
1✔
465
        }
466

467
        if(number instanceof Double || number instanceof Float) {
1✔
468
            final double doubleValue = number.doubleValue();
1✔
469
            if(!Double.isFinite(doubleValue)) {
1✔
470
                throw unsupportedNumberValue(doubleValue);
1✔
471
            }
472
            if(doubleValue == 0) {
1✔
473
                return 0;
1✔
474
            }
475
            if(doubleValue % 1 == 0) {
1✔
476
                // double represents an integer other than zero
477

478
                // narrow to long if possible
479
                if(MIN_LONG_AS_DOUBLE <= doubleValue && doubleValue <= MAX_LONG_AS_DOUBLE) {
1✔
480
                    long longValue = (long) doubleValue;
1✔
481

482
                    // further narrow to int if possible
483
                    if(Integer.MIN_VALUE <= longValue && longValue <= Integer.MAX_VALUE) {
1✔
484
                        return (int) longValue;
1✔
485
                    }
486
                    return longValue;
1✔
487
                }
488
                return narrow(BigDecimal.valueOf(doubleValue));
1✔
489
            }
490
            return number;
1✔
491
        }
492

493
        if(isIntegerOnly(number)) {
1✔
494

495
            // number is one of {BigInteger, Long}
496

497
            final int total_bits_required = bitLengthOfInteger(number);
1✔
498

499
            // check whether we have enough bits to store the result into an int
500
            if(total_bits_required<31) {
1✔
501
                return number.intValue();
1✔
502
            }
503

504
            // check whether we have enough bits to store the result into a long
505
            if(total_bits_required<63) {
1✔
506
                return number.longValue();
1✔
507
            }
508

509
            return number; // cannot narrow down
1✔
510

511
        }
512

513
        if(number instanceof BigDecimal) {
1✔
514

515
            final BigDecimal decimal = (BigDecimal) number;
1✔
516
            // educated guess: it is more likely for the given decimal to have fractional parts, than not;
517
            // hence in order to avoid the expensive conversion attempt decimal.toBigIntegerExact() below,
518
            // we do a less expensive check first
519
            if(isFractional(decimal)) {
1✔
520
                return number; // cannot narrow to integer
1✔
521
            }
522
            try {
523
                BigInteger integer = decimal.toBigIntegerExact();
1✔
524
                return narrow(integer);
1✔
UNCOV
525
            } catch (ArithmeticException e) {
×
UNCOV
526
                return number; // cannot narrow to integer (unexpected code reach, due to isFractional(decimal) guard above)
×
527
            }
528
        }
529

530
        if(number instanceof RationalNumber) {
1✔
531

532
            final RationalNumber rational = ((RationalNumber) number);
1✔
533

534
            return rational.isInteger()
1✔
535
                    ? narrow(rational.getDividend()) // divisor is ONE
1✔
536
                            : number; // cannot narrow to integer;
1✔
537
        }
538

539
        // for any other number type just do nothing
UNCOV
540
        return number;
×
541
    }
542

543
    @Override
544
    public int compare(final Number x, final Number y) {
545

546
        final NumberType type_x = NumberType.valueOf(x);
1✔
547
        final NumberType type_y = NumberType.valueOf(y);
1✔
548

549
        final boolean reorder_args = type_y.ordinal()>type_x.ordinal();
1✔
550

551
        return reorder_args
1✔
552
                ? -compareWideVsNarrow(type_y, y, type_x, x)
1✔
553
                : compareWideVsNarrow(type_x, x, type_y, y);
1✔
554
    }
555

556
    @Override
557
    public boolean isZero(final Number number) {
558
        NumberType numberType = NumberType.valueOf(number);
1✔
559
        return numberType.isZero(number);
1✔
560
    }
561

562
    @Override
563
    public boolean isOne(final Number number) {
564
        NumberType numberType = NumberType.valueOf(number);
1✔
565
        return compare(numberType.one, number) == 0;
1✔
566
    }
567

568
    @Override
569
    public boolean isLessThanOne(final Number number) {
570
        NumberType numberType = NumberType.valueOf(number);
1✔
571
        return compare(numberType.one, number) > 0;
1✔
572
    }
573

574
    @Override
575
    public boolean isInteger(final Number number) {
576
        NumberType numberType = NumberType.valueOf(number);
1✔
577
        return isInteger(numberType, number);
1✔
578
    }
579

580

581
    // -- HELPER
582

583
    private IllegalArgumentException unsupportedNumberValue(final Number number) {
584
        final String msg = String.format("Unsupported number value '%s' of type '%s' in number system '%s'",
1✔
585
                "" + number,
586
                number.getClass(),
1✔
587
                this.getClass().getName());
1✔
588

589
        return new IllegalArgumentException(msg);
1✔
590
    }
591

592
    private IllegalArgumentException unsupportedNumberType(final Number number) {
UNCOV
593
        final String msg = String.format("Unsupported number type '%s' in number system '%s'",
×
UNCOV
594
                number.getClass().getName(),
×
UNCOV
595
                this.getClass().getName());
×
596

UNCOV
597
        return new IllegalArgumentException(msg);
×
598
    }
599

600
    private IllegalStateException unexpectedCodeReach() {
UNCOV
601
        final String msg = String.format("Implementation Error: Code was reached that is expected unreachable");
×
UNCOV
602
        return new IllegalStateException(msg);
×
603
    }
604

605
    /**
606
     * Whether the {@link Number}'s type can only represent integers.
607
     * <p>
608
     * If <code>false</code> it can also represent fractional numbers.
609
     * <p>
610
     * Note: this does not check whether given number represents an integer.
611
     */
612
    private boolean isIntegerOnly(final Number number) {
613
        return NumberType.valueOf(number).isIntegerOnly();
1✔
614
    }
615

616
    /**
617
     * Whether given {@link BigDecimal} has (non-zero) fractional parts.
618
     * When <code>false</code>, given {@link BigDecimal} can be converted to a {@link BigInteger}.
619
     * @implNote {@link BigDecimal#stripTrailingZeros()} creates a new {@link BigDecimal} just to do the check.
620
     * @see https://stackoverflow.com/questions/1078953/check-if-bigdecimal-is-integer-value
621
     */
622
    static boolean isFractional(final BigDecimal decimal) {
623
        // check if is ZERO first
624
        if(decimal.signum() == 0) {
1✔
625
            return false;
1✔
626
        }
627
        // check if scale <= 0; if it is, then decimal definitely has no fractional parts
628
        if(decimal.scale()<=0) {
1✔
629
            return false;
1✔
630
        }
631
        // Note: this creates a new BigDecimal instance just to check for fractional parts
632
        // (perhaps we can improve that in the future)
633
        return decimal.stripTrailingZeros().scale() > 0;
1✔
634
    }
635

636
    /**
637
     * Whether given {@link Number} represents an integer.
638
     * Optimized for when we know the {@link NumberType} in advance.
639
     */
640
    private boolean isInteger(final NumberType numberType, final Number number) {
641
        if(numberType.isIntegerOnly()) {
1✔
642
            return true; // numberType only allows integer
1✔
643
        }
UNCOV
644
        if(number instanceof RationalNumber) {
×
UNCOV
645
            return ((RationalNumber)number).isInteger();
×
646
        }
647

648
        // remaining types to check: Double, Float, BigDecimal ...
649

UNCOV
650
        if(number instanceof BigDecimal) {
×
UNCOV
651
            return !isFractional((BigDecimal)number);
×
652
        }
UNCOV
653
        if(number instanceof Double || number instanceof Float) {
×
UNCOV
654
            double doubleValue = number.doubleValue();
×
655
            // see https://stackoverflow.com/questions/15963895/how-to-check-if-a-double-value-has-no-decimal-part
UNCOV
656
            if (numberType.isZero(number)) return false;
×
UNCOV
657
            return doubleValue % 1 == 0;
×
658
        }
UNCOV
659
        throw unsupportedNumberType(number);
×
660
    }
661

662
    private int bitLengthOfInteger(final Number number) {
663
        if(number instanceof BigInteger) {
1✔
664
            return ((BigInteger) number).bitLength();
1✔
665
        }
666
        long long_value = number.longValue();
1✔
667

668
        if(long_value == Long.MIN_VALUE) {
1✔
UNCOV
669
            return 63;
×
670
        } else {
671
            int leadingZeros = Long.numberOfLeadingZeros(Math.abs(long_value));
1✔
672
            return 64-leadingZeros;
1✔
673
        }
674
    }
675

676
    private BigInteger integerToBigInteger(final Number number) {
677
        if(number instanceof BigInteger) {
1✔
678
            return (BigInteger) number;
1✔
679
        }
680
        return BigInteger.valueOf(number.longValue());
1✔
681
    }
682

683
    private BigDecimal toBigDecimal(final Number number) {
684
        if(number instanceof BigDecimal) {
1✔
685
            return (BigDecimal) number;
1✔
686
        }
687
        if(number instanceof BigInteger) {
1✔
UNCOV
688
            return new BigDecimal((BigInteger) number);
×
689
        }
690
        if(number instanceof Long ||
1✔
691
                number instanceof AtomicLong ||
692
                number instanceof Integer ||
693
                number instanceof AtomicInteger ||
694
                number instanceof Short ||
695
                number instanceof Byte) {
696
            return BigDecimal.valueOf(number.longValue());
1✔
697
        }
UNCOV
698
        if(number instanceof Double || number instanceof Float) {
×
UNCOV
699
            return BigDecimal.valueOf(number.doubleValue());
×
700
        }
UNCOV
701
        if(number instanceof RationalNumber) {
×
UNCOV
702
            throw unexpectedCodeReach();
×
703
            //Note: don't do that (potential precision loss)
704
            //return ((RationalNumber) number).bigDecimalValue();
705
        }
UNCOV
706
        throw unsupportedNumberType(number);
×
707
    }
708

709
    private Number addWideAndNarrow(
710
            final NumberType wideType, final Number wide,
711
            final NumberType narrowType, final Number narrow) {
712

713
        // avoid type-check or widening if one of the arguments is zero
714
        // https://github.com/unitsofmeasurement/indriya/issues/384
715
        if (wideType.isZero(wide)) {
1✔
716
            return narrow;
1✔
717
        } else if (narrowType.isZero(narrow)) {
1✔
718
            return wide;
1✔
719
        }
720

721
        if(wideType.isIntegerOnly()) {
1✔
722
            // at this point we know, that narrow must also be an integer-only type
723
            if(wide instanceof BigInteger) {
1✔
UNCOV
724
                return ((BigInteger) wide).add(integerToBigInteger(narrow));
×
725
            }
726

727
            // at this point we know, that 'wide' and 'narrow' are one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
728

729
            // +1 carry, not including sign
730
            int total_bits_required = Math.max(bitLengthOfInteger(wide), bitLengthOfInteger(narrow)) + 1;
1✔
731

732
            // check whether we have enough bits to store the result into a long
733
            if(total_bits_required<63) {
1✔
734
                return wide.longValue() + narrow.longValue();
1✔
735
            }
736

737
            return integerToBigInteger(wide).add(integerToBigInteger(narrow));
1✔
738
        }
739

740
        if(wide instanceof RationalNumber) {
1✔
741

742
            // at this point we know, that narrow must either be rational or an integer-only type
743
            if(narrow instanceof RationalNumber) {
1✔
744
                return ((RationalNumber) wide).add((RationalNumber) narrow);
1✔
745
            }
746

747
            return ((RationalNumber) wide).add(
1✔
748
                    RationalNumber.ofInteger(integerToBigInteger(narrow)));
1✔
749
        }
750

751
        // at this point we know, that wide is one of {BigDecimal, Double, Float}
752

753
        if(wide instanceof BigDecimal) {
1✔
754

755
            if(narrow instanceof BigDecimal) {
1✔
756
                return ((BigDecimal) wide).add((BigDecimal) narrow, Calculus.MATH_CONTEXT);
1✔
757
            }
758

759
            if(narrow instanceof Double || narrow instanceof Float) {
1✔
760
                return ((BigDecimal) wide).add(BigDecimal.valueOf(narrow.doubleValue()), Calculus.MATH_CONTEXT);
1✔
761
            }
762

763
            if(narrow instanceof RationalNumber) {
1✔
764
                //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber
765
                return ((BigDecimal) wide).add(((RationalNumber) narrow).bigDecimalValue());
1✔
766
            }
767

768
            // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
769
            return ((BigDecimal) wide).add(BigDecimal.valueOf(narrow.longValue()));
1✔
770

771
        }
772

773
        // at this point we know, that wide is one of {Double, Float}
774

775
        if(narrow instanceof Double || narrow instanceof Float) {
1✔
776
            //converting to BigDecimal, because especially fractional addition is sensitive to precision loss
777
            return BigDecimal.valueOf(wide.doubleValue())
1✔
778
                .add(BigDecimal.valueOf(narrow.doubleValue()));
1✔
779
        }
780

781
        if(narrow instanceof RationalNumber) {
1✔
782
            //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber
UNCOV
783
            return BigDecimal.valueOf(wide.doubleValue())
×
UNCOV
784
                    .add(((RationalNumber) narrow).bigDecimalValue());
×
785
        }
786

787
        if(narrow instanceof BigInteger) {
1✔
UNCOV
788
            return BigDecimal.valueOf(wide.doubleValue())
×
UNCOV
789
                    .add(new BigDecimal((BigInteger) narrow));
×
790
        }
791

792
        // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
793
        return BigDecimal.valueOf(wide.doubleValue())
1✔
794
                .add(BigDecimal.valueOf(narrow.longValue()));
1✔
795

796
    }
797

798
    private Number multiplyWideAndNarrow(
799
            final NumberType wideType, final Number wide,
800
            final NumberType narrowType, final Number narrow) {
801

802
        // shortcut if any of the operands is zero.
803
        if (wideType.isZero(wide)
1✔
804
                || narrowType.isZero(narrow)) {
1✔
805
            return 0;
1✔
806
        }
807

808
        if(wideType.isIntegerOnly()) {
1✔
809
            // at this point we know, that narrow must also be an integer-only type
810
            if(wide instanceof BigInteger) {
1✔
811
                return ((BigInteger) wide).multiply(integerToBigInteger(narrow));
1✔
812
            }
813

814
            // at this point we know, that 'wide' and 'narrow' are one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
815

816
            int total_bits_required = bitLengthOfInteger(wide) + bitLengthOfInteger(narrow); // not including sign
1✔
817

818
            // check whether we have enough bits to store the result into a long
819
            if(total_bits_required<63) {
1✔
820
                return wide.longValue() * narrow.longValue();
1✔
821
            }
822

823
            return integerToBigInteger(wide).multiply(integerToBigInteger(narrow));
1✔
824
        }
825

826
        if(wide instanceof RationalNumber) {
1✔
827

828
            // at this point we know, that narrow must either be rational or an integer-only type
829
            if(narrow instanceof RationalNumber) {
1✔
830
                return ((RationalNumber) wide).multiply((RationalNumber) narrow);
1✔
831
            }
832

833
            return ((RationalNumber) wide).multiply(
1✔
834
                    RationalNumber.ofInteger(integerToBigInteger(narrow)));
1✔
835
        }
836

837
        // at this point we know, that wide is one of {BigDecimal, Double, Float}
838

839
        if(wide instanceof BigDecimal) {
1✔
840

841
            if(narrow instanceof BigDecimal) {
1✔
842
                return ((BigDecimal) wide).multiply((BigDecimal) narrow, Calculus.MATH_CONTEXT);
1✔
843
            }
844

845
            if(narrow instanceof BigInteger) {
1✔
846
                return ((BigDecimal) wide).multiply(new BigDecimal((BigInteger)narrow), Calculus.MATH_CONTEXT);
1✔
847
            }
848

849
            if(narrow instanceof Double || narrow instanceof Float) {
1✔
850
                return ((BigDecimal) wide).multiply(BigDecimal.valueOf(narrow.doubleValue()), Calculus.MATH_CONTEXT);
1✔
851
            }
852

853
            if(narrow instanceof RationalNumber) {
1✔
854
                //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber
855
                return ((BigDecimal) wide).multiply(((RationalNumber) narrow).bigDecimalValue());
1✔
856
            }
857

858
            // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
859
            return ((BigDecimal) wide).multiply(BigDecimal.valueOf(narrow.longValue()));
1✔
860

861
        }
862

863
        // at this point we know, that wide is one of {Double, Float}
864

865
        if(narrow instanceof Double || narrow instanceof Float) {
1✔
866
            // not converting to BigDecimal, because fractional multiplication is not sensitive to precision loss
867
            return wide.doubleValue() * narrow.doubleValue();
1✔
868
        }
869

870
        if(narrow instanceof RationalNumber) {
1✔
871
            //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber
872
            return BigDecimal.valueOf(wide.doubleValue())
1✔
873
                    .multiply(((RationalNumber) narrow).bigDecimalValue());
1✔
874
        }
875

876
        if(narrow instanceof BigInteger) {
1✔
877
            return BigDecimal.valueOf(wide.doubleValue())
1✔
878
                    .multiply(new BigDecimal((BigInteger) narrow));
1✔
879
        }
880

881
        // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
882
        return BigDecimal.valueOf(wide.doubleValue())
1✔
883
                .multiply(BigDecimal.valueOf(narrow.longValue()));
1✔
884

885
    }
886

887
    /**
888
     * @param unusedNarrowType - currently unused (but future refactoring might use it)
889
     */
890
    private int compareWideVsNarrow(
891
            final NumberType wideType, final Number wide,
892
            final NumberType unusedNarrowType, final Number narrow) {
893

894
        if(wideType.isIntegerOnly()) {
1✔
895
            // at this point we know, that narrow must also be an integer-only type
896
            if(wide instanceof BigInteger) {
1✔
897
                return ((BigInteger) wide).compareTo(integerToBigInteger(narrow));
1✔
898
            }
899

900
            // at this point we know, that 'wide' and 'narrow' are one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
901
            return Long.compare(wide.longValue(), narrow.longValue());
1✔
902
        }
903

904
        if(wide instanceof RationalNumber) {
1✔
905

906
            // at this point we know, that narrow must either be rational or an integer-only type
907
            if(narrow instanceof RationalNumber) {
1✔
908
                return ((RationalNumber) wide).compareTo((RationalNumber) narrow);
1✔
909
            }
910

911
            return ((RationalNumber) wide).compareTo(
1✔
912
                    RationalNumber.ofInteger(integerToBigInteger(narrow)));
1✔
913
        }
914

915
        // at this point we know, that wide is one of {BigDecimal, Double, Float}
916

917
        if(wide instanceof BigDecimal) {
1✔
918

919
            if(narrow instanceof BigDecimal) {
1✔
920
                return ((BigDecimal) wide).compareTo((BigDecimal) narrow);
1✔
921
            }
922

923
            if(narrow instanceof Double || narrow instanceof Float) {
1✔
UNCOV
924
                return ((BigDecimal) wide).compareTo(BigDecimal.valueOf(narrow.doubleValue()));
×
925
            }
926

927
            if(narrow instanceof RationalNumber) {
1✔
928
                //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber
UNCOV
929
                return ((BigDecimal) wide).compareTo(((RationalNumber) narrow).bigDecimalValue());
×
930
            }
931

932
            if (narrow instanceof BigInteger) {
1✔
933
                //TODO for optimization, can this be done without instantiating a new BigDecimal?
934
                return ((BigDecimal) wide).compareTo(new BigDecimal((BigInteger) narrow));
1✔
935
            }
936

937
            // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
938
            return ((BigDecimal) wide).compareTo(BigDecimal.valueOf(narrow.longValue()));
1✔
939

940
        }
941

942
        // at this point we know, that wide is one of {Double, Float}
943

944
        if(narrow instanceof Double || narrow instanceof Float) {
1✔
945
            return Double.compare(wide.doubleValue(), narrow.doubleValue());
1✔
946
        }
947

948
        if(narrow instanceof RationalNumber) {
1✔
949
            //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber
UNCOV
950
            return BigDecimal.valueOf(wide.doubleValue())
×
UNCOV
951
                    .compareTo(((RationalNumber) narrow).bigDecimalValue());
×
952
        }
953

954
        if(narrow instanceof BigInteger) {
1✔
955
            return BigDecimal.valueOf(wide.doubleValue())
1✔
956
                    .compareTo(new BigDecimal((BigInteger) narrow));
1✔
957
        }
958

959
        // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte}
960
        return BigDecimal.valueOf(wide.doubleValue())
1✔
961
                .compareTo(BigDecimal.valueOf(narrow.longValue()));
1✔
962

963
    }
964

965
    // only for non-zero sign
966
    private static BigInteger copySignTo(final int sign, final BigInteger absNumber) {
967
        if(sign==-1) {
1✔
968
            return absNumber.negate();
×
969
        }
970
        return absNumber;
1✔
971
    }
972

973
    // only for non-zero sign
974
    private static BigDecimal copySignTo(final int sign, final BigDecimal absNumber) {
975
        if(sign==-1) {
1✔
UNCOV
976
            return absNumber.negate();
×
977
        }
978
        return absNumber;
1✔
979
    }
980

981
    private static Number[] applyToArray(final Number[] array, final UnaryOperator<Number> operator) {
982
        // only ever used for length=2
983
        return new Number[] {
1✔
984
                operator.apply(array[0]),
1✔
985
                operator.apply(array[1])
1✔
986
        };
987
    }
988
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc