All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.firebirdsql.extern.decimal.Decimal Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2018 Firebird development team and individual contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.firebirdsql.extern.decimal;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.Locale;
import java.util.Objects;

import static java.util.Objects.requireNonNull;

/**
 * Abstract base class for IEEE-754 decimals.
 *
 * @author Mark Rotteveel
 */
public abstract class Decimal> {

    private final int signum;
    private final DecimalType type;
    private final BigDecimal bigDecimal;

    Decimal(int signum, DecimalType type) {
        assert type != null : "Type should not be null";
        assert type != DecimalType.FINITE : "Constructor only suitable for non-FINITE";
        assert -1 == signum || signum == 1 : "Invalid signum, " + signum;
        this.signum = signum;
        this.type = type;
        bigDecimal = null;
    }

    Decimal(int signum, BigDecimal bigDecimal) {
        assert -1 <= signum && signum <= 1 : "Invalid signum, " + signum;
        this.type = DecimalType.FINITE;
        this.signum = signum != 0 ? signum : Signum.POSITIVE;
        this.bigDecimal = requireNonNull(bigDecimal, "bigDecimal");
        if (bigDecimal.compareTo(BigDecimal.ZERO) != 0 && this.signum != bigDecimal.signum()) {
            throw new IllegalArgumentException("Signum value not consistent with big decimal value, was: "
                    + signum + ", expected: " + bigDecimal.signum());
        }
    }

    /**
     * Converts this decimal to a {@code BigDecimal}.
     *
     * @return Value as BigDecimal
     * @throws DecimalInconvertibleException
     *         If this value is a NaN, sNaN or Infinity, which can't be represented as a {@code BigDecimal}.
     */
    public final BigDecimal toBigDecimal() {
        if (type != DecimalType.FINITE) {
            throw new DecimalInconvertibleException(
                    "Value " + this + " cannot be converted to a BigDecimal", type, signum);
        }
        return bigDecimal;
    }

