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.truncate;
025
026import java.math.RoundingMode;
027import java.util.Collections;
028import java.util.EnumSet;
029import java.util.Set;
030
031/**
032 * Defines the same constants as {@link RoundingMode} and implements the
033 * functionality to actually perform such rounding.
034 */
035public enum DecimalRounding {
036
037        /**
038         * Rounding mode to round away from zero. Always increments the digit prior
039         * to a non-zero discarded fraction. Note that this rounding mode never
040         * decreases the magnitude of the calculated value.
041         * 
042         * @see RoundingMode#UP
043         */
044        UP {
045                @Override
046                public final RoundingMode getRoundingMode() {
047                        return RoundingMode.UP;
048                }
049                @Override
050                public final int calculateRoundingIncrement(int sgn, long truncatedValue, TruncatedPart truncatedPart) {
051                        if (truncatedPart.isGreaterThanZero()) {
052                                return sgn;
053                        }
054                        return 0;
055                }
056        },
057
058        /**
059         * Rounding mode to round towards zero. Never increments the digit prior to
060         * a discarded fraction (i.e., truncates). Note that this rounding mode
061         * never increases the magnitude of the calculated value.
062         * 
063         * @see RoundingMode#DOWN
064         */
065        DOWN {
066                @Override
067                public final RoundingMode getRoundingMode() {
068                        return RoundingMode.DOWN;
069                }
070                @Override
071                public final int calculateRoundingIncrement(int sgn, long truncatedValue, TruncatedPart truncatedPart) {
072                        return 0;
073                }
074        },
075
076        /**
077         * Rounding mode to round towards positive infinity. If the result is
078         * positive, behaves as for {@code RoundingMode.UP}; if negative, behaves as
079         * for {@code RoundingMode.DOWN}. Note that this rounding mode never
080         * decreases the calculated value.
081         * 
082         * @see RoundingMode#CEILING
083         */
084        CEILING {
085                @Override
086                public final RoundingMode getRoundingMode() {
087                        return RoundingMode.CEILING;
088                }
089                @Override
090                public final int calculateRoundingIncrement(int sgn, long truncatedValue, TruncatedPart truncatedPart) {
091                        if (sgn > 0) {
092                                if (truncatedPart.isGreaterThanZero()) {
093                                        return 1;
094                                }
095                        }
096                        return 0;
097                }
098        },
099
100        /**
101         * Rounding mode to round towards negative infinity. If the result is
102         * positive, behave as for {@code RoundingMode.DOWN}; if negative, behave as
103         * for {@code RoundingMode.UP}. Note that this rounding mode never increases
104         * the calculated value.
105         * 
106         * @see RoundingMode#FLOOR
107         */
108        FLOOR {
109                @Override
110                public final RoundingMode getRoundingMode() {
111                        return RoundingMode.FLOOR;
112                }
113                @Override
114                public final int calculateRoundingIncrement(int sgn, long truncatedValue, TruncatedPart truncatedPart) {
115                        if (sgn < 0) {
116                                if (truncatedPart.isGreaterThanZero()) {
117                                        return -1;
118                                }
119                        }
120                        return 0;
121                }
122        },
123
124        /**
125         * Rounding mode to round towards {@literal "nearest neighbor"} unless both
126         * neighbors are equidistant, in which case round up. Behaves as for
127         * {@code RoundingMode.UP} if the discarded fraction is &ge; 0.5; otherwise,
128         * behaves as for {@code RoundingMode.DOWN}. Note that this is the rounding
129         * mode commonly taught at school.
130         * 
131         * @see RoundingMode#HALF_UP
132         */
133        HALF_UP {
134                @Override
135                public final RoundingMode getRoundingMode() {
136                        return RoundingMode.HALF_UP;
137                }
138                @Override
139                public final int calculateRoundingIncrement(int sgn, long truncatedValue, TruncatedPart truncatedPart) {
140                        if (truncatedPart.isGreaterEqualHalf()) {
141                                return sgn;
142                        }
143                        return 0;
144                }
145        },
146
147        /**
148         * Rounding mode to round towards {@literal "nearest neighbor"} unless both
149         * neighbors are equidistant, in which case round down. Behaves as for
150         * {@code RoundingMode.UP} if the discarded fraction is &gt; 0.5; otherwise,
151         * behaves as for {@code RoundingMode.DOWN}.
152         * 
153         * @see RoundingMode#HALF_DOWN
154         */
155        HALF_DOWN {
156                @Override
157                public final RoundingMode getRoundingMode() {
158                        return RoundingMode.HALF_DOWN;
159                }
160                @Override
161                public final int calculateRoundingIncrement(int sgn, long truncatedValue, TruncatedPart truncatedPart) {
162                        if (truncatedPart.isGreaterThanHalf()) {
163                                return sgn;
164                        }
165                        return 0;
166                }
167        },
168
169        /**
170         * Rounding mode to round towards the {@literal "nearest neighbor"} unless
171         * both neighbors are equidistant, in which case, round towards the even
172         * neighbor. Behaves as for {@code RoundingMode.HALF_UP} if the digit to the
173         * left of the discarded fraction is odd; behaves as for
174         * {@code RoundingMode.HALF_DOWN} if it's even. Note that this is the
175         * rounding mode that statistically minimizes cumulative error when applied
176         * repeatedly over a sequence of calculations. It is sometimes known as
177         * {@literal "Banker's rounding,"} and is chiefly used in the USA. This
178         * rounding mode is analogous to the rounding policy used for {@code float}
179         * and {@code double} arithmetic in Java.
180         * 
181         * @see RoundingMode#HALF_EVEN
182         */
183        HALF_EVEN {
184                @Override
185                public final RoundingMode getRoundingMode() {
186                        return RoundingMode.HALF_EVEN;
187                }
188                @Override
189                public final int calculateRoundingIncrement(int sgn, long truncatedValue, TruncatedPart truncatedPart) {
190                        if (truncatedPart.isGreaterEqualHalf()) {
191                                if (truncatedPart.isGreaterThanHalf() | ((truncatedValue & 0x1) != 0)) {
192                                        return sgn;
193                                }
194                        }
195                        return 0;
196                }
197        },
198
199        /**
200         * Rounding mode to assert that the requested operation has an exact result,
201         * hence no rounding is necessary. If this rounding mode is specified on an
202         * operation that yields an inexact result, an {@code ArithmeticException}
203         * is thrown.
204         * 
205         * @see RoundingMode#UNNECESSARY
206         */
207        UNNECESSARY {
208                @Override
209                public final RoundingMode getRoundingMode() {
210                        return RoundingMode.UNNECESSARY;
211                }
212                @Override
213                public final int calculateRoundingIncrement(int sgn, long truncatedValue, TruncatedPart truncatedPart) {
214                        if (truncatedPart.isGreaterThanZero()) {
215                                throw new ArithmeticException("Rounding necessary");
216                        }
217                        return 0;
218                }
219        };
220
221        /**
222         * Immutable set with all values of this enum. Avoids object creation in
223         * contrast to {@link #values()}.
224         */
225        public static final Set<DecimalRounding> VALUES = Collections.unmodifiableSet(EnumSet.allOf(DecimalRounding.class));
226
227        /**
228         * Returns the {@link RoundingMode} associated with this decimal rounding
229         * constant.
230         * 
231         * @return the corresponding rounding mode
232         */
233        abstract public RoundingMode getRoundingMode();
234
235        /**
236         * Returns the rounding increment appropriate for this decimal rounding. The
237         * returned value is one of -1, 0 or 1.
238         * 
239         * @param sign
240         *            the sign of the total value, either +1 or -1; determines the
241         *            result value if rounded
242         * @param truncatedValue
243         *            the truncated result before rounding is applied
244         * @param truncatedPart
245         *            classification of the trunctated part of the value
246         * @return the value to add to {@code truncatedValue} to get the rounded
247         *         result, one of -1, 0 or 1
248         */
249        abstract public int calculateRoundingIncrement(int sign, long truncatedValue, TruncatedPart truncatedPart);
250
251        /**
252         * Returns the decimal rounding constant for the given rounding mode.
253         * 
254         * @param roundingMode
255         *            the rounding mode
256         * @return the constant corresponding to the given rounding mode
257         */
258        public static final DecimalRounding valueOf(RoundingMode roundingMode) {
259                return ByRoundingMode.VALUES_BY_ROUNDING_MODE_ORDINAL[roundingMode.ordinal()];
260        }
261
262        private static class ByRoundingMode {
263                private static final DecimalRounding[] VALUES_BY_ROUNDING_MODE_ORDINAL = sortByRoundingModeOrdinal();
264        
265                private static final DecimalRounding[] sortByRoundingModeOrdinal() {
266                        final DecimalRounding[] sorted = new DecimalRounding[VALUES.size()];
267                        for (final DecimalRounding dr : VALUES) {
268                                sorted[dr.getRoundingMode().ordinal()] = dr;
269                        }
270                        return sorted;
271                }
272        }
273}