1 /*
2 * Oceanus: Java Utilities
3 * Copyright 2026. Tony Washer
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6 * use this file except in compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 * License for the specific language governing permissions and limitations under
15 * the License.
16 */
17
18 /**
19 * Provides classes to represent decimal numbers with fixed numbers of decimal digits
20 * {@link #theScale} as Long integers. The decimal value is multiplied by 10 to the power of the
21 * number of decimals for the number ({@link #theFactor}). The integral part of the number can be
22 * expressed as (Value / Factor) and the fractional part as (Value % Factor). Arithmetic is then
23 * performed as whole number arithmetic on these values, with due care taken on multiplication and
24 * division to express the result to the correct number of decimals without losing any part of the
25 * answer to overflow.
26 */
27 package io.github.tonywasher.joceanus.oceanus.decimal;
28
29 import java.math.BigDecimal;
30 import java.math.RoundingMode;
31
32 /**
33 * Decimal class performing integer arithmetic on large decimals.
34 */
35 public class OceanusNewDecimal {
36 /**
37 * The Decimal radix.
38 */
39 private static final int RADIX_TEN = 10;
40
41 /**
42 * The Maximum # of Decimals.
43 */
44 public static final int MAX_DECIMALS = 9;
45
46 /**
47 * The Integer boost.
48 */
49 private static final long INTEGER_BOOST = 0x100000000L;
50
51 /**
52 * The Integer mask.
53 */
54 private static final long INTEGER_MASK = 0xFFFFFFFFL;
55
56 /**
57 * Powers of Ten.
58 */
59 private static final int[] POWERS_OF_TEN = getPowersOfTen(MAX_DECIMALS);
60
61 /**
62 * The number of decimal digits.
63 */
64 private final int theScale;
65
66 /**
67 * The Decimal factor, used for rounding fractional parts.
68 */
69 private final int theFactor;
70
71 /**
72 * sign.
73 */
74 private int theSign;
75
76 /**
77 * Positive Integral part.
78 */
79 private long theIntegral;
80
81 /**
82 * Positive Fractional part.
83 */
84 private int theFractional;
85
86 /**
87 * Constructor.
88 *
89 * @param pScale the number of decimal digits
90 */
91 public OceanusNewDecimal(final int pScale) {
92 this(0, 0, 0, pScale);
93 }
94
95 /**
96 * Constructor.
97 *
98 * @param pSource the source BigDecimal
99 */
100 public OceanusNewDecimal(final BigDecimal pSource) {
101 /* Store sign and scale */
102 theSign = pSource.signum();
103 theScale = pSource.scale();
104 checkValidScale(theScale);
105 theFactor = getFactor(theScale);
106
107 /* Extract the integral and fractional parts */
108 theIntegral = pSource.longValue();
109 theIntegral *= theSign;
110 long myFractional = pSource.movePointRight(theScale).longValue() * theSign;
111 myFractional %= theFactor;
112 theFractional = (int) myFractional;
113 }
114
115 /**
116 * Constructor.
117 *
118 * @param pIntegral the integral part of the decimal.
119 * @param pFractional the fractional part of the decimal
120 * @param pSign the sign of the decimal
121 * @param pScale the number of decimal digits
122 */
123 public OceanusNewDecimal(final long pIntegral,
124 final int pFractional,
125 final int pSign,
126 final int pScale) {
127 /* Check that scale if valid */
128 checkValidScale(pScale);
129
130 /* Store details */
131 theIntegral = pIntegral;
132 theFractional = pFractional;
133 theSign = pSign;
134 theScale = pScale;
135 theFactor = getFactor(theScale);
136 }
137
138 /**
139 * Check that the scale is valid.
140 *
141 * @param pScale the scale
142 */
143 private static void checkValidScale(final int pScale) {
144 if (pScale < 0 || pScale > MAX_DECIMALS) {
145 throw new IllegalArgumentException("Invalid scale - " + pScale);
146 }
147 }
148
149 /**
150 * Obtain the integral part of the decimal.
151 *
152 * @return the integral part of the decimal
153 */
154 public long integralValue() {
155 return theIntegral * theSign;
156 }
157
158 /**
159 * Obtain the sign of the decimal.
160 *
161 * @return -1, 0, or 1 as the value of this Decimal is negative, zero, or positive
162 */
163 public int signum() {
164 return theSign;
165 }
166
167 /**
168 * Obtain the fractional part of the decimal.
169 *
170 * @return the fractional part of the decimal
171 */
172 public int fractionalValue() {
173 return theFractional * theSign;
174 }
175
176 /**
177 * Obtain the scale of the decimal.
178 *
179 * @return the scale of the decimal
180 */
181 public int scale() {
182 return theScale;
183 }
184
185 /**
186 * Add a decimal to value.
187 *
188 * @param pDecimal the decimal to add to this value
189 */
190 public void add(final OceanusNewDecimal pDecimal) {
191 /* Access the integral/fractional part of the second decimal at the same scale */
192 long myIntegral = pDecimal.theIntegral;
193 long myFractional = adjustDecimals(pDecimal.theFractional, theScale - pDecimal.theScale);
194 if (myFractional >= theFactor) {
195 myFractional %= theFactor;
196 myIntegral++;
197 }
198 add(myIntegral, (int) myFractional);
199 }
200
201 /**
202 * Subtract a decimal from value.
203 *
204 * @param pDecimal the decimal to subtract from this value
205 */
206 public void subtract(final OceanusNewDecimal pDecimal) {
207 /* Access the integral/fractional part of the second decimal at the same scale */
208 long myIntegral = pDecimal.theIntegral;
209 long myFractional = adjustDecimals(pDecimal.theFractional, theScale - pDecimal.theScale);
210 if (myFractional >= theFactor) {
211 myFractional %= theFactor;
212 myIntegral++;
213 }
214 add(-myIntegral, (int) -myFractional);
215 }
216
217 /**
218 * Add a decimal to this value.
219 *
220 * @param pIntegral the integral part of the decimal.
221 * @param pFractional the fractional part of the decimal
222 */
223 private void add(final long pIntegral,
224 final int pFractional) {
225 /* Add the fractional and non-fractional parts of the sum */
226 theFractional = pFractional + fractionalValue();
227 theIntegral = pIntegral + integralValue();
228
229 /* If we have a positive integral # */
230 if (theIntegral > 0) {
231 /* Handle fractional too small */
232 if (theFractional < 0) {
233 theFractional += theFactor;
234 theIntegral--;
235
236 /* Handle fractional too large */
237 } else if (theFractional >= theFactor) {
238 theFractional -= theFactor;
239 theIntegral++;
240 }
241
242 /* Set sign */
243 theSign = 1;
244
245 /* If we have a negative integral # */
246 } else if (theIntegral < 0) {
247 /* Handle fractional too large */
248 if (theFractional > 0) {
249 theFractional -= theFactor;
250 theIntegral++;
251
252 /* Handle fractional too small */
253 } else if (theFractional <= -theFactor) {
254 theFractional += theFactor;
255 theIntegral--;
256 }
257
258 /* Set sign */
259 theSign = -1;
260 theFractional = -theFractional;
261 theIntegral = -theIntegral;
262
263 /* else we have a zero integral */
264 } else {
265 /* Handle fractional too large */
266 if (theFractional >= theFactor) {
267 theFractional -= theFactor;
268 theIntegral = 1;
269 theSign = 1;
270
271 /* Handle Fractional too small */
272 } else if (theFractional <= -theFactor) {
273 theFractional += theFactor;
274 theSign = -1;
275 theIntegral = 1;
276 theFractional = -theFractional;
277
278 /* Handle positive fractional */
279 } else if (theFractional > 0) {
280 theSign = 1;
281
282 /* Handle negative fractional */
283 } else if (theFractional < 0) {
284 theSign = -1;
285 theFractional = -theFractional;
286
287 /* Handle zero fractional */
288 } else {
289 theSign = 0;
290 }
291 }
292 }
293
294 /**
295 * Multiply by another decimal
296 * <p>
297 * This function splits the values into three separate integers and then performs long arithmetic to
298 * prevent loss of precision. The value is represented as (x,y,z,s) where the decimal may be written as
299 * x*2<sup>32</sup> + y + z*10<sup>-s</sup> and x,y,z,s are all integers.
300 * <p>
301 * The product of (x<sub>1</sub>, y<sub>1</sub>, z<sub>1</sub>, s) by
302 * (x<sub>2</sub>, y<sub>2</sub>, z<sub>2</sub>, t)
303 * is therefore x<sub>1</sub>*x<sub>2</sub>*2<sup>64</sup> (discardable)
304 * + (x<sub>1</sub>*y<sub>2</sub> + x<sub>2</sub>*y<sub>1</sub>)*2<sup>32</sup>
305 * + x<sub>2</sub>*y<sub>2</sub>
306 * + x<sub>1</sub>*z<sub>2</sub>*2<sup>32</sup>*10<sup>-t</sup>
307 * + x<sub>2</sub>*z<sub>1</sub>*2<sup>32</sup>*10<sup>-s</sup>
308 * + y<sub>1</sub>*z<sub>2</sub>*10<sup>-t</sup>
309 * + y<sub>2</sub>*z<sub>1</sub>*10<sup>-s</sup>
310 * + z<sub>1</sub>*z<sub>2</sub>*10<sup>-s-t</sup>
311 *
312 * @param pMultiplicand the decimal to multiply by
313 */
314 public void multiply(final OceanusNewDecimal pMultiplicand) {
315 /* Access the parts of this value */
316 final long myX1 = theIntegral >>> Integer.SIZE;
317 final long myY1 = theIntegral & INTEGER_MASK;
318 final long myZ1 = theFractional;
319 final int myS = theScale;
320 final int mySFactor = theFactor;
321
322 /* Access the parts of the multiplicand */
323 final long myX2 = pMultiplicand.theIntegral >>> Integer.SIZE;
324 final long myY2 = pMultiplicand.theIntegral & INTEGER_MASK;
325 final long myZ2 = pMultiplicand.theFractional;
326 final int myT = pMultiplicand.theScale;
327 final int myTFactor = pMultiplicand.theFactor;
328 final long mySTFactor = mySFactor * (long) myTFactor;
329
330 /* Calculate integral products */
331 long myIntegral = ((myX1 * myY2) + (myY1 * myX2)) << Integer.SIZE;
332 myIntegral += myY2 * myX2;
333
334 /* Calculate product of X2 and Z1 and multiply by 2^32 */
335 long myProduct = myX2 * myZ1;
336 long myIntPart = myProduct / mySFactor;
337 long myFracPart = myProduct % mySFactor;
338 myIntegral += myIntPart << Integer.SIZE;
339 myFracPart *= INTEGER_BOOST;
340 myIntegral += myFracPart / mySFactor;
341 long myFractional = adjustDecimals(myFracPart % mySFactor, myT);
342
343 /* Calculate products of Y2 and Z1 */
344 myProduct = myY2 * myZ1;
345 myIntegral += myProduct / mySFactor;
346 myFractional += adjustDecimals(myProduct % mySFactor, myT);
347
348 /* Calculate product of X1 and Z2 and multiply by 2^32 */
349 myProduct = myX1 * myZ2;
350 myIntPart = myProduct / myTFactor;
351 myFracPart = myProduct % myTFactor;
352 myIntegral += myIntPart << Integer.SIZE;
353 myFracPart *= INTEGER_BOOST;
354 myIntegral += myFracPart / myTFactor;
355 myFractional += adjustDecimals(myFracPart % myTFactor, myS);
356
357 /* Calculate products of Y1 and Z2 */
358 myProduct = myY1 * myZ2;
359 myIntegral += myProduct / myTFactor;
360 myFractional += adjustDecimals(myProduct % myTFactor, myS);
361
362 /* Calculate products of Z1 and Z2 */
363 myFractional += myZ1 * myZ2;
364
365 /* Handle wrap of fractional */
366 myIntegral += myFracPart / mySTFactor;
367 myFractional %= mySTFactor;
368
369 /* Adjust decimals */
370 myFractional = adjustDecimals(myFractional, -myT);
371 if (myFractional >= mySFactor) {
372 myFractional %= mySFactor;
373 myIntegral++;
374 }
375
376 /* Store the result */
377 theIntegral = myIntegral;
378 theFractional = (int) myFractional;
379 theSign *= pMultiplicand.theSign;
380 }
381
382 /**
383 * Divide by another decimal.
384 * <p>
385 * This function uses BigDecimal to perform the calculation
386 *
387 * @param pDivisor the decimal to divide by
388 */
389 public void divide(final OceanusNewDecimal pDivisor) {
390 /* Calculate the result */
391 final BigDecimal myNumerator = toBigDecimal();
392 final BigDecimal myDenominator = pDivisor.toBigDecimal();
393 final BigDecimal myResult = myNumerator.divide(myDenominator, theScale, RoundingMode.HALF_UP);
394
395 /* Extract the integral and fractional parts */
396 theSign = myResult.signum();
397 theIntegral = myResult.longValue();
398 theIntegral *= theSign;
399 long myFractional = myResult.movePointRight(theScale).longValue() * theSign;
400 myFractional %= theFactor;
401 theFractional = (int) myFractional;
402 }
403
404 /**
405 * Convert to BigDecimal.
406 *
407 * @return the BigDecimal equivalent
408 */
409 public BigDecimal toBigDecimal() {
410 final BigDecimal myIntegral = new BigDecimal(theIntegral);
411 final BigDecimal myFractional = new BigDecimal(theFractional).movePointLeft(theScale);
412 final BigDecimal myResult = myIntegral.add(myFractional);
413 return theSign == -1 ? myResult.negate() : myResult;
414 }
415
416 @Override
417 public String toString() {
418 /* Format the string */
419 final StringBuilder myString = new StringBuilder(100);
420 if (theSign == -1) {
421 myString.append('-');
422 }
423 myString.append(theIntegral);
424 if (theScale > 0) {
425 final int myLen = myString.length();
426 myString.append(theFractional + theFactor);
427 myString.setCharAt(myLen, '.');
428 }
429
430 /* Return the string */
431 return myString.toString();
432 }
433
434 /**
435 * Build powers of ten.
436 *
437 * @param pMax maximum power of ten
438 * @return array of powers of ten
439 */
440 private static int[] getPowersOfTen(final int pMax) {
441 /* Allocate the array */
442 final int[] myArray = new int[pMax + 1];
443
444 /* Initialise array */
445 int myValue = 1;
446 myArray[0] = myValue;
447
448 /* Loop through array */
449 for (int i = 1; i < pMax + 1; i++) {
450 /* Adjust value and record it */
451 myValue *= RADIX_TEN;
452 myArray[i] = myValue;
453 }
454
455 /* Return the array */
456 return myArray;
457 }
458
459 /**
460 * Obtain factor.
461 *
462 * @param pDecimals the number of decimals
463 * @return the decimal part of the number
464 */
465 private static int getFactor(final int pDecimals) {
466 return POWERS_OF_TEN[pDecimals];
467 }
468
469 /**
470 * Adjust a value to a different number of decimals.
471 * <p>
472 * If the adjustment is to reduce the number of decimals, the most significant digit of the
473 * discarded digits is examined to determine whether to round up. If the number of decimals is
474 * to be increased, zeros are simply added to the end.
475 *
476 * @param pValue the value to adjust
477 * @param iAdjust the adjustment (positive if # of decimals are to increase, negative if they
478 * are to decrease)
479 * @return the adjusted value
480 */
481 private static long adjustDecimals(final long pValue,
482 final int iAdjust) {
483 /* Take a copy of the value */
484 long myValue = pValue;
485
486 /* If we need to reduce decimals */
487 if (iAdjust < 0) {
488 /* If we have more than one decimal to remove */
489 if (iAdjust + 1 < 0) {
490 /* Calculate division factor (minus one) */
491 final long myFactor = getFactor(-(iAdjust + 1));
492
493 /* Reduce to 10 times required value */
494 myValue /= myFactor;
495 }
496
497 /* Access last digit */
498 long myDigit = myValue
499 % RADIX_TEN;
500
501 /* Handle negative values */
502 int myAdjust = 1;
503 if (myDigit < 0) {
504 myAdjust = -1;
505 myDigit = -myDigit;
506 }
507
508 /* Reduce final decimal and round up if required */
509 myValue /= RADIX_TEN;
510 if (myDigit >= (RADIX_TEN >> 1)) {
511 myValue += myAdjust;
512 }
513
514 /* else if we need to expand fractional product */
515 } else if (iAdjust > 0) {
516 myValue *= getFactor(iAdjust);
517 }
518
519 /* Return the adjusted value */
520 return myValue;
521 }
522 }