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.base;
025
026import java.math.RoundingMode;
027
028import org.decimal4j.api.Decimal;
029import org.decimal4j.api.ImmutableDecimal;
030import org.decimal4j.arithmetic.Exceptions;
031import org.decimal4j.scale.ScaleMetrics;
032import org.decimal4j.scale.Scales;
033
034/**
035 * Base class for immutable {@link Decimal} classes of different scales.
036 * 
037 * @param <S>
038 *            the scale metrics type associated with this decimal
039 * @param <D>
040 *            the concrete class implementing this {@code ImmutableDecimal}
041 */
042@SuppressWarnings("serial")
043abstract public class AbstractImmutableDecimal<S extends ScaleMetrics, D extends AbstractImmutableDecimal<S, D>>
044                extends AbstractDecimal<S, D>implements ImmutableDecimal<S> {
045
046        private final long unscaled;
047
048    /*Used to store the string representation, if computed */
049    private transient String stringCache;
050
051        /**
052         * Constructor with unscaled value.
053         * 
054         * @param unscaled
055         *            the unscaled value
056         */
057        public AbstractImmutableDecimal(long unscaled) {
058                this.unscaled = unscaled;
059        }
060
061        @Override
062        public final long unscaledValue() {
063                return unscaled;
064        }
065
066        @Override
067        public ImmutableDecimal<?> scale(int scale) {
068                return scale(scale, RoundingMode.HALF_UP);
069        }
070
071        @Override
072        @SuppressWarnings("hiding")
073        public <S extends ScaleMetrics> ImmutableDecimal<S> scale(S scaleMetrics) {
074                return scale(scaleMetrics, RoundingMode.HALF_UP);
075        }
076
077        @Override
078        public ImmutableDecimal<?> scale(int scale, RoundingMode roundingMode) {
079                final int myScale = getScale();
080                if (scale == myScale) {
081                        return this;
082                }
083                final ScaleMetrics targetMetrics = Scales.getScaleMetrics(scale);
084                try {
085                        final long targetUnscaled = targetMetrics.getArithmetic(roundingMode).fromUnscaled(unscaled, myScale);
086                        return getFactory().deriveFactory(targetMetrics).valueOfUnscaled(targetUnscaled);
087                } catch (IllegalArgumentException e) {
088                        throw Exceptions.newArithmeticExceptionWithCause("Overflow: cannot convert " + this + " to scale " + scale,
089                                        e);
090                }
091        }
092
093        @Override
094        @SuppressWarnings("hiding")
095        public <S extends ScaleMetrics> ImmutableDecimal<S> scale(S scaleMetrics, RoundingMode roundingMode) {
096                if (scaleMetrics == getScaleMetrics()) {
097                        @SuppressWarnings("unchecked")
098                        // safe: we know it is the same scale metrics
099                        final ImmutableDecimal<S> self = (ImmutableDecimal<S>) this;
100                        return self;
101                }
102                try {
103                        final long targetUnscaled = scaleMetrics.getArithmetic(roundingMode).fromUnscaled(unscaled, getScale());
104                        return getFactory().deriveFactory(scaleMetrics).valueOfUnscaled(targetUnscaled);
105                } catch (IllegalArgumentException e) {
106                        throw Exceptions.newArithmeticExceptionWithCause(
107                                        "Overflow: cannot convert " + this + " to scale " + scaleMetrics.getScale(), e);
108                }
109        }
110
111        @Override
112        public ImmutableDecimal<?> multiplyExact(Decimal<?> multiplicand) {
113                final int targetScale = getScale() + multiplicand.getScale();
114                if (targetScale > Scales.MAX_SCALE) {
115                        throw new IllegalArgumentException("sum of scales in exact multiplication exceeds max scale "
116                                        + Scales.MAX_SCALE + ": " + this + " * " + multiplicand);
117                }
118                try {
119                        final long unscaledProduct = getDefaultCheckedArithmetic().multiplyByLong(unscaled,
120                                        multiplicand.unscaledValue());
121                        return getFactory().deriveFactory(targetScale).valueOfUnscaled(unscaledProduct);
122                } catch (ArithmeticException e) {
123                        throw new ArithmeticException("Overflow: " + this + " * " + multiplicand);
124                }
125        }
126
127        @Override
128        public ImmutableDecimal<S> min(ImmutableDecimal<S> val) {
129                return isLessThanOrEqualTo(val) ? this : val;
130        }
131
132        @Override
133        public ImmutableDecimal<S> max(ImmutableDecimal<S> val) {
134                return isGreaterThanOrEqualTo(val) ? this : val;
135        }
136        
137        @Override
138        public final String toString() {
139                String s = stringCache;
140                if (s == null) {
141                        stringCache = s = getDefaultArithmetic().toString(unscaledValue());
142                }
143                return s;
144        }
145}