content: /*
   * Copyright 2007-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
   *
   * Licensed under the Apache License, Version 2.0 (the "License").
   * You may not use this file except in compliance with the License.
   * A copy of the License is located at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * or in the "license" file accompanying this file. This file 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 com.amazon.ion;
  
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.math.MathContext;
  
  /**
   * An extension of {@link BigDecimal} that can represent negative zeros.
   * The primary change is the addition of {@link #isNegativeZero()}.
   * <p>
   * <b>WARNING:</b> This class should not be extended by code outside of
   * this library.
   * <p>
   * This class currently does not have any behavioral difference
   * from {@link BigDecimal} except for the following:
   * <ul>
   *   <li>{@link #toString()}, {@link #toEngineeringString()}, and
   *     {@link #toPlainString()} print a negative sign if necessary.
   *   </li>
   *   <li>{@link #floatValue()} and {@link #doubleValue()} return negative zero
   *     results.
   *   </li>
   *   <li>{@link #abs} does the right thing.
   * </ul>
   * This class does <b>NOT</b> override {@link #equals} or {@link #compareTo}
   * so those methods cannot distinguish positive and negative zeros.
   * <p>
   * It also does not currently override any of the numerical methods,
   * <em>but it may do so in the future.</em>  If you are concerned about
   * consistent treatment of negative zeros in future releases, you may wish to
   * use {@link #bigDecimalValue} before performing those operations.
   */
  public class Decimal
      extends BigDecimal
  {
      private static final long serialVersionUID = 1L;
  
      private static final class NegativeZero extends Decimal
      {
          private static final long serialVersionUID = 1L;
  
          private NegativeZero(int scale)
          {
              super(BigInteger.ZERO, scale);
          }
  
          private NegativeZero(int scale, MathContext mc)
          {
              super(BigInteger.ZERO, scale, mc);
          }
  
  
          @Override
          public float floatValue()
          {
              float v = super.floatValue();
              if (Float.compare(0f, v) <= 0) v = -1 * v;
              return v;
          }
  
          @Override
          public double doubleValue()
          {
              double v = super.doubleValue();
              if (Double.compare(0d, v) <= 0) v = -1 * v;
              return v;
          }
  
  
          @Override
          public BigDecimal abs()
          {
              return new BigDecimal(unscaledValue(), scale());
          }
  
          @Override
          public BigDecimal abs(MathContext mc)
          {
              return new BigDecimal(unscaledValue(), scale(), mc);
          }
  
          // TODO signum might break clients if -0 returns <0 instead of 0
          // TODO static negate(BigDecimal)
          // TODO hashcode
  
          // TODO some other things:
          //   * byte/int/longValueExact throws?
          //   * movePointLeft/Right
          //   * round
          //   * scaleByPowerOfTen
          //   * setScale
          //   * stripTrailingZeros
          //   * toBigIntegerExact
  
          @Override
          public String toString()
          {
              return '-' + super.toString();
          }
  
          @Override
          public String toEngineeringString()
          {
              return '-' + super.toEngineeringString();
          }
  
          @Override
          public String toPlainString()
          {
              return '-' + super.toPlainString();
          }
      }
  
      /**
       * The value 0, with a scale of 0.
       */
      public static final Decimal ZERO = new Decimal(0);
  
      /**
       * The value -0, with a scale of 0.
       */
      public static final Decimal NEGATIVE_ZERO = new NegativeZero(0);
  
  
      /**
       * Efficiently determines whether an arbitary decimal value is a negative
       * zero.  This can only be true when the value is actually a
       * {@link Decimal}.
       *
       * @return {@code true} if and only if the value is a negative zero.
       *
       * @throws NullPointerException if the value is {@code null}.
       */
      public static boolean isNegativeZero(BigDecimal val)
      {
          return (val.getClass() == NegativeZero.class);
      }
  
      /**
       * Returns a "plain" {@link BigDecimal} instance, never a {@link Decimal}
       * subclass.  As a side effect, this strips any negative-zero information.
       *
       * @param val may be null.
       * @return {@code null} if the given value is {@code null}.
       */
      public static BigDecimal bigDecimalValue(BigDecimal val)
      {
          if (val == null
              || val.getClass() == BigDecimal.class)
          {
              return val;
          }
          return new BigDecimal(val.unscaledValue(), val.scale());
      }
  
      /**
       * Compares two decimal values for equality, observing both precision and
       * negative zeros.  This differs from {@link BigDecimal#equals(Object)},
       * which isn't aware of negative zeros, and from
       * {@link BigDecimal#compareTo(BigDecimal)}, which ignores both precision
       * and negative zeros.
       *
       * @return {@code true} if and only if the two {@link BigDecimal} objects
       * have the same value, scale, and sign.
       *
       * @throws NullPointerException if either parameter is {@code null}.
       */
      public static boolean equals(BigDecimal v1, BigDecimal v2)
      {
          return (isNegativeZero(v1) == isNegativeZero(v2)
                  && v1.equals(v2));
      }
  
  
      //=========================================================================
  
      /**
       * Returns a negative-zero decimal value, with the given number of
       * significant digits (zeros).
       *
       * @param scale the number of significant digits (zeros) after the decimal
       * point.
       */
      public static Decimal negativeZero(int scale)
      {
          return new NegativeZero(scale);
      }
  
      /**
       * Returns a negative-zero decimal value, with the given number of
       * significant digits (zeros) and given context.
       *
       * @param scale the number of significant digits (zeros) after the decimal
       * point.
       */
      public static Decimal negativeZero(int scale, MathContext mc)
      {
          return new NegativeZero(scale, mc);
      }
  
  
      public static Decimal valueOf(BigInteger unscaledVal, int scale)
      {
          return new Decimal(unscaledVal, scale);
      }
  
      public static Decimal valueOf(BigInteger unscaledVal, int scale,
                                    MathContext mc)
      {
          return new Decimal(unscaledVal, scale, mc);
      }
  
  
      public static Decimal valueOf(BigInteger val)
      {
          return new Decimal(val);
      }
  
      public static Decimal valueOf(BigInteger val, MathContext mc)
      {
          return new Decimal(val, mc);
      }
  
  
      public static Decimal valueOf(int val)
      {
          return new Decimal(val);
      }
  
      public static Decimal valueOf(int val, MathContext mc)
      {
          return new Decimal(val, mc);
      }
  
  
      public static Decimal valueOf(long val)
      {
          return new Decimal(val);
      }
  
      public static Decimal valueOf(long val, MathContext mc)
      {
          return new Decimal(val, mc);
      }
  
      public static Decimal valueOf(double val)
      {
          if (Double.compare(val, -0d) == 0)
          {
              // Simulate BigDecimal.valueOf(0d) which has scale of 1
              return new NegativeZero(1);
          }
          return new Decimal(Double.toString(val));
      }
  
      public static Decimal valueOf(double val, MathContext mc)
      {
          if (Double.compare(val, -0d) == 0)
          {
              return new NegativeZero(1, mc);
          }
          return new Decimal(Double.toString(val), mc);
      }
  
      public static Decimal valueOf(BigDecimal val)
      {
          if (val == null || val instanceof Decimal) return (Decimal) val;
          return new Decimal(val.unscaledValue(), val.scale());
      }
  
      public static Decimal valueOf(BigDecimal val, MathContext mc)
      {
          return new Decimal(val.unscaledValue(), val.scale(), mc);
      }
  
      public static Decimal valueOf(String val)
      {
          boolean negative = val.startsWith("-");
          Decimal ibd = new Decimal(val);
          if (negative && ibd.signum() == 0)
          {
              ibd = new NegativeZero(ibd.scale());
          }
          return ibd;
      }
  
      public static Decimal valueOf(String val, MathContext mc)
      {
          boolean negative = val.startsWith("-");
          Decimal ibd = new Decimal(val, mc);
          if (negative && ibd.signum() == 0)
          {
              ibd = new NegativeZero(ibd.scale(), mc);
          }
          return ibd;
      }
  
  
      //=========================================================================
      // Constructors are private so we have flexibility in changing
      // implementation of how we create negative zero.
      // We force the user to call static valueOf() methods instead.
  
      private Decimal(BigInteger unscaledVal, int scale)
      {
          super(unscaledVal, scale);
      }
  
      private Decimal(BigInteger unscaledVal, int scale, MathContext mc)
      {
          super(unscaledVal, scale, mc);
      }
  
  
      private Decimal(BigInteger val)
      {
          super(val);
      }
  
      private Decimal(BigInteger val, MathContext mc)
      {
          super(val, mc);
      }
  
  
      private Decimal(int val)
      {
          super(val);
      }
  
      private Decimal(int val, MathContext mc)
      {
          super(val, mc);
      }
  
      private Decimal(long val)
      {
          super(val);
      }
  
      private Decimal(long val, MathContext mc)
      {
          super(val, mc);
      }
  
  
      private Decimal(double val)
      {
          super(val);
      }
  
      private Decimal(double val, MathContext mc)
      {
          super(val, mc);
      }
  
  
      // TODO create static valueOf to check for -0
      private Decimal(char[] in, int offset, int len)
      {
          super(in, offset, len);
      }
  
      // TODO create static valueOf to check for -0
      private Decimal(char[] in, int offset, int len, MathContext mc)
      {
         super(in, offset, len, mc);
      }
  
  
      // TODO create static valueOf to check for -0
      private Decimal(char[] in)
      {
          super(in);
      }
  
      // TODO create static valueOf to check for -0
      private Decimal(char[] in, MathContext mc)
      {
          super(in, mc);
      }
  
  
      private Decimal(String val)
      {
          super(val);
      }
  
      private Decimal(String val, MathContext mc)
      {
          super(val, mc);
      }
  
  
      //========================================================================
  
      public final boolean isNegativeZero()
      {
          return (getClass() == NegativeZero.class);
      }
  
      /**
       * Converts this to a "plain" {@link BigDecimal} instance, losing any
       * negative zeros in the process.
       *
       * @return a {@link BigDecimal}, never a {@link Decimal}.
       */
      public final BigDecimal bigDecimalValue()
      {
  
          return new BigDecimal(unscaledValue(), scale());
      }
  }
  