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 java.math.BigDecimal;
027import java.math.BigInteger;
028import java.math.RoundingMode;
029
030import org.decimal4j.scale.ScaleMetrics;
031import org.decimal4j.scale.Scales;
032
033/**
034 * Contains methods to convert from and to {@link BigDecimal}.
035 */
036final class BigDecimalConversion {
037
038        /**
039         * Converts the specified big decimal value to a long value applying the
040         * given rounding mode. An exception is thrown if the value exceeds the
041         * valid long range.
042         * 
043         * @param roundingMode
044         *            the rounding mode to apply if necessary
045         * @param value
046         *            the big decimal value to convert
047         * @return <code>round(value)</code>
048         * @throws IllegalArgumentException
049         *             if the value is outside of the valid long range
050         * @throws ArithmeticException
051         *             if {@code roundingMode==UNNECESSARY} and rounding is
052         *             necessary
053         */
054        public static final long bigDecimalToLong(RoundingMode roundingMode, BigDecimal value) {
055                // TODO any chance to make this garbage free?
056                // Difficult as we cannot look inside the BigDecimal value
057                final BigInteger scaled = value//
058                                .setScale(0, roundingMode)//
059                                .toBigInteger();
060                if (scaled.bitLength() <= 63) {
061                        return scaled.longValue();
062                }
063                throw new IllegalArgumentException("Overflow: cannot convert " + value + " to long");
064        }
065
066        /**
067         * Converts the specified big decimal value to an unscaled decimal applying
068         * the given rounding mode if necessary. An exception is thrown if the value
069         * exceeds the valid Decimal range.
070         * 
071         * @param scaleMetrics
072         *            the scale metrics of the result value
073         * @param roundingMode
074         *            the rounding mode to apply if necessary
075         * @param value
076         *            the big decimal value to convert
077         * @return <code>round(value)</code>
078         * @throws IllegalArgumentException
079         *             if the value is outside of the valid Decimal range
080         * @throws ArithmeticException
081         *             if {@code roundingMode==UNNECESSARY} and rounding is
082         *             necessary
083         */
084        public static final long bigDecimalToUnscaled(ScaleMetrics scaleMetrics, RoundingMode roundingMode, BigDecimal value) {
085                // TODO any chance to make this garbage free?
086                // Difficult as we cannot look inside the BigDecimal value
087                final BigInteger scaled = value//
088                                .multiply(scaleMetrics.getScaleFactorAsBigDecimal())//
089                                .setScale(0, roundingMode)//
090                                .toBigInteger();
091                if (scaled.bitLength() <= 63) {
092                        return scaled.longValue();
093                }
094                throw new IllegalArgumentException(
095                                "Overflow: cannot convert " + value + " to Decimal with scale " + scaleMetrics.getScale());
096        }
097
098        /**
099         * Converts the given unscaled decimal value to a {@link BigDecimal} of the
100         * same scale as the given decimal value.
101         * 
102         * @param scaleMetrics
103         *            the scale metrics associated with the unscaled value
104         * @param uDecimal
105         *            the unscaled decimal value to convert
106         * @return a big decimal with the scale from scale metrics
107         */
108        public static final BigDecimal unscaledToBigDecimal(ScaleMetrics scaleMetrics, long uDecimal) {
109                return BigDecimal.valueOf(uDecimal, scaleMetrics.getScale());
110        }
111
112        /**
113         * Converts the given unscaled decimal value to a {@link BigDecimal} of the
114         * specified {@code targetScale} rounding the value if necessary.
115         * 
116         * @param scaleMetrics
117         *            the scale metrics associated with the unscaled value
118         * @param roundingMode
119         *            the rounding mode to use if rounding is necessary
120         * @param uDecimal
121         *            the unscaled decimal value to convert
122         * @param targetScale
123         *            the scale of the result value
124         * @return a big decimal with the specified {@code targetScale}
125         * @throws ArithmeticException
126         *             if {@code roundingMode==UNNECESSARY} and rounding is
127         *             necessary
128         */
129        public static final BigDecimal unscaledToBigDecimal(ScaleMetrics scaleMetrics, RoundingMode roundingMode, long uDecimal, int targetScale) {
130                final int sourceScale = scaleMetrics.getScale();
131                if (targetScale == sourceScale) {
132                        return unscaledToBigDecimal(scaleMetrics, uDecimal);
133                }
134                if (targetScale < sourceScale) {
135                        final int diff = sourceScale - targetScale;
136                        if (diff <= 18) {
137                                final ScaleMetrics diffMetrics = Scales.getScaleMetrics(diff);
138                                final long rescaled = diffMetrics.getArithmetic(roundingMode).divideByPowerOf10(uDecimal, diff);
139                                return BigDecimal.valueOf(rescaled, targetScale);
140                        }
141                } else {
142                        // does it fit in a long?
143                        final int diff = targetScale - sourceScale;
144                        if (diff <= 18) {
145                                final ScaleMetrics diffMetrics = Scales.getScaleMetrics(diff);
146                                if (diffMetrics.isValidIntegerValue(uDecimal)) {
147                                        final long rescaled = diffMetrics.multiplyByScaleFactor(uDecimal);
148                                        return BigDecimal.valueOf(rescaled, targetScale);
149                                }
150                        }
151                }
152                // let the big decimal deal with such large numbers then
153                return BigDecimal.valueOf(uDecimal, sourceScale).setScale(targetScale, roundingMode);
154        }
155
156        // no instances
157        private BigDecimalConversion() {
158                super();
159        }
160}