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.ScaleMetrics;
028import org.decimal4j.truncate.DecimalRounding;
029
030/**
031 * Contains static methods to convert between different scales.
032 */
033final class UnscaledConversion {
034
035        private static final int getScaleDiff(ScaleMetrics scaleMetrics, int scale) {
036                return getScaleDiff(scaleMetrics.getScale(), scale);
037        }
038
039        private static final int getScaleDiff(int targetScale, int sourceScale) {
040                final int diffScale = targetScale - sourceScale;
041                if (!Checked.isSubtractOverflow(targetScale, sourceScale, diffScale)) {
042                        return diffScale;
043                }
044                throw new IllegalArgumentException("Cannot convert from scale " + sourceScale + " to " + targetScale
045                                + " (scale difference is out of integer range)");
046        }
047
048        /**
049         * Converts the given {@code unscaledValue} with the specified {@code scale}
050         * to a long value. The value is rounded DOWN if necessary. An exception is
051         * thrown if the conversion is not possible.
052         * 
053         * @param arith
054         *            arithmetic of the target value
055         * @param unscaledValue
056         *            the unscaled value to convert
057         * @param scale
058         *            the scale of {@code unscaledValue}
059         * @return a long value rounded down if necessary
060         * @throws IllegalArgumentException
061         *             if the conversion cannot be performed due to overflow
062         */
063        public static final long unscaledToLong(DecimalArithmetic arith, long unscaledValue, int scale) {
064                try {
065                        return Pow10.divideByPowerOf10Checked(arith, unscaledValue, scale);
066                } catch (ArithmeticException e) {
067                        throw toIllegalArgumentExceptionOrRethrow(e, unscaledValue, scale, arith.getScale());
068                }
069        }
070
071        /**
072         * Converts the given {@code unscaledValue} with the specified {@code scale}
073         * to a long value. The value is rounded using the specified
074         * {@code rounding} if necessary. An exception is thrown if the conversion
075         * is not possible.
076         * 
077         * @param arith
078         *            arithmetic of the target value
079         * @param rounding
080         *            the rounding to apply if rounding is necessary
081         * @param unscaledValue
082         *            the unscaled value to convert
083         * @param scale
084         *            the scale of {@code unscaledValue}
085         * @return long value rounded with given rounding if necessary
086         * @throws IllegalArgumentException
087         *             if the conversion cannot be performed due to overflow
088         * @throws ArithmeticException
089         *             if rounding is necessary and {@code rounding==UNNECESSARY}
090         */
091        public static final long unscaledToLong(DecimalArithmetic arith, DecimalRounding rounding, long unscaledValue, int scale) {
092                try {
093                        return Pow10.divideByPowerOf10Checked(arith, rounding, unscaledValue, scale);
094                } catch (ArithmeticException e) {
095                        throw toIllegalArgumentExceptionOrRethrow(e, unscaledValue, scale, arith.getScale());
096                }
097        }
098
099        /**
100         * Returns an unscaled value of the scale defined by {@code arith} given an
101         * {@code unscaledValue} with its {@code scale}. The value is rounded DOWN
102         * if necessary. An exception is thrown if the conversion is not possible.
103         * 
104         * @param arith
105         *            arithmetic defining the target scale
106         * @param unscaledValue
107         *            the unscaled value to convert
108         * @param scale
109         *            the scale of {@code unscaledValue}
110         * @return the unscaled value in the arithmetic's scale
111         * @throws IllegalArgumentException
112         *             if the conversion cannot be performed due to overflow
113         */
114        public static final long unscaledToUnscaled(DecimalArithmetic arith, long unscaledValue, int scale) {
115                final int scaleDiff = getScaleDiff(arith.getScaleMetrics(), scale);
116                try {
117                        return Pow10.multiplyByPowerOf10Checked(arith, unscaledValue, scaleDiff);
118                } catch (ArithmeticException e) {
119                        throw toIllegalArgumentExceptionOrRethrow(e, unscaledValue, scale, arith.getScale());
120                }
121        }
122
123        /**
124         * Returns an unscaled value of the scale defined by {@code arith} given an
125         * {@code unscaledValue} with its {@code scale}. The value is rounded using
126         * the specified {@code rounding} if necessary. An exception is thrown if
127         * the conversion is not possible.
128         * 
129         * @param arith
130         *            arithmetic defining the target scale
131         * @param rounding
132         *            the rounding to apply if rounding is necessary
133         * @param unscaledValue
134         *            the unscaled value to convert
135         * @param scale
136         *            the scale of {@code unscaledValue}
137         * @return the unscaled value in the arithmetic's scale
138         * @throws IllegalArgumentException
139         *             if the conversion cannot be performed due to overflow
140         * @throws ArithmeticException
141         *             if rounding is necessary and {@code rounding==UNNECESSARY}
142         */
143        public static final long unscaledToUnscaled(DecimalArithmetic arith, DecimalRounding rounding, long unscaledValue, int scale) {
144                final int scaleDiff = getScaleDiff(arith.getScaleMetrics(), scale);
145                try {
146                        return Pow10.multiplyByPowerOf10Checked(arith, rounding, unscaledValue, scaleDiff);
147                } catch (ArithmeticException e) {
148                        throw toIllegalArgumentExceptionOrRethrow(e, unscaledValue, scale, arith.getScale());
149                }
150        }
151
152        /**
153         * Converts an unscaled value {@code uDecimal} having the scale specified by
154         * {@code arith} into another unscaled value of the provided
155         * {@code targetScale}. The value is rounded DOWN if necessary. An exception
156         * is thrown if the conversion is not possible.
157         * 
158         * @param targetScale
159         *            the scale of the result value
160         * @param arith
161         *            arithmetic defining the source scale
162         * @param uDecimal
163         *            the unscaled value to convert
164         * @return the unscaled value with {@code targetScale}
165         * @throws IllegalArgumentException
166         *             if the conversion cannot be performed due to overflow
167         */
168        public static final long unscaledToUnscaled(int targetScale, DecimalArithmetic arith, long uDecimal) {
169                final int scaleDiff = getScaleDiff(targetScale, arith.getScale());
170                try {
171                        return Pow10.multiplyByPowerOf10Checked(arith, uDecimal, scaleDiff);
172                } catch (ArithmeticException e) {
173                        throw toIllegalArgumentExceptionOrRethrow(e, uDecimal, arith.getScale(), targetScale);
174                }
175        }
176
177        /**
178         * Converts an unscaled value {@code uDecimal} having the scale specified by
179         * {@code arith} into another unscaled value of the provided
180         * {@code targetScale}. The value is rounded using the specified
181         * {@code rounding} if necessary. An exception is thrown if the conversion
182         * is not possible.
183         * 
184         * 
185         * @param rounding
186         *            the rounding to apply if rounding is necessary
187         * @param targetScale
188         *            the scale of the result value
189         * @param arith
190         *            arithmetic defining the source scale
191         * @param uDecimal
192         *            the unscaled value to convert
193         * @return the unscaled value with {@code targetScale}
194         * @throws IllegalArgumentException
195         *             if the conversion cannot be performed due to overflow
196         */
197        public static final long unscaledToUnscaled(DecimalRounding rounding, int targetScale, DecimalArithmetic arith, long uDecimal) {
198                final int scaleDiff = getScaleDiff(targetScale, arith.getScale());
199                try {
200                        return Pow10.multiplyByPowerOf10Checked(arith, rounding, uDecimal, scaleDiff);
201                } catch (ArithmeticException e) {
202                        throw toIllegalArgumentExceptionOrRethrow(e, uDecimal, arith.getScale(), targetScale);
203                }
204        }
205
206        private static final IllegalArgumentException toIllegalArgumentExceptionOrRethrow(ArithmeticException e, long unscaledValue, int sourceScale, int targetScale) {
207                Exceptions.rethrowIfRoundingNecessary(e);
208                if (targetScale > 0) {
209                        return new IllegalArgumentException("Overflow: Cannot convert unscaled value " + unscaledValue
210                                        + " from scale " + sourceScale + " to scale " + targetScale, e);
211                } else {
212                        return new IllegalArgumentException("Overflow: Cannot convert unscaled value " + unscaledValue
213                                        + " from scale " + sourceScale + " to long", e);
214                }
215        }
216
217        // no instances
218        private UnscaledConversion() {
219        }
220}