OceanusDecimal.java

/*******************************************************************************
 * Oceanus: Java Utilities
 * Copyright 2012,2025 Tony Washer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package net.sourceforge.joceanus.oceanus.decimal;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Arrays;

import net.sourceforge.joceanus.oceanus.convert.OceanusDataConverter;

/**
 * Provides classes to represent decimal numbers with fixed numbers of decimal digits
 * {@link #theScale} as Long integers. The decimal value is multiplied by 10 to the power of the
 * number of decimals for the number ({@link #theFactor}). The integral part of the number can be
 * expressed as (Value / Factor) and the fractional part as (Value % Factor). Arithmetic is then
 * performed as whole number arithmetic on these values, with due care taken on multiplication and
 * division to express the result to the correct number of decimals without losing any part of the
 * answer to overflow.
 */
public class OceanusDecimal
        implements Comparable<OceanusDecimal> {
    /**
     * Decimal Byte length.
     */
    public static final int BYTE_LEN = Long.BYTES + 1;

    /**
     * The Decimal radix.
     */
    public static final int RADIX_TEN = 10;

    /**
     * The Maximum # of Decimals.
     */
    public static final int MAX_DECIMALS = 10;

    /**
     * Powers of Ten.
     */
    private static final long[] POWERS_OF_TEN = getPowersOfTen(MAX_DECIMALS);

    /**
     * The Shift factor to move top part of long to an integer.
     */
    private static final int INT_SHIFT = 32;

    /**
     * Out of range error text.
     */
    private static final String ERROR_RANGE = "Value out of range";

    /**
     * The unscaled value.
     */
    private long theValue;

    /**
     * The scale.
     */
    private int theScale;

    /**
     * The Decimal factor, used for isolating integral and fractional parts.
     */
    private long theFactor;

    /**
     * Standard constructor.
     */
    protected OceanusDecimal() {
        theValue = 0;
        theScale = 0;
        theFactor = 1;
    }

    /**
     * Constructor.
     * @param pSource the decimal as a string
     * @throws IllegalArgumentException on invalidly formatted argument
     */
    public OceanusDecimal(final String pSource) {
        /* Parse the string */
        OceanusDecimalParser.parseDecimalValue(pSource, this);

        /* Remove redundant decimals */
        reduceScale(0);
    }

    /**
     * Constructor.
     * @param pSource the decimal as a double
     */
    public OceanusDecimal(final double pSource) {
        /* Convert to string and parse */
        this(Double.toString(pSource));
    }

    /**
     * Constructor.
     * @param pSource the source decimal
     */
    public OceanusDecimal(final OceanusDecimal pSource) {
        /* Copy value and scale */
        setValue(pSource.unscaledValue(), pSource.scale());
    }

    /**
     * Constructor.
     * @param pUnscaledValue the unscaled value
     * @param pScale the scale
     */
    public OceanusDecimal(final long pUnscaledValue,
                          final int pScale) {
        /* Store value and scale */
        setValue(pUnscaledValue, pScale);
    }

    /**
     * Create the decimal from a byte array.
     * @param pBuffer the buffer
     */
    public OceanusDecimal(final byte[] pBuffer) {
        if (pBuffer == null || pBuffer.length < Long.BYTES + 1) {
            throw new IllegalArgumentException();
        }
        final byte[] myValue = Arrays.copyOf(pBuffer, Long.BYTES);
        final long myUnscaled = OceanusDataConverter.byteArrayToLong(myValue);
        final int myScale = pBuffer[Long.BYTES];
        setValue(myUnscaled, myScale);
    }

    /**
     * Obtain the unscaled value of the decimal.
     * @return the unscaled value
     */
    public long unscaledValue() {
        return theValue;
    }

    /**
     * Obtain the scale of the decimal.
     * @return the scale
     */
    public int scale() {
        return theScale;
    }

    /**
     * Set the value and scale.
     * @param pUnscaledValue the unscaled value
     * @param pScale the scale
     */
    protected final void setValue(final long pUnscaledValue,
                                  final int pScale) {
        /* Validate the scale */
        recordScale(pScale);

        /* Store value and scale */
        theValue = pUnscaledValue;
    }

    /**
     * Record the scale. The unscaled value is unchanged.
     * @param pScale the scale
     */
    protected final void recordScale(final int pScale) {
        /* Validate the scale */
        validateScale(pScale);

        /* Store scale */
        theScale = pScale;

        /* Calculate decimal factor */
        theFactor = getFactor(theScale);
    }

    /**
     * Adjust to scale.
     * @param pScale required scale
     */
    protected void adjustToScale(final int pScale) {
        /* If the scale is not correct */
        if (theScale != pScale) {
            /* Adjust the value appropriately */
            movePointLeft(pScale
                          - theScale);
        }
    }

    /**
     * Obtain factor.
     * @param pDecimals the number of decimals
     * @return the decimal part of the number
     */
    protected static long getFactor(final int pDecimals) {
        return POWERS_OF_TEN[pDecimals];
    }

    /**
     * Validate the scale.
     * @param pScale the scale
     */
    private static void validateScale(final int pScale) {
        /* Throw exception on invalid decimals */
        if ((pScale < 0)
            || (pScale > MAX_DECIMALS)) {
            throw new IllegalArgumentException("Decimals must be in the range 0 to "
                                               + MAX_DECIMALS);
        }
    }

    /**
     * Obtain integral part of number.
     * @return the integer part of the number
     */
    private long getIntegral() {
        return theValue
               / theFactor;
    }

    /**
     * Obtain fractional part of number.
     * @return the decimal part of the number
     */
    private long getFractional() {
        return theValue
               % theFactor;
    }

    /**
     * Determine whether we have a non-zero value.
     * @return <code>true</code> if the value is non-zero, <code>false</code> otherwise.
     */
    public boolean isNonZero() {
        return theValue != 0;
    }

    /**
     * Determine whether we have a zero value.
     * @return <code>true</code> if the value is zero, <code>false</code> otherwise.
     */
    public boolean isZero() {
        return theValue == 0;
    }

    /**
     * Determine whether we have a positive (or zero) value.
     * @return <code>true</code> if the value is non-negative, <code>false</code> otherwise.
     */
    public boolean isPositive() {
        return theValue >= 0;
    }

    /**
     * Negate the value.
     */
    public void negate() {
        theValue = -theValue;
    }

    /**
     * Set to zero value.
     */
    public void setZero() {
        theValue = 0;
    }

    /**
     * Returns the sign function.
     * @return -1, 0, or 1 as the value of this Decimal is negative, zero, or positive.
     */
    public int signum() {
        if (theValue == 0) {
            return 0;
        }
        return (theValue < 0)
                              ? -1
                              : 1;
    }

    /**
     * Reduce scale. Remove redundant zero digits in scale.
     * @param pDesiredScale the desired scale.
     */
    protected final void reduceScale(final int pDesiredScale) {
        /* While we have a large scale */
        while (theScale > pDesiredScale) {
            /* If we have relevant digits, break loop */
            if ((theValue % RADIX_TEN) != 0) {
                break;
            }

            /* Adjust the value appropriately */
            movePointRight(1);
        }
    }

    /**
     * Adjust a value to a different number of decimals.
     * <p>
     * If the adjustment is to reduce the number of decimals, the most significant digit of the
     * discarded digits is examined to determine whether to round up. If the number of decimals is
     * to be increased, zeros are simply added to the end.
     * @param pValue the value to adjust
     * @param iAdjust the adjustment (positive if # of decimals are to increase, negative if they
     * are to decrease)
     * @return the adjusted value
     */
    protected static long adjustDecimals(final long pValue,
                                         final int iAdjust) {
        /* Take a copy of the value */
        long myValue = pValue;

        /* If we need to reduce decimals */
        if (iAdjust < 0) {
            /* If we have more than one decimal to remove */
            if (iAdjust + 1 < 0) {
                /* Calculate division factor (minus one) */
                final long myFactor = getFactor(-(iAdjust + 1));

                /* Reduce to 10 times required value */
                myValue /= myFactor;
            }

            /* Access last digit */
            long myDigit = myValue
                             % RADIX_TEN;

            /* Handle negatiove values */
            int myAdjust = 1;
            if (myDigit < 0) {
                myAdjust = -1;
                myDigit = -myDigit;
            }

            /* Reduce final decimal and round up if required */
            myValue /= RADIX_TEN;
            if (myDigit >= (RADIX_TEN >> 1)) {
                myValue += myAdjust;
            }

            /* else if we need to expand fractional product */
        } else if (iAdjust > 0) {
            myValue *= getFactor(iAdjust);
        }

        /* Return the adjusted value */
        return myValue;
    }

    /**
     * Multiply two decimals together to produce a third.
     * <p>
     * This function splits each part of the multiplication into integral and fractional parts (a,b)
     * and (c,d). It then treats each factor as the sum of the two parts (a+b) etc. and calculates
     * the product as (a.c + a.d + b.c + b.d). To avoid losing significant digits at either end of
     * the calculation each partial product is split into integral and fractional parts. The
     * integers are summed together and the fractional parts are summed together at combined decimal
     * places of the two factors. Once all partial products have been calculated, the integral and
     * fractional totals are adjusted to the correct number of decimal places and combined. This
     * allows the multiplication to be built without risk of unnecessary arithmetic overflow.
     * @param pFirst the first factor
     * @param pSecond the second factor
     */
    protected void calculateProduct(final OceanusDecimal pFirst,
                                    final OceanusDecimal pSecond) {
        /* Access information about first factor */
        final long myIntFirst = pFirst.getIntegral();
        final long myFracFirst = pFirst.getFractional();
        final int myScaleFirst = pFirst.scale();

        /* Access information about second factor */
        final long myIntSecond = pSecond.getIntegral();
        final long myFracSecond = pSecond.getFractional();
        final int myScaleSecond = pSecond.scale();

        /*
         * Calculate (a.c) the integral part of the answer and initialise the fractional part (at
         * maxScale)
         */
        int maxScale = myScaleFirst
                       + myScaleSecond;
        long myIntegral = myIntFirst
                          * myIntSecond;
        long myFractional = 0;

        /* Calculate (a.d) (@myScaleSecond scale) and split off fractions */
        long myIntermediate = myIntFirst
                              * myFracSecond;
        long myFractions = myIntermediate
                           % getFactor(myScaleSecond);
        myIntermediate -= myFractions;
        myIntegral += adjustDecimals(myIntermediate, -myScaleSecond);
        myFractional += adjustDecimals(myFractions, maxScale
                                                    - myScaleSecond);

        /* Calculate (b.c) (@myScaleFirst scale) and split off fractions */
        myIntermediate = myIntSecond
                         * myFracFirst;
        myFractions = myIntermediate
                      % getFactor(myScaleFirst);
        myIntermediate -= myFractions;
        myIntegral += adjustDecimals(myIntermediate, -myScaleFirst);
        myFractional += adjustDecimals(myFractions, maxScale
                                                    - myScaleFirst);

        /* Calculate (b.d) (@maxScale scale) */
        myIntermediate = myFracFirst
                         * myFracSecond;
        myFractional += myIntermediate;

        /* If the maxScale is too large, reduce it */
        if (maxScale > MAX_DECIMALS) {
            /* Adjust the decimals */
            myFractional = adjustDecimals(myFractional, MAX_DECIMALS
                                                        - maxScale);

            /* Reduce maxScale */
            maxScale = MAX_DECIMALS;
        }

        /* Adjust and combine the two calculations */
        myIntegral = adjustDecimals(myIntegral, theScale);
        myFractional = adjustDecimals(myFractional, theScale
                                                    - maxScale);
        theValue = myIntegral
                   + myFractional;
    }

    /**
     * Divide a decimal by another decimal to produce a third.
     * <p>
     * The calculation can be written as
     * <code>x.10<sup>a</sup>/y.10<sup>b</sup> = (x/y).10<sup>a-b</sup> = z.10<sup>c</sup></code>.
     * <p>
     * where x is the unscaled dividend, y the unscaled divisor and z the unscaled result, and a,b,c
     * the relevant scales.
     * <p>
     * In order to avoid losing significant digits at either end of the calculation we calculate
     * (x/y) in integer arithmetic.
     * <p>
     * <code>x/y = m, x%y = n =&gt; x=my + n</code> where m and n are integers, and
     * <p>
     * <code>(x/y).10<sup>a-b</sup> = (my +n).10<sup>a-b</sup>/y = (m + (n/y)).10<sup>a-b</sup></code>
     * <p>
     * To obtain the result in the correct scale we find
     * <p>
     * <code>z.10<sup>c</sup> = m.10<sup>c-(a-b)</sup> + IntegralPart(n.10<sup>c-(a-b)</sup>/y)</code>
     * <p>
     * taking care to round the IntegralPart calculation correctly.
     * <p>
     * In the case where it is not possible to avoid overflow, the slower safeQuotient method is used.
     * @param pDividend the number to divide
     * @param pDivisor the number to divide
     */
    protected void calculateQuotient(final OceanusDecimal pDividend,
                                     final OceanusDecimal pDivisor) {
        /* Access the two values */
        final long myDividend = pDividend.unscaledValue();
        final long myDivisor = pDivisor.unscaledValue();

        /* Check for possible overflow */
        final int numDivisorBits = 1 + Long.SIZE - Long.numberOfLeadingZeros(pDivisor.isPositive() ? myDivisor : -myDivisor);
        final int numScaleBits = 1 + Long.SIZE - Long.numberOfLeadingZeros(POWERS_OF_TEN[theScale + 1]);
        if (numDivisorBits + numScaleBits >= Long.SIZE) {
            calculateSafeQuotient(pDividend, pDivisor);
            return;
        }

        /* Calculate fractions (m,n) */
        long myInteger = myDividend
                         / myDivisor;
        long myRemainder = myDividend
                           % myDivisor;

        /* Calculate the required shift (c-(a-b)) */
        int myShift = scale();
        myShift += pDivisor.scale()
                   - pDividend.scale();

        /* If the shift is positive */
        if (myShift > 0) {
            /* Adjust integer and remainder taking care of rounding for remainder */
            myInteger = adjustDecimals(myInteger, myShift);
            myRemainder = adjustDecimals(myRemainder, myShift + 1);
            myRemainder /= myDivisor;
            myRemainder = adjustDecimals(myRemainder, -1);

            /* Combine values */
            theValue = myInteger
                       + myRemainder;
        } else if (myShift == 0) {
            /* Only need to adjust remainder for rounding */
            myRemainder = adjustDecimals(myRemainder, 1);
            myRemainder /= myDivisor;
            myRemainder = adjustDecimals(myRemainder, -1);

            /* Combine values */
            theValue = myInteger
                       + myRemainder;
        } else {
            /* Integer value also rounds so add in prior to rounding */
            myInteger = adjustDecimals(myInteger, myShift + 1);
            myRemainder = adjustDecimals(myRemainder, myShift + 1);
            myRemainder /= myDivisor;
            myInteger += myRemainder;
            myInteger = adjustDecimals(myInteger, -1);

            /* Combine values */
            theValue = adjustDecimals(myInteger, -1);
        }
    }

    /**
     * Divide a decimal by another decimal to produce a third using slow BigDecimal arithmetic.
     * <p>
     * This is necessary when the quotient is large since there is a danger of overflow in the standard method
     * @param pDividend the number to divide
     * @param pDivisor the number to divide
     */
    protected void calculateSafeQuotient(final OceanusDecimal pDividend,
                                         final OceanusDecimal pDivisor) {
        final BigDecimal myDividend = pDividend.toBigDecimal();
        final BigDecimal myDivisor = pDivisor.toBigDecimal();
        BigDecimal myResult = myDividend.divide(myDivisor, theScale, RoundingMode.HALF_UP);
        myResult = myResult.movePointRight(theScale);
        theValue = myResult.longValue();
    }

    /**
     * Add a Decimal to the value. The value of this Decimal is updated and the scale is
     * maintained.
     * @param pValue The Decimal to add to this one.
     */
    public void addValue(final OceanusDecimal pValue) {
        /* Access the parameter at the correct scale */
        long myDelta = pValue.unscaledValue();
        final int myScale = pValue.scale();
        if (theScale != myScale) {
            myDelta = adjustDecimals(myDelta, theScale
                                              - myScale);
        }

        /* Adjust the value accordingly */
        theValue += myDelta;
    }

    /**
     * Subtract a Decimal from the value. The value of this Decimal is updated and the scale is
     * maintained.
     * @param pValue The decimal to subtract from this one.
     */
    public void subtractValue(final OceanusDecimal pValue) {
        /* Access the parameter at the correct scale */
        long myDelta = pValue.unscaledValue();
        final int myScale = pValue.scale();
        if (theScale != myScale) {
            myDelta = adjustDecimals(myDelta, theScale
                                              - myScale);
        }

        /* Adjust the value accordingly */
        theValue -= myDelta;
    }

    /**
     * Move decimal point to the left.
     * @param pPlaces number of places to move the decimal point
     */
    public final void movePointLeft(final int pPlaces) {
        /* Calculate the new scale */
        final int myNewScale = theScale
                               + pPlaces;

        /* record the scale */
        recordScale(myNewScale);

        /* Adjust the value and record the new scale */
        theValue = adjustDecimals(theValue, pPlaces);
    }

    /**
     * Move decimal point to the right.
     * @param pPlaces number of places to move the decimal point
     */
    public final void movePointRight(final int pPlaces) {
        /* Call movePointLeft */
        movePointLeft(-pPlaces);
    }

    @Override
    public String toString() {
        /* Format the value */
        return OceanusDecimalFormatter.toString(this);
    }

    /**
     * Returns the maximum of this Decimal and pValue.
     * @param pValue the value to compare.
     * @return the Decimal whose value is the greater of this Decimal and pValue. If they are
     * equal, as defined by the compareTo method, this is returned
     */
    public OceanusDecimal max(final OceanusDecimal pValue) {
        /* return the BigDecimal value */
        return (compareTo(pValue) < 0)
                                       ? pValue
                                       : this;
    }

    /**
     * Returns the minimum of this Decimal and pValue.
     * @param pValue the value to compare.
     * @return the Decimal whose value is the lesser of this Decimal and pValue. If they are
     * equal, as defined by the compareTo method, this is returned
     */
    public OceanusDecimal min(final OceanusDecimal pValue) {
        /* return the BigDecimal value */
        return (compareTo(pValue) > 0)
                                       ? pValue
                                       : this;
    }

    /**
     * Returns a new Decimal which is the sum of this Decimal and pValue, and whose scale is the
     * maximum of the two.
     * @param pValue the value to add.
     * @return the resulting Decimal
     * @see BigDecimal#add
     */
    public OceanusDecimal add(final OceanusDecimal pValue) {
        /* Create the new decimal */
        final OceanusDecimal myResult;

        /* If the operand has the higher scale */
        if (theScale < pValue.scale()) {
            /* Initialise from operand and add this value */
            myResult = new OceanusDecimal(pValue);
            myResult.addValue(this);
        } else {
            /* Initialise from operand and add this value */
            myResult = new OceanusDecimal(this);
            myResult.addValue(pValue);
        }

        /* return the result */
        return myResult;
    }

    /**
     * Returns a new Decimal which is the difference of this Decimal and pValue, and whose scale
     * is the maximum of the two.
     * @param pValue the value to subtract.
     * @return the resulting Decimal
     * @see BigDecimal#subtract
     */
    public OceanusDecimal subtract(final OceanusDecimal pValue) {
        /* Create the new decimal */
        final OceanusDecimal myResult;

        /* If the operand has the higher scale */
        if (theScale < pValue.scale()) {
            /* Initialise from operand and subtract this value */
            myResult = new OceanusDecimal(pValue);
            myResult.subtractValue(this);
        } else {
            /* Initialise from operand and subtract this value */
            myResult = new OceanusDecimal(this);
            myResult.subtractValue(pValue);
        }

        /* return the result */
        return myResult;
    }

    /**
     * Returns a new Decimal which is the product of this Decimal and pValue, and whose scale is
     * the sum of the two.
     * @param pValue the value to multiply by.
     * @return the resulting Decimal
     * @see BigDecimal#multiply
     */
    public OceanusDecimal multiply(final OceanusDecimal pValue) {
        /* Create the new decimal at the correct scale */
        final OceanusDecimal myResult = new OceanusDecimal();
        myResult.setValue(0, theScale
                             + pValue.scale());

        /* Calculate the product */
        myResult.calculateProduct(this, pValue);

        /* return the result */
        return myResult;
    }

    /**
     * Multiplies the value by the amount given. The scale remains the same.
     * @param pValue the value to multiply by.
     */
    public void multiply(final long pValue) {
        /* Multiply the value */
        theValue *= pValue;
    }

    /**
     * Returns a new Decimal whose value is (this / pValue), and whose scale is the same as this
     * Decimal.
     * @param pValue the value to divide by.
     * @return the resulting Decimal
     * @see BigDecimal#divide
     */
    public OceanusDecimal divide(final OceanusDecimal pValue) {
        /* Create the new decimal at the correct scale */
        final OceanusDecimal myResult = new OceanusDecimal();
        myResult.setValue(0, theScale);

        /* Calculate the quotient */
        myResult.calculateQuotient(this, pValue);

        /* return the result */
        return myResult;
    }

    /**
     * Divides the value by the amount given. The scale remains the same.
     * @param pValue the value to divide by.
     */
    public void divide(final long pValue) {
        /* Multiply the value */
        theValue /= pValue;
    }

    /**
     * Returns a new Decimal whose value is the integral part of (this / pValue).
     * @param pValue the value to divide by.
     * @return the resulting Decimal
     * @see BigDecimal#divide
     */
    public OceanusDecimal divideToIntegralValue(final OceanusDecimal pValue) {
        /* Create the new decimal at the correct scale */
        final OceanusDecimal myResult = new OceanusDecimal();
        myResult.setValue(0, theScale);

        /* Calculate the quotient */
        myResult.calculateQuotient(this, pValue);

        /* Extract the integral part of the result */
        myResult.setValue(getIntegral(), 0);

        /* return the result */
        return myResult;
    }

    /**
     * Returns a new Decimal whose value is (this / pValue), and whose scale is the same as this
     * Decimal.
     * @param pValue the value to divide by.
     * @return the resulting Decimal
     * @see BigDecimal#remainder
     */
    public OceanusDecimal remainder(final OceanusDecimal pValue) {
        /* Create the new decimal at the correct scale */
        final OceanusDecimal myQuotient = new OceanusDecimal();
        myQuotient.setValue(0, theScale);

        /* Calculate the quotient */
        myQuotient.calculateQuotient(this, pValue);

        /* Extract the integral part of the result */
        myQuotient.setValue(getIntegral(), 0);

        /* Re-multiply by the divisor and adjust to correct scale */
        final OceanusDecimal myWhole = myQuotient.multiply(pValue);
        myWhole.setValue(adjustDecimals(myWhole.unscaledValue(), theScale
                                                                 - pValue.scale()), theScale);

        /* Calculate the result */
        final OceanusDecimal myResult = new OceanusDecimal(this);
        myResult.subtractValue(myWhole);

        /* return the result */
        return myResult;
    }

    /**
     * Convert the value into a BigDecimal.
     * @return the value as a BigDecimal
     */
    public BigDecimal toBigDecimal() {
        /* return the BigDecimal value */
        return new BigDecimal(toString());
    }

    /**
     * Convert the value into a Double.
     * @return the value as a double
     * @see BigDecimal#doubleValue
     */
    public double doubleValue() {
        /* Format the string */
        final String myString = toString();

        /* return the double value */
        return Double.parseDouble(myString);
    }

    /**
     * Convert the value into a Float.
     * @return the value as a float
     * @see BigDecimal#floatValue
     */
    public float floatValue() {
        /* Format the string */
        final String myString = toString();

        /* return the float value */
        return Float.parseFloat(myString);
    }

    /**
     * Convert the value into a BigInteger.
     * @return the value as a BigInteger
     * @see BigDecimal#toBigInteger
     */
    public BigInteger toBigInteger() {
        /* return the BigInteger value */
        return new BigInteger(Long.toString(getIntegral()));
    }

    /**
     * Convert the value into a long.
     * @return the value as a long
     * @see BigDecimal#longValue
     */
    public long longValue() {
        /* return the long value */
        return getIntegral();
    }

    /**
     * Convert the value into an integer.
     * @return the value as an integer
     * @see BigDecimal#intValue
     */
    public int intValue() {
        /* return the integer value */
        return (int) getIntegral();
    }

    /**
     * Convert the value into a short.
     * @return the value as a short
     * @see BigDecimal#shortValue
     */
    public short shortValue() {
        /* return the short value */
        return (short) getIntegral();
    }

    /**
     * Convert the value into a byte.
     * @return the value as a byte
     * @see BigDecimal#byteValue
     */
    public byte byteValue() {
        /* return the byte value */
        return (byte) getIntegral();
    }

    /**
     * Check for fractional part on conversion.
     */
    public void checkFractionalZero() {
        /* If we have a fractional part */
        if (getFractional() != 0) {
            throw new ArithmeticException("Decimal has fractional part");
        }
    }

    /**
     * Convert the value into a BigInteger, checking for loss of information.
     * @return the value as a BigInteger
     * @see BigDecimal#toBigIntegerExact
     */
    public BigInteger toBigIntegerExact() {
        /* Check fractional is zero */
        checkFractionalZero();

        /* return the BigInteger value */
        return toBigInteger();
    }

    /**
     * Convert the value into a long, checking for loss of information.
     * @return the value as a long
     * @see BigDecimal#longValueExact
     */
    public long longValueExact() {
        /* Check fractional is zero */
        checkFractionalZero();

        /* return the long value */
        return longValue();
    }

    /**
     * Convert the value into an integer, checking for loss of information.
     * @return the value as an integer
     * @see BigDecimal#intValueExact
     */
    public int intValueExact() {
        /* Check fractional is zero */
        checkFractionalZero();

        /* If we have a fractional part */
        final long myValue = getIntegral();
        if ((myValue > Integer.MAX_VALUE)
            || (myValue < Integer.MIN_VALUE)) {
            throw new ArithmeticException(ERROR_RANGE);
        }

        /* return the integer value */
        return (int) myValue;
    }

    /**
     * Convert the value into a short, checking for loss of information.
     * @return the value as a short
     * @see BigDecimal#shortValueExact
     */
    public short shortValueExact() {
        /* Check fractional is zero */
        checkFractionalZero();

        /* If we have a fractional part */
        final long myValue = getIntegral();
        if ((myValue > Short.MAX_VALUE)
            || (myValue < Short.MIN_VALUE)) {
            throw new ArithmeticException(ERROR_RANGE);
        }

        /* return the short value */
        return (short) myValue;
    }

    /**
     * Convert the value into a byte, checking for loss of information.
     * @return the value as a byte
     * @see BigDecimal#byteValueExact
     */
    public byte byteValueExact() {
        /* Check fractional is zero */
        checkFractionalZero();

        /* If we have a fractional part */
        final long myValue = getIntegral();
        if ((myValue > Byte.MAX_VALUE)
            || (myValue < Byte.MIN_VALUE)) {
            throw new ArithmeticException(ERROR_RANGE);
        }

        /* return the byte value */
        return (byte) myValue;
    }

    @Override
    public boolean equals(final Object pThat) {
        /* Handle trivial cases */
        if (this == pThat) {
            return true;
        }
        if (pThat == null) {
            return false;
        }

        /* Make sure that the object is the same class */
        if (getClass() != pThat.getClass()) {
            return false;
        }

        /* Cast as decimal */
        final OceanusDecimal myThat = (OceanusDecimal) pThat;

        /* Check value and scale */
        return (theValue == myThat.theValue)
               && (theScale == myThat.theScale);
    }

    @Override
    public int hashCode() {
        return (int) (theValue ^ (theValue >>> INT_SHIFT))
               + theScale;
    }

    @Override
    public int compareTo(final OceanusDecimal pThat) {
        /* Handle trivial case */
        if (this.equals(pThat)) {
            return 0;
        }

        /* If there is no difference in scale */
        final int myScaleDiff = scale()
                                - pThat.scale();
        if (myScaleDiff == 0) {
            /* Just compare unscaled value */
            if (theValue == pThat.theValue) {
                return 0;
            }
            return (theValue < pThat.theValue)
                                               ? -1
                                               : 1;
        }

        /* Compare integral values */
        long myDiff = getIntegral()
                      - pThat.getIntegral();
        if (myDiff != 0) {
            return (myDiff < 0)
                                ? -1
                                : 1;
        }

        /* Access fractional parts */
        long myFirst = getFractional();
        long mySecond = pThat.getFractional();

        /* Adjust to same maximum scale */
        if (myScaleDiff < 0) {
            myFirst = adjustDecimals(myFirst, -myScaleDiff);
        } else {
            mySecond = adjustDecimals(mySecond, myScaleDiff);
        }

        /* Compare fractional values */
        myDiff = myFirst
                 - mySecond;
        if (myDiff != 0) {
            return (myDiff < 0)
                                ? -1
                                : 1;
        }

        /* Equal to all intents and purposes */
        return 0;
    }

    /**
     * Build powers of ten.
     * @param pMax maximum power of ten
     * @return array of powers of ten
     */
    private static long[] getPowersOfTen(final int pMax) {
        /* Allocate the array */
        final long[] myArray = new long[pMax + 2];

        /* Initialise array */
        long myValue = 1;
        myArray[0] = myValue;

        /* Loop through array */
        for (int i = 1; i <= pMax + 1; i++) {
            /* Adjust value and record it */
            myValue *= RADIX_TEN;
            myArray[i] = myValue;
        }

        /* Return the array */
        return myArray;
    }

    /**
     * Convert the Decimal to a byte array.
     * @return the byte array
     */
    public byte[] toBytes() {
        final byte[] myValue = OceanusDataConverter.longToByteArray(unscaledValue());
        final byte[] myResult = Arrays.copyOf(myValue, myValue.length + 1);
        myResult[myValue.length] = (byte) scale();
        return myResult;
    }
}