    /**
     * Converts this decimal to a double value.
     * 

* For normal, finite, decimal values, see {@link BigDecimal#doubleValue()}. *

*

* For type INFINITY, returns {@code Double.POSITIVE_INFINITY} or {@code Double.NEGATIVE_INFINITY}. For all * NaN-specials, returns {@code Double.NaN} (irrespective of signum). *

* * @return this decimal converted to a {@code double} */ public final double doubleValue() { switch (type) { case FINITE: return bigDecimal.doubleValue(); case INFINITY: return signum == Signum.NEGATIVE ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; case NAN: case SIGNALING_NAN: // No differentiation between positive/negative and signaling/normal return Double.NaN; default: throw new IllegalStateException("Unsupported DecimalType " + type); } } /** * Converts this decimal to its IEEE-754 byte encoding in network byte-order (aka big-endian). *

* This method returns network byte-order (aka big-endian). When you need little-endian order, you will need to * reverse the bytes in the array. *

* * @return byte array */ @SuppressWarnings("unchecked") public final byte[] toBytes() { return getDecimalCodec().encodeDecimal((T) this); } /** * Converts this decimal to the requested decimal type, rounding when necessary. * * @param decimalType * Target decimal type * @param * Type parameter of decimal * @return This value after conversion, or this if {@code decimalType} is the same as this type * @throws IllegalArgumentException * If conversion to {@code decimalType} is not supported */ public final > D toDecimal(Class decimalType) { return toDecimal(decimalType, OverflowHandling.ROUND_TO_INFINITY); } /** * Converts this decimal to the requested decimal type, rounding when necessary. * * @param decimalType * Target decimal type * @param overflowHandling * Handling of overflows * @param * Type parameter of decimal * @return This value after conversion, or this if {@code decimalType} is the same as this type * @throws IllegalArgumentException * If conversion to {@code decimalType} is not supported * @throws DecimalOverflowException * If {@code OverflowHandling#THROW_EXCEPTION} and the value is out of range for the target decimal type. */ public final > D toDecimal(Class decimalType, OverflowHandling overflowHandling) { if (decimalType == getClass()) { return decimalType.cast(this); } else if (decimalType == Decimal128.class) { return decimalType.cast(Decimal128.valueOf(this, overflowHandling)); } else if (decimalType == Decimal64.class) { return decimalType.cast(Decimal64.valueOf(this, overflowHandling)); } else if (decimalType == Decimal32.class) { return decimalType.cast(Decimal32.valueOf(this, overflowHandling)); } else { throw new IllegalArgumentException("Unsupported conversion to " + decimalType.getName()); } } final DecimalType getType() { return type; } final int signum() { return signum; } /** * @return {@code true} if this value is zero (ignoring scale), {@code false} if this is a special, or not zero. */ final boolean isEquivalentToZero() { return type == DecimalType.FINITE && BigDecimal.ZERO.compareTo(bigDecimal) == 0; } /** * @return The codec for this decimal type. */ abstract DecimalCodec getDecimalCodec(); /** * @return The decimal factory for this decimal type. */ abstract DecimalFactory getDecimalFactory(); /** * Negates this decimal (positive to negative, negative to positive). * * @return Negated value */ final T negate() { final DecimalFactory decimalFactory = getDecimalFactory(); if (type != DecimalType.FINITE) { return decimalFactory.getSpecialConstant(-1 * signum, type); } return decimalFactory.createDecimal(-1 * signum, bigDecimal.negate()); } @Override public final String toString() { switch (type) { case FINITE: if (signum == Signum.NEGATIVE && isEquivalentToZero()) { return "-" + bigDecimal.toString(); } return bigDecimal.toString(); case INFINITY: return signum == Signum.NEGATIVE ? "-Infinity" : "+Infinity"; case NAN: return signum == Signum.NEGATIVE ? "-NaN" : "+NaN"; case SIGNALING_NAN: return signum == Signum.NEGATIVE ? "-sNaN" : "+sNaN"; default: throw new IllegalStateException("Unsupported DecimalType " + type); } } @Override public final boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Decimal decimal = (Decimal) o; if (signum != decimal.signum) return false; if (type != decimal.type) return false; return Objects.equals(bigDecimal, decimal.bigDecimal); } @Override public final int hashCode() { int result = signum; result = 31 * result + type.hashCode(); result = 31 * result + (bigDecimal != null ? bigDecimal.hashCode() : 0); return result; } abstract static class AbstractDecimalFactory> implements DecimalFactory { private final Class type; private final DecimalFormat decimalFormat; private final T positiveInfinity; private final T negativeInfinity; private final T positiveNan; private final T negativeNan; private final T positiveSignalingNaN; private final T negativeSignalingNaN; AbstractDecimalFactory(Class type, DecimalFormat decimalFormat, T positiveInfinity, T negativeInfinity, T positiveNan, T negativeNan, T positiveSignalingNaN, T negativeSignalingNaN) { this.type = type; this.decimalFormat = decimalFormat; this.positiveInfinity = positiveInfinity; this.negativeInfinity = negativeInfinity; this.positiveNan = positiveNan; this.negativeNan = negativeNan; this.positiveSignalingNaN = positiveSignalingNaN; this.negativeSignalingNaN = negativeSignalingNaN; } @Override public final DecimalFormat getDecimalFormat() { return decimalFormat; } /** * @return Math context for the decimal type constructed. */ private MathContext getMathContext() { return decimalFormat.getMathContext(); } /** * @see DecimalFormat#validate(BigDecimal) */ final BigDecimal validateRange(BigDecimal value) { return decimalFormat.validate(value); } /** * Creates a decimal from {@code value}, applying rounding where necessary. *

* Values exceeding the range of this type will be handled according to the specified overflow handling. *

* * @param value * Big decimal value to convert * @param overflowHandling * Handling of overflows * @return Decimal equivalent * @throws DecimalOverflowException * If {@code OverflowHandling#THROW_EXCEPTION} and the value is out of range. */ final T valueOf(BigDecimal value, OverflowHandling overflowHandling) { final BigDecimal roundedValue = decimalFormat.tryRound(value); if (overflowHandling == OverflowHandling.ROUND_TO_INFINITY && decimalFormat.isOutOfRange(roundedValue)) { return getSpecialConstant(roundedValue.signum(), DecimalType.INFINITY); } // OverflowHandling.THROW_EXCEPTION is handled implicitly in createDecimal // Using value.signum() as rounding may round to zero, which would lose the signum information return createDecimal(value.signum(), roundedValue); } /** * Creates a decimal from {@code value}, applying rounding where necessary. *

* Values exceeding the range of this type will be handled according to the specified overflow handling. *

*

* Calling this method is equivalent to {@code valueOf(new BigDecimal(value), overflowHandling)}. *

* * @param value * Big integer value to convert * @param overflowHandling * Handling of overflows * @return Decimal equivalent * @throws DecimalOverflowException * If {@code OverflowHandling#THROW_EXCEPTION} and the value is out of range. * @see #valueOfExact(BigInteger) */ final T valueOf(BigInteger value, OverflowHandling overflowHandling) { return valueOf(new BigDecimal(value, getMathContext()), overflowHandling); } /** * Creates a decimal from {@code value}, rejecting values that would lose precision due to rounding. * * @param value Big integer value to convert * @throws DecimalOverflowException * If the value is out of range. * @return Decimal equivalent * @see #valueOf(BigInteger, OverflowHandling) */ final T valueOfExact(BigInteger value) { final BigDecimal bigDecimal = new BigDecimal(decimalFormat.validateCoefficient(value)); return createDecimal(value.signum(), bigDecimal); } /** * Creates a decimal from {@code value}, applying rounding where necessary. *

* {@code Double.NaN} is mapped to positive NaN, the infinities to their equivalent +/- infinity. *

*

* For normal, finite, values, this is equivalent to {@code valueOf(BigDecimal.valueOf(value), overflowHandling)}. *

* * @param value * Double value * @param overflowHandling * Handling of overflows * @return Decimal equivalent * @throws DecimalOverflowException * If {@code OverflowHandling#THROW_EXCEPTION} and the value is out of range. */ final T valueOf(double value, OverflowHandling overflowHandling) { if (Double.isNaN(value)) { return getSpecialConstant(Signum.POSITIVE, DecimalType.NAN); } else if (value == Double.POSITIVE_INFINITY) { return getSpecialConstant(Signum.POSITIVE, DecimalType.INFINITY); } else if (value == Double.NEGATIVE_INFINITY) { return getSpecialConstant(Signum.NEGATIVE, DecimalType.INFINITY); } return valueOf(new BigDecimal(Double.toString(value), getMathContext()), overflowHandling); } /** * Converts a decimal to this type. *

* For normal, finite, decimals, this behaves like {@code valueOf(decimal.toBigDecimal(), overflowHandling)}, see * {@link #valueOf(BigDecimal, OverflowHandling)}. *

* * @param decimal * Decimal to convert * @param overflowHandling * Handling of overflows * @return Decimal converted to this type, or {@code decimal} itself if it already is of this type * @throws DecimalOverflowException * If {@code OverflowHandling#THROW_EXCEPTION} and the value is out of range. */ final T valueOf(Decimal decimal, OverflowHandling overflowHandling) { if (decimal.getClass() == type) { return type.cast(decimal); } else if (decimal.type == DecimalType.FINITE) { return valueOf(decimal.bigDecimal, overflowHandling); } else { return getSpecialConstant(decimal.signum, decimal.type); } } /** * Creates a decimal from {@code value}, applying rounding where necessary. *

* Except for the special values [+/-]Inf, [+/-]Infinity, [+/-]NaN and [+/-]sNaN (case insensitive), the rules * of {@link BigDecimal#BigDecimal(String)} apply, with special handling in place to discern between positive * and negative zero. *

*

* Values exceeding the range of this type will be handled according to the specified overflow handling. *

* * @param value * String value to convert * @param overflowHandling * Handling of overflows * @return Decimal equivalent * @throws NumberFormatException * If the provided string is not valid numeric string. * @throws DecimalOverflowException * If {@code OverflowHandling#THROW_EXCEPTION} and the value is out of range. */ final T valueOf(String value, OverflowHandling overflowHandling) { if (value.length() > 2) { char checkChar = value.charAt(0); if (checkChar == '+' || checkChar == '-') { checkChar = value.charAt(1); } if (checkChar == 'i' || checkChar == 'I' || checkChar == 'n' || checkChar == 'N' || checkChar == 's' || checkChar == 'S') { return valueOfSpecial(value); } } BigDecimal bdValue = new BigDecimal(value, getMathContext()); T decimalValue = valueOf(bdValue, overflowHandling); if (decimalValue.isEquivalentToZero() && value.charAt(0) == '-' && bdValue.signum() != Signum.NEGATIVE) { return decimalValue.negate(); } return decimalValue; } private T valueOfSpecial(String special) { switch (special.toLowerCase(Locale.ROOT)) { case "inf": case "infinity": case "+inf": case "+infinity": return getSpecialConstant(Signum.POSITIVE, DecimalType.INFINITY); case "-inf": case "-infinity": return getSpecialConstant(Signum.NEGATIVE, DecimalType.INFINITY); case "nan": case "+nan": return getSpecialConstant(Signum.POSITIVE, DecimalType.NAN); case "-nan": return getSpecialConstant(Signum.NEGATIVE, DecimalType.NAN); case "snan": case "+snan": return getSpecialConstant(Signum.POSITIVE, DecimalType.SIGNALING_NAN); case "-snan": return getSpecialConstant(Signum.NEGATIVE, DecimalType.SIGNALING_NAN); default: throw new NumberFormatException("Invalid value " + special); } } @Override public final T getSpecialConstant(int signum, DecimalType decimalType) { switch (decimalType) { case INFINITY: return signum == Signum.NEGATIVE ? negativeInfinity : positiveInfinity; case NAN: return signum == Signum.NEGATIVE ? negativeNan : positiveNan; case SIGNALING_NAN: return signum == Signum.NEGATIVE ? negativeSignalingNaN : positiveSignalingNaN; default: throw new AssertionError("Invalid special value for decimalType " + decimalType); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy