001/*
002 * The MIT License (MIT)
003 *
004 * Copyright (c) 2015-2023 decimal4j (tools4j), Marco Terzer
005 *
006 * Permission is hereby granted, free of charge, to any person obtaining a copy
007 * of this software and associated documentation files (the "Software"), to deal
008 * in the Software without restriction, including without limitation the rights
009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010 * copies of the Software, and to permit persons to whom the Software is
011 * furnished to do so, subject to the following conditions:
012 *
013 * The above copyright notice and this permission notice shall be included in all
014 * copies or substantial portions of the Software.
015 *
016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
022 * SOFTWARE.
023 */
024package org.decimal4j.arithmetic;
025
026import org.decimal4j.api.DecimalArithmetic;
027import org.decimal4j.scale.Scale0f;
028import org.decimal4j.scale.ScaleMetrics;
029import org.decimal4j.scale.Scales;
030import org.decimal4j.truncate.DecimalRounding;
031
032/**
033 * Provides static methods to calculate additions.
034 */
035final class Add {
036
037        /**
038         * Calculates unchecked unrounded addition of a long value and an unscaled
039         * value with the given scale.
040         * 
041         * @param lValue
042         *            the long value
043         * @param unscaled
044         *            the unscaled value
045         * @param scale
046         *            the scale of the second value
047         * @return the addition result without rounding and without overflow checks
048         */
049        public static final long addLongUnscaled(long lValue, long unscaled, int scale) {
050                return addUnscaledUnscaled(Scale0f.INSTANCE, lValue, unscaled, scale);
051        }
052
053        /**
054         * Calculates unchecked rounded addition of a long value and an unscaled
055         * value with the given scale.
056         * 
057         * @param rounding
058         *            the rounding to apply
059         * @param lValue
060         *            the long value
061         * @param unscaled
062         *            the unscaled value
063         * @param scale
064         *            the scale of the second value
065         * @return the addition result with rounding but without overflow checks
066         */
067        public static final long addLongUnscaled(DecimalRounding rounding, long lValue, long unscaled, int scale) {
068                return addUnscaledUnscaled(Scale0f.INSTANCE, rounding, lValue, unscaled, scale);
069        }
070
071        /**
072         * Calculates unchecked addition of an unscaled value and a long value.
073         * 
074         * @param arith
075         *            the arithmetic associated with the first value
076         * @param uDecimal
077         *            the unscaled value
078         * @param lValue
079         *            the long value
080         * @return the addition result without overflow checks
081         */
082        public static final long addUnscaledLong(DecimalArithmetic arith, long uDecimal, long lValue) {
083                return uDecimal + Pow10.multiplyByPowerOf10(lValue, arith.getScale());
084        }
085
086        /**
087         * Calculates checked addition of an unscaled value and a long value.
088         * 
089         * @param arith
090         *            the arithmetic associated with the first value
091         * @param uDecimal
092         *            the unscaled value
093         * @param lValue
094         *            the long value
095         * @return the addition result performed with overflow checks
096         */
097        public static final long addUnscaledLongChecked(DecimalArithmetic arith, long uDecimal, long lValue) {
098                final int scale = arith.getScale();
099                if (lValue == 0 | scale == 0) {
100                        return arith.add(uDecimal, lValue);
101                }
102                try {
103                        return addForNegativeScaleDiff(arith, uDecimal, lValue, -scale);
104                } catch (ArithmeticException e) {
105                        throw Exceptions.newArithmeticExceptionWithCause("Overflow: " + arith.toString(uDecimal) + " + " + lValue, e);
106                }
107        }
108
109        /**
110         * Calculates unchecked unrounded addition of an unscaled value and another
111         * unscaled value with the given {@code scaleMetrics} and {@code scale},
112         * respectively.
113         * 
114         * @param scaleMetrics
115         *            the scaleMetrics associated with the first value
116         * @param uDecimal
117         *            the first unscaled value
118         * @param unscaled
119         *            the second unscaled value
120         * @param scale
121         *            the scale of the second value
122         * @return the addition result without rounding and without overflow checks
123         */
124        public static final long addUnscaledUnscaled(ScaleMetrics scaleMetrics, long uDecimal, long unscaled, int scale) {
125                if (scale > Scales.MAX_SCALE) {
126                        throw new IllegalArgumentException("Illegal scale, must be <=" + Scales.MAX_SCALE + " but was " + scale);
127                }
128                final int scaleDiff = scale - scaleMetrics.getScale();
129                if (unscaled == 0 | scaleDiff == 0) {
130                        return uDecimal + unscaled;
131                } else if (scaleDiff < 0) {
132                        return uDecimal + Pow10.divideByPowerOf10(unscaled, scaleDiff);//multiplication
133                }
134                return addForPositiveScaleDiff(uDecimal, unscaled, scaleDiff);
135        }
136
137        /**
138         * Calculates unchecked rounded addition of an unscaled value and another
139         * unscaled value with the given {@code scaleMetrics} and {@code scale},
140         * respectively.
141         * 
142         * @param scaleMetrics
143         *            the scaleMetrics associated with the first value
144         * @param rounding
145         *            the rounding to apply
146         * @param uDecimal
147         *            the first unscaled value
148         * @param unscaled
149         *            the second unscaled value
150         * @param scale
151         *            the scale of the second value
152         * @return the addition result with rounding but without overflow checks
153         */
154        public static final long addUnscaledUnscaled(ScaleMetrics scaleMetrics, DecimalRounding rounding, long uDecimal, long unscaled, int scale) {
155                if (scale > Scales.MAX_SCALE) {
156                        throw new IllegalArgumentException("Illegal scale, must be <=" + Scales.MAX_SCALE + " but was " + scale);
157                }
158                final int scaleDiff = scale - scaleMetrics.getScale();
159                if (unscaled == 0 | scaleDiff == 0) {
160                        return uDecimal + unscaled;
161                } else if (scaleDiff < 0) {
162                        return uDecimal + Pow10.divideByPowerOf10(unscaled, scaleDiff);//multiplication
163                }
164                //scale > 0
165                return addForPositiveScaleDiff(rounding, uDecimal, unscaled, scaleDiff);
166        }
167
168        /**
169         * Calculates checked unrounded addition of an unscaled value and another
170         * unscaled value with the given {@code scaleMetrics} and {@code scale},
171         * respectively.
172         * 
173         * @param arith
174         *            the arithmetic associated with the first value
175         * @param uDecimal
176         *            the first unscaled value
177         * @param unscaled
178         *            the second unscaled value
179         * @param scale
180         *            the scale of the second value
181         * @return the addition result without rounding but with overflow checks
182         */
183        public static final long addUnscaledUnscaledChecked(DecimalArithmetic arith, long uDecimal, long unscaled, int scale) {
184                if (scale > Scales.MAX_SCALE) {
185                        throw new IllegalArgumentException("Illegal scale, must be <=" + Scales.MAX_SCALE + " but was " + scale);
186                }
187                final int scaleDiff = scale - arith.getScale();
188                if (unscaled == 0 | scaleDiff == 0) {
189                        return arith.add(uDecimal, unscaled);
190                } else if (scaleDiff < 0) {
191                        try {
192                                return addForNegativeScaleDiff(arith, uDecimal, unscaled, scaleDiff);
193                        } catch (ArithmeticException e) {
194                                throw Exceptions.newArithmeticExceptionWithCause("Overflow: " + arith.toString(uDecimal) + " + " + unscaled + "*10^" + (-scale), e);
195                        }
196                }
197                final long sum = addForPositiveScaleDiff(uDecimal, unscaled, scaleDiff);
198                if (!Checked.isAddOverflow(uDecimal, unscaled, sum)) {
199                        return sum;
200                }
201                throw new ArithmeticException("Overflow: " + arith.toString(uDecimal) + " + " + unscaled + "*10^" + (-scale) + "=" + sum);
202        }
203        
204        /**
205         * Calculates checked rounded addition of an unscaled value and another
206         * unscaled value with the given {@code arith} and {@code scale},
207         * respectively.
208         * 
209         * @param arith
210         *            the arithmetic associated with the first value
211         * @param rounding
212         *            the rounding to apply
213         * @param uDecimal
214         *            the first unscaled value
215         * @param unscaled
216         *            the second unscaled value
217         * @param scale
218         *            the scale of the second value
219         * @return the addition result with rounding and overflow checks
220         */
221        public static final long addUnscaledUnscaledChecked(DecimalArithmetic arith, DecimalRounding rounding, long uDecimal, long unscaled, int scale) {
222                if (scale > Scales.MAX_SCALE) {
223                        throw new IllegalArgumentException("Illegal scale, must be <=" + Scales.MAX_SCALE + " but was " + scale);
224                }
225                final int scaleDiff = scale - arith.getScale();
226                if (unscaled == 0 | scaleDiff == 0) {
227                        return arith.add(uDecimal, unscaled);
228                } else if (scaleDiff < 0) {
229                        try {
230                                return addForNegativeScaleDiff(arith, uDecimal, unscaled, scaleDiff);
231                        } catch (ArithmeticException e) {
232                                throw Exceptions.newArithmeticExceptionWithCause("Overflow: " + arith.toString(uDecimal) + " + " + unscaled + "*10^" + (-scale), e);
233                        }
234                }
235                final long sum = addForPositiveScaleDiff(rounding, uDecimal, unscaled, scaleDiff);
236                if (!Checked.isAddOverflow(uDecimal, unscaled, sum)) {
237                        return sum;
238                }
239                throw new ArithmeticException("Overflow: " + arith.toString(uDecimal) + " + " + unscaled + "*10^" + (-scale) + "=" + sum);
240        }
241
242        /**
243         * Calculates unchecked unrounded addition of an unscaled value and another
244         * unscaled value with the given {@code scaleDiff=scale2-scale1 > 0}.
245         * 
246         * @param uDecimal
247         *            the first unscaled value
248         * @param unscaled
249         *            the second unscaled value
250         * @param scaleDiff
251         *            scale2 - scale1, must be positive
252         * @return the addition result without rounding and without overflow checks
253         */
254        //PRECONDITION: scaleDiff > 0
255        private static final long addForPositiveScaleDiff(long uDecimal, long unscaled, int scaleDiff) {
256                //scaleDiff > 0
257                final ScaleMetrics diffMetrics = Scales.getScaleMetrics(scaleDiff);
258                final long trunc = diffMetrics.divideByScaleFactor(unscaled);
259                final long sum = uDecimal + trunc;
260                if (uDecimal == 0 | sum == 0 | (uDecimal ^ unscaled) >= 0 | (sum ^ unscaled) >= 0) { 
261                        return sum;
262                }
263                final long remainder = unscaled - diffMetrics.multiplyByScaleFactor(trunc);
264                return sum + Long.signum(remainder);
265        }
266
267        /**
268         * Calculates unchecked rounded addition of an unscaled value and another
269         * unscaled value with the given {@code scaleDiff=scale2-scale1 > 0}.
270         * 
271         * @param rounding
272         *            the rounding to apply
273         * @param uDecimal
274         *            the first unscaled value
275         * @param unscaled
276         *            the second unscaled value
277         * @param scaleDiff
278         *            scale2 - scale1, must be positive
279         * @return the addition result with rounding but without overflow checks
280         */
281        //PRECONDITION: scaleDiff > 0
282        private static final long addForPositiveScaleDiff(DecimalRounding rounding, long uDecimal, long unscaled, int scaleDiff) {
283                //scaleDiff > 0
284                final ScaleMetrics diffMetrics = Scales.getScaleMetrics(scaleDiff);
285                final long trunc = diffMetrics.divideByScaleFactor(unscaled);
286                final long remainder = unscaled - diffMetrics.multiplyByScaleFactor(trunc);
287                final long sum = uDecimal + trunc;
288                if (uDecimal == 0 | sum == 0 | (uDecimal ^ unscaled) >= 0 | (sum ^ unscaled) >= 0) { 
289                        return sum + Rounding.calculateRoundingIncrement(rounding, sum, remainder, diffMetrics.getScaleFactor());
290                }
291                return sum + Rounding.calculateRoundingIncrement(RoundingInverse.ADDITIVE_REVERSION.invert(rounding), sum, remainder, diffMetrics.getScaleFactor());
292        }
293
294        /**
295         * Calculates checked addition of an unscaled value and another
296         * unscaled value with the given {@code scaleDiff = scal2 - scale1 > 0} which must be negative, such that the added
297         * value can be rescaled through multiplication.
298         * 
299         * @param arith
300         *            the arithmetic associated with the first value
301         * @param uDecimal
302         *            the first unscaled value
303         * @param unscaled
304         *            the second unscaled value
305         * @param scaleDiff
306         *            the scale of the second value
307         * @return the addition result with overflow checks
308         */
309        //PRECONDITION: scaleDiff < 0
310        private static final long addForNegativeScaleDiff(DecimalArithmetic arith, long uDecimal, long unscaled, int scaleDiff) {
311                //NOTE: multiplication by power of 10 may lead to an overflow but the result may still be valid if signs are opposite
312                //              --> therefore we multiply only half of the value with pow10 and add it twice
313                //              --> then we add the remainder 1 (x pow10) if the value was odd (again in halves to avoid overflow)
314                final long half = Pow10.divideByPowerOf10Checked(arith, unscaled / 2, scaleDiff);//multiplication;
315                final long halfReminder = ((unscaled & 0x1) == 0) ? 0 : Pow10.divideByPowerOf10Checked(arith, unscaled > 0 ? 5 : -5, scaleDiff + 1);
316                long result = uDecimal;
317                result = arith.add(result, half);
318                result = arith.add(result, half);
319                result = arith.add(result, halfReminder);
320                result = arith.add(result, halfReminder);
321                return result;
322        }
323
324        // no instances
325        private Add() {
326                super();
327        }
328}