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;
028
029/**
030 * Provides static methods to calculate average of two numbers, that is,
031 * {@code (a+b)/2}.
032 */
033final class Avg {
034
035        /**
036         * Calculates and returns the average of the two values rounded DOWN.
037         * 
038         * @param a
039         *            the first value
040         * @param b
041         *            the second value
042         * @return <code>round<sub>DOWN</sub>((a + b) / 2)</code>
043         */
044        public static final long avg(long a, long b) {
045                final long xor = a ^ b;
046                final long floor = (a & b) + (xor >> 1);
047                return floor + ((floor >>> 63) & xor);
048        }
049
050        /**
051         * Calculates and returns the average of the two values applying the given
052         * rounding if necessary.
053         * 
054         * @param arith
055         *            the arithmetic associated with the two values
056         * @param rounding
057         *            the rounding to apply if necessary
058         * @param a
059         *            the first value
060         * @param b
061         *            the second value
062         * @return <code>round((a + b) / 2)</code>
063         */
064        public static final long avg(DecimalArithmetic arith, DecimalRounding rounding, long a, long b) {
065                final long xor = a ^ b;
066                switch (rounding) {
067                case FLOOR: {
068                        return (a & b) + (xor >> 1);
069                }
070                case CEILING: {
071                        return (a | b) - (xor >> 1);
072                }
073                case DOWN:// fallthrough
074                case HALF_DOWN: {
075                        final long floor = (a & b) + (xor >> 1);
076                        return floor + ((floor >>> 63) & xor);
077                }
078                case UP:// fallthrough
079                case HALF_UP: {
080                        final long floor = (a & b) + (xor >> 1);
081                        return floor + ((~floor >>> 63) & xor);
082                }
083                case HALF_EVEN: {
084                        final long xorShifted = xor >> 1;
085                        final long floor = (a & b) + xorShifted;
086                        // use ceiling if floor is odd
087                        return ((floor & 0x1) == 0) ? floor : (a | b) - xorShifted;
088                }
089                case UNNECESSARY: {
090                        final long floor = (a & b) + (xor >> 1);
091                        if ((xor & 0x1) != 0) {
092                                throw new ArithmeticException("Rounding necessary: " + arith.toString(a) + " avg " + arith.toString(b)
093                                                + " = " + arith.toString(floor));
094                        }
095                        return floor;
096                }
097                default: {
098                        // should not get here
099                        throw new IllegalArgumentException("Unsupported rounding mode: " + rounding);
100                }
101                }
102        }
103
104        // no instances
105        private Avg() {
106                super();
107        }
108}