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.scale.Scales;
029import org.decimal4j.truncate.DecimalRounding;
030
031/**
032 * Contains static helper methods to round Decimal values.
033 */
034final class Round {
035
036        /**
037         * Truncates the specified value to the given precision.
038         * 
039         * @param arith
040         *            the arithmetic associated with the value
041         * @param uDecimal
042         *            the unscaled decimal value
043         * @param precision
044         *            the precision to round to
045         * @return <code>round<sub>DOWN</sub>(uDecimal, precision)</code>
046         */
047        public static final long round(DecimalArithmetic arith, long uDecimal, int precision) {
048                final ScaleMetrics scaleMetrics = arith.getScaleMetrics();
049                final int scale = scaleMetrics.getScale();
050                final ScaleMetrics deltaMetrics;
051                if (precision == 0) {
052                        deltaMetrics = scaleMetrics;
053                } else if (precision < scale) {
054                        final int deltaScale = scale - precision;
055                        if (deltaScale <= 18) {
056                                deltaMetrics = Scales.getScaleMetrics(scale - precision);
057                        } else {
058                                throw new IllegalArgumentException("scale - precision must be <= 18 but was " + deltaScale
059                                                + " for scale=" + scale + " and precision=" + precision);
060                        }
061                } else {
062                        // precision >= scale
063                        return uDecimal;
064                }
065                return uDecimal - deltaMetrics.moduloByScaleFactor(uDecimal);
066        }
067
068        /**
069         * Rounds the specified value to the given precision.
070         * 
071         * @param arith
072         *            the arithmetic associated with the value
073         * @param rounding
074         *            the rounding to apply
075         * @param uDecimal
076         *            the unscaled decimal value
077         * @param precision
078         *            the precision to round to
079         * @return <code>round(uDecimal, precision)</code>
080         */
081        public static final long round(DecimalArithmetic arith, DecimalRounding rounding, long uDecimal, int precision) {
082                final ScaleMetrics scaleMetrics = arith.getScaleMetrics();
083                final int scale = scaleMetrics.getScale();
084                final int deltaScale = scale - precision;
085                final ScaleMetrics deltaMetrics;
086                if (precision == 0) {
087                        deltaMetrics = scaleMetrics;
088                } else if (precision < scale) {
089                        if (deltaScale <= 18) {
090                                deltaMetrics = Scales.getScaleMetrics(scale - precision);
091                        } else {
092                                throw new IllegalArgumentException("scale - precision must be <= 18 but was " + deltaScale
093                                                + " for scale=" + scale + " and precision=" + precision);
094                        }
095                } else {
096                        // precision >= scale
097                        return uDecimal;
098                }
099                if (uDecimal == 0) {
100                        return 0;
101                }
102                final long truncatedDigits = deltaMetrics.moduloByScaleFactor(uDecimal);
103                final long truncatedValue = uDecimal - truncatedDigits;
104                final long truncatedOddEven = truncatedValue >> deltaScale; // move odd
105                                                                                                                                        // bit into
106                                                                                                                                        // place for
107                                                                                                                                        // HALF_EVEN
108                                                                                                                                        // rounding
109                final long roundingInc = Rounding.calculateRoundingIncrement(rounding, truncatedOddEven, truncatedDigits,
110                                deltaMetrics.getScaleFactor());
111                return arith.add(truncatedValue, roundingInc == 0 ? 0 : deltaMetrics.multiplyByScaleFactor(roundingInc));// must
112                                                                                                                                                                                                                                        // add
113                                                                                                                                                                                                                                        // via
114                                                                                                                                                                                                                                        // arith
115                                                                                                                                                                                                                                        // to
116                                                                                                                                                                                                                                        // check
117                                                                                                                                                                                                                                        // for
118                                                                                                                                                                                                                                        // overflow
119        }
120
121        // no instances
122        private Round() {
123                super();
124        }
125
126}