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.truncate.DecimalRounding;
028import org.decimal4j.truncate.OverflowMode;
029import org.decimal4j.truncate.TruncatedPart;
030
031/**
032 * Provides methods for left and right shifts.
033 */
034final class Shift {
035
036        /**
037         * Performs a shift left operation applying the given rounding mode if
038         * rounding is necessary. Overflows are siltently truncated.
039         * 
040         * @param rounding
041         *            the rounding to apply for negative position (i.e. a right
042         *            shift)
043         * @param uDecimal
044         *            the value to shift
045         * @param positions
046         *            the positions to shift
047         * @return {@code round(uDecimal << positions)}
048         */
049        public static final long shiftLeft(DecimalRounding rounding, long uDecimal, int positions) {
050                if (positions >= 0) {
051                        return positions < Long.SIZE ? uDecimal << positions : 0;
052                }
053                // one shift missing for (-Integer.MIN_VALUE) but does not matter as
054                // result is always between 0 (incl) and 0.5 (excl)
055                return shiftRight(rounding, uDecimal, -positions > 0 ? -positions : Integer.MAX_VALUE);
056        }
057
058        /**
059         * Performs a shift right operation applying the given rounding mode if
060         * rounding is necessary. Overflows are siltently truncated.
061         * 
062         * @param rounding
063         *            the rounding to apply if necessary
064         * @param uDecimal
065         *            the value to shift
066         * @param positions
067         *            the positions to shift
068         * @return {@code round(uDecimal >> positions)}
069         */
070        public static final long shiftRight(DecimalRounding rounding, long uDecimal, int positions) {
071                if (uDecimal == 0 | positions == 0) {
072                        return uDecimal;
073                }
074                if (positions >= 0) {
075                        if (rounding == DecimalRounding.FLOOR) {
076                                return positions < Long.SIZE ? uDecimal >> positions : (uDecimal >= 0 ? 0 : -1);
077                        }
078                        if (positions < Long.SIZE) {
079                                final long truncated = uDecimal >= 0 ? (uDecimal >>> positions) : -(-uDecimal >>> positions);
080                                final long remainder = uDecimal - (truncated << positions);
081                                final TruncatedPart truncatedPart = positions == 63 ? Rounding.truncatedPartFor2pow63(remainder)
082                                                : Rounding.truncatedPartFor(Math.abs(remainder), 1L << positions);
083                                return truncated + rounding.calculateRoundingIncrement(Long.signum(uDecimal), truncated, truncatedPart);
084                        }
085                        if (positions == Long.SIZE) {
086                                return rounding.calculateRoundingIncrement(Long.signum(uDecimal), 0,
087                                                Rounding.truncatedPartFor2pow64(Math.abs(uDecimal)));
088                        }
089                        return rounding.calculateRoundingIncrement(Long.signum(uDecimal), 0,
090                                        TruncatedPart.LESS_THAN_HALF_BUT_NOT_ZERO);
091                }
092                return positions > -Long.SIZE ? uDecimal << -positions : 0;
093        }
094
095        /**
096         * Performs a shift left operation applying the given rounding mode if
097         * rounding is necessary. Throws an exception if an overflow occurs.
098         * 
099         * @param arith
100         *            the arithmetic associated with the shifted value
101         * @param rounding
102         *            the rounding to apply for negative position (i.e. a right
103         *            shift)
104         * @param uDecimal
105         *            the value to shift
106         * @param positions
107         *            the positions to shift
108         * @return {@code round(uDecimal << positions)}
109         * @throws ArithmeticException
110         *             if an overflow occurs and the arithmetic's
111         *             {@link OverflowMode} is set to throw an exception
112         */
113        public static final long shiftLeftChecked(DecimalArithmetic arith, DecimalRounding rounding, long uDecimal, int positions) {
114                if (positions >= 0) {
115                        if (uDecimal == 0 | positions == 0) {
116                                return uDecimal;
117                        }
118                        if (positions < Long.SIZE) {
119                                if (uDecimal > 0) {
120                                        if (positions < Long.SIZE - 1) {
121                                                final int leadingZeros = Long.numberOfLeadingZeros(uDecimal);
122                                                if (leadingZeros > positions) {
123                                                        return uDecimal << positions;
124                                                }
125                                        }
126                                } else if (uDecimal > Long.MIN_VALUE) {
127                                        final int leadingZeros = Long.numberOfLeadingZeros(~uDecimal);
128                                        if (leadingZeros > positions) {
129                                                return uDecimal << positions;
130                                        }
131                                }
132                        }
133                        throw new ArithmeticException("Overflow: " + arith.toString(uDecimal) + " << " + positions + " = "
134                                        + arith.toString(uDecimal << positions));
135                }
136                // one shift missing for (-Integer.MIN_VALUE) but does not matter as
137                // result is always between 0 (incl) and 0.5 (excl)
138                return shiftRight(rounding, uDecimal, -positions > 0 ? -positions : Integer.MAX_VALUE);
139        }
140
141        /**
142         * Performs a shift right operation applying the given rounding mode if
143         * rounding is necessary. Throws an exception if an overflow occurs.
144         * 
145         * @param arith
146         *            the arithmetic associated with the shifted value
147         * @param rounding
148         *            the rounding to apply if necessary
149         * @param uDecimal
150         *            the value to shift
151         * @param positions
152         *            the positions to shift
153         * @return {@code round(uDecimal >> positions)}
154         * @throws ArithmeticException
155         *             if an overflow occurs and the arithmetic's
156         *             {@link OverflowMode} is set to throw an exception
157         */
158        public static final long shiftRightChecked(DecimalArithmetic arith, DecimalRounding rounding, long uDecimal, int positions) {
159                if (uDecimal == 0) {
160                        return 0;
161                }
162                if (positions >= 0) {
163                        return shiftRight(rounding, uDecimal, positions);
164                }
165                if (positions > -Long.SIZE) {
166                        try {
167                                return shiftLeftChecked(arith, rounding, uDecimal, -positions);
168                        } catch (ArithmeticException e) {
169                                // ignore, throw again below with correct shift direction
170                        }
171                }
172                throw new ArithmeticException("Overflow: " + arith.toString(uDecimal) + " >> " + positions + " = "
173                                + arith.toString(uDecimal >> positions));
174        }
175
176        // no instances
177        private Shift() {
178                super();
179        }
180}