# decimal.java

    /*
    ** Module   : decimal.java
    ** Abstract : Progress 4GL compatible decimal object
    **
    ** Copyright (c) 2005-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- -----------------------------Description-----------------------------
    ** 001 GES 20050517   @21250 Created initial version with full support
    **                           for all Progress decimal processing including
    **                           some functions like decimal, minimum and
    **                           maximum.
    ** 002 GES 20050607   @21442 Minor additions for function support.
    ** 003 GES 20050614   @21490 Added increment and decrement methods.
    ** 004 GES 20050616   @21504 Modified intValue() and longValue() to round
    **                           before return.
    ** 005 GES 20050621   @21532 Added an assign() method to modify this
    **                           instance's state based on the result of
    **                           an expression of compatible type.
    ** 006 GES 20050721   @21764 Fixed extra leading space issue in
    **                           toStringExport(). Merged cloning support
    **                           for SVG.
    ** 007 GES 20050822   @22193 Added Undoable interface support.
    ** 008 GES 20050826   @22259 Implemented use of a BigDecimal in certain
    **                           cases where the faster home-grown algorithm
    **                           in truncateWorker() is limited.  Fixed
    **                           intValue() to throw an error when the value
    **                           cannot be fit into an integer.
    ** 009 GES 20050828   @22278 Added support for temporarily setting
    **                           precision with an automatic reset at the
    **                           end of the scope in which the setting was
    **                           made.
    ** 010 GES 20050829   @22283 Split assignment of configuration and state
    **                           from normal assign(). deepAssign() is now
    **                           provided to set both configuration and state
    **                           at the same time.  Also resolved a problem
    **                           where assign() did not properly round the
    **                           assigned data.
    ** 011 GES 20050829   @22364 Added processing to generate a string of all
    **                           '?' when there is a negative num that has
    **                           no sign char in the format string OR when a
    **                           left side overflow has occurred.
    ** 012 GES 20050830   @22383 Added error condition processing.
    ** 013 ECF 20051004   @22969 Implement hashCode() method. Required to be
    **                           consistent with BaseDataType's implementation
    **                           of equals() method.
    ** 014 ECF 20051104   @23238 Added iterate() method to PrecisionResetter
    **                           inner class. No-op implementation; required
    **                           to satisfy expanded Finalizable interface.
    ** 015 GES 20060123   @24023 Implemented support for decimal and group
    **                           separator overrides.
    ** 016 GES 20060206   @24299 Made compareTo() safe to process any object
    **                           type (avoids ClassCastException).
    ** 017 GES 20060413   @25521 Added static method to set precision of an
    **                           entire array.
    ** 018 GES 20060417   @25554 Added default value generation.
    ** 019 GES 20060517   @26193 Match new Finalizable interface requirement.
    ** 020 GES 20060724   @28160 Added a forced assign() abstract method.
    ** 021 GES 20060807   @28473 Moved to externalizable interface to optimize
    **                           network performance.
    ** 022 ECF 20060812   @28587 Added methods needed for database server-side
    **                           function support.
    ** 023 GES 20060825   @28899 Added context local default format data.
    ** 024 GES 20060830   @28990 Added toString signature to expose control
    **                           over right alignment (insertion of padding
    **                           on the left).
    ** 025 GES 20060920   @29717 Fix for export format with value == 0.
    ** 026 SIY 20070405   @32826 Part of numbers conversion quirk handling.
    ** 027 GES 20070427   @33294 Added handling for the case when Java's
    **                           floating point math silently loses least
    **                           significant digits. This is not a foolproof
    **                           approach but it is better than the previous
    **                           one since it reduces the number of
    **                           significant digits that it processes since
    **                           the digits on the left of the decimal point
    **                           don't need any modification so they can be
    **                           ignored. Note that it is still possible that
    **                           there are inexact representations (multiple
    **                           numbers on input are represented as the same
    **                           double on output). This is a core part of the
    **                           design of IEEE 754 floating point upon which
    **                           Java's double is based.
    ** 028 ECF 20070607   @34012 Replaced internal implementation of double
    **                           with BigDecimal. Certain boundary cases were
    **                           failing with double and required either a
    **                           fixed point decimal implementation or a
    **                           significant rewrite to use a more complicated
    **                           primitive representation (versus a simple
    **                           double). Although the performance is worse
    **                           using BigDecimal, the performance impact of a
    **                           custom solution was unknown and the risk of
    **                           introducing bugs with such a solution was
    **                           high.
    ** 029 ECF 20070618   @34088 Fixed regression introduced by #028 (@34012).
    **                           Certain methods were incorrectly scaling the
    **                           internal value by powers of 10 when they
    **                           should only have been setting the precision.
    ** 030 ECF 20070620   @34157 Fixed NPE in hashCode().
    ** 031 ECF 20070629   @34334 Fixed int/longValue() regression. Code was
    **                           truncating instead of rounding when converting
    **                           to integral values.
    ** 032 ECF 20071025   @35523 Fixed toStringExport(). A change to the
    **                           parent class to insert a space between a
    **                           leading minus and a decimal point is not
    **                           compatible with the export format, so we fix
    **                           up this anomaly in toStringExport(). Also
    **                           reworked ContextContainer to parameterize
    **                           the ContextLocal superclass.
    ** 033 EVL 20080415   @37993 Added new method toString() for the case we
    **                           need to transfer negative - sign for the 0.0
    **                           variable value.
    ** 034 GES 20080417   @38013 Reworked init logic to honor group/dec sep
    **                           overrides that come from the directory. Also
    **                           changed some assignment code to use the
    **                           BigDecimal(double) constructor instead of
    **                           using string parsing.
    ** 035 EVL 20080418   @38026 Fix for the regression caused by the changes
    **                           from #033. The big decimal bd must be used
    **                           in second call to signum().
    ** 036 GES 20080911   @39789 Added assign(int) and assign(double)
    **                           signatures.
    ** 037 CA  20090119   @41173 In some cases, scale was lost in method
    **                           assign(NumberType, boolean) - if the assigned
    **                           value was not a decimal value, the resulting
    **                           BigDecimal was forced to a scale of 10.
    ** 038 ECF 20090212   @41298 Implemented a faster equals(). Handles most
    **                           common cases without having to invoke the
    **                           superclass' implementation.
    ** 039 GES 20090424   @41970 Import change.
    ** 040 CA  20090702   @43025 Added decimal(Date) constructor.
    ** 041 ECF 20090816   @43663 Removed final modifier from class.
    ** 042 SVL 20090824   @43723 Fixed NPE into setValue(String).
    ** 043 SVL 20090908   @43844 Fixed export format.
    ** 044 ECF 20091030   @44290 Fixed precision (i.e., scale) when unknown value
    **                           is assigned. We were losing our scale value in
    **                           this case, because it was only being stored in
    **                           the internal BigDecimal object, which is nulled
    **                           out to represent unknown value.
    ** 045 GES 20110303          Moved maximum/minimum to varargs.
    ** 046 OM  20121220          Added int64 support.
    ** 047 CA  20130123          Added c'tor which builds a new instance from a
    **                           BaseDataType value, if possible. This should be
    **                           used only when constructing an instance using the
    **                           value returned by a DYNAMIC-FUNCTION call; 4GL
    **                           does some special casting in this case, which is
    **                           not implemented yet.
    ** 048 CS  20130128          Added conversion support for memptr related
    **                           functions task #1635
    ** 049 GES 20130322          Fixed the BDT c'tor and modified the assign(BDT,boolean) to be the
    **                           common location for the _POLY morphing. This is safe because all
    **                           current usage would have generated a ClassCastException if that
    **                           method was ever used with an invalid type. Now, instead of the CCE,
    **                           the code will morph the value as is possible. The morphing code
    **                           still needs to be implemented.
    ** 050 OM  20130403          Fixed maximum/minimum methods when unknown values were returned.
    **                           Fixed compareTo when comparing to int64 objects.
    ** 051 OM  20130710          Removed unneeded conversion from Date().
    ** 052 CA  20131001          Eliminated custom group/dec separators when format is used. In this
    **                           case, they must default to comma (,) for group and dot (.) for
    **                           decimal separator.
    ** 053 CA  20131013          Added no-op deleted() method, required by the changes in Finalizable
    **                           interface.
    ** 054 GES 20140115          Implemented proper overflow checking and error generation.
    ** 055 HC  20140213          Added precise conversion to BigDecimal.
    ** 056 GES 20140131          Eliminated stack overflow issue (infinite recursion). Cleaned up the
    **                           code in isOverflow().
    ** 057 HC  20140430          Fixed decimal precision for extent variables.
    ** 058 HC  20140511          Fixed setPrecision behavior - the precision becomes effective after
    **                           the next value assignment.
    ** 059 MAG 20140602          Fixed the BDT c'tor and modified the assign(BDT,boolean) on _POLY
    **                           morphing. Add BDT types on compareTo(Object).
    **                           Improve compareTo(Object) by comparing unknown values.
    **                           Fixed BiDecimal ONE and ZERO with MAX_SCALE.
    **                           Use BigDecimal.ONE on increment() and decrement() methods.
    ** 060 OM  20150630          Converting a DATETIME to DECIMAL is an error. New decimal.fromLiteral
    **                           static method for parsing number format independent literals.
    ** 061 CA  20151222          Added instantiateDefaultExtent.
    ** 062 EVL 20160224          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
    ** 063 GES 20160617          Added unknown literal processing for the polymorphic assign case.
    ** 064 ECF 20160614          Implemented faster copy constructor. Fixed constructor which accepts
    **                           a BigDecimal to honor the scale of the argument. Fixed constructor
    **                           which accepts a String and int to set scale even if value is unknown.
    ** 065 CA  20160627          Reworked undoable support - the undoables register themselves with
    **                           all blocks up the stack, until either 1. the tx block which created
    **                           it is reached or 2. the last tx block where it was saved is reached.
    ** 066 GES 20171016          Added support for construction from a BigInteger. Added
    **                           fromUnsignedHexLiteral() convenience method.
    ** 067 GES 20171206          Removed explicit bypass on pending error (new silent error mode).
    ** 068 CA  20181029          Added a direct-assignment case related to CALL's dynamic invoke, when
    **                           copying data back from an OUTPUT/INPUT-OUTPUT parameter.
    ** 069 OM  20190206          Added implementation of field size needed by record-length.
    ** 070 CA  20190526          Disambiguate an assign(Long) call to assign(long).
    ** 071 CA  20190927          Added support for direct Java access from 4GL code.
    ** 072 GES 20200520          Added 4GL enum constructor.
    **     GES 20200605          Fixed lost precision during conversion of enums into decimals.
    **     ME  20200528          Add assign for "legacy" object - resource id.
    ** 073 CA  20200608          Fixed equals - must use BigDecimal.compareTo.
    ** 074 IAS 20201007          Added Type enum
    ** 075 ME  20210504          Added isIncompatibleTypesOnConversion override
    **                           for invalid POLY conversion.
    **     AL2 20220319          Added value proxy check in assign.
    **     TJD 20220504          Java 11 compatibility minor changes
    ** 076 CA  20230215          'duplicate()' method returns the real type instead of BDT.
    ** 077 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.util;

    import java.io.*;
    import java.math.*;
    import java.text.*;
    import java.util.*;

    import com.goldencode.p2j.util.logging.*;
    import com.goldencode.util.*;
    import com.goldencode.p2j.oo.lang.*;
    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.util.BaseDataType.*;

    /**
     * A class that represents a Progress 4GL compatible decimal object whose
     * data is mutable.  This class provides the basic wrappering of the
     * primitive data, instantiation, type conversion, <code>unknown value</code>
     * representation and the core comparison logic.
     * <p>
     * All Progress language features related to integer/decimal data
     * types are implemented in the {@link MathOps} class (except comparison
     * logic which is in {@link CompareOps}.  All Progress integer/decimal
     * features are supported including all functionality that can be accessed
     * via operators and built-in Progress functions.
     * <p>
     * Rounding and truncation behavior is completely implemented in this
     * class.  In particular, assignments do cause a rounding (thus a potential
     * loss of precision) but constructors (the equivalent of the Progress
     * "init" keyword) do not round even when the precision is set lower than
     * the number of significant digits to the right of the decimal point.  This
     * is how it is done in Progress.
     * <p>
     * An interesting point: intermediate decimal outputs from functions and
     * operators do not inherit the precision (<code>DECIMALS</code>) of the
     * operands or parameters.  In other words, all returned instances from
     * operators or functions will have the default precision of 10.
     */
    public class decimal
    extends NumberType
    {
       /** Maximum precision scale applied to any decimal instance */
       public static final int MAX_SCALE = 10;

       /** Largest 4GL hexadecimal literal that can be represented as a Java signed long. */
       public final static decimal MAX_VALID_HEX_LITERAL =
          decimal.fromUnsignedHexLiteral("0x7FFFFFFFFFFFFFFF");

       /** Smallest 4GL hexadecimal literal that can be represented as a Java signed long. */
       public final static decimal MIN_VALID_HEX_LITERAL =
          decimal.fromUnsignedHexLiteral("-0x8000000000000000");

       /** Constant for BigDecimal value 0 with scale MAX_SCALE */
       static final BigDecimal ZERO = BigDecimal.ZERO.setScale(MAX_SCALE);

       /** Constant for BigDecimal value 1 with scale MAX_SCALE */
       static final BigDecimal ONE = BigDecimal.ONE.setScale(MAX_SCALE);

       /** Stores context-local state variables. */
       private static ContextContainer work = new ContextContainer();

       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(decimal.class);

       /**
        * Stores the value of this instance. <code>null</code> represents the
        * <code>unknown value</code>.
        */
       private BigDecimal value = null;

       /**
        * The number of digits to preserve and to limit to the right of the
        * decimal point.  Defaults to 10 digits.  This is recorded separately from
        * {@link #value} to preserve scale when wrapper is set to <code>unknown
        * value</code>.
        */
       private int scale = MAX_SCALE;

       /**
        * Default constructor which creates an instance representing the
        * <code>unknown value</code>.
        */
       public decimal()
       {
       }

       /**
        * This is a special c'tor which should be used only when converting the
        * value returned by a function or method with polymorphic return type into the
        * expected type (i.e. DYNAMIC-FUNCTION()).  In such cases, the 4GL does some
        * automatic type conversion (see {@link #assign(BaseDataType)}).
        *
        * @param    value
        *           The value to be used for this instance.
        */
       public decimal(BaseDataType value)
       {
          // the value may be a proxy; make sure to unwrap and work with the real value
          value = value == null ? null : value.val();
          if (value == null || value.isUnknown())
          {
             setUnknown();
          }
          else
          {
             assign(value);
          }
       }

       /**
        * Constructs an instance from a long value and with precision set to its
        * maximum.  No loss of precision will occur.
        * <p>
        * It is not possible to yield an <code>unknown value</code> from this
        * constructor without calling the method {@link #setUnknown} separately.
        *
        * @param   value
        *          The value to be used for this instance.
        */
       public decimal(long value)
       {
          this(BigDecimal.valueOf(value).setScale(MAX_SCALE));
       }

       /**
        * Constructs an instance from an int value and with precision set to its
        * maximum.  No loss of precision will occur.
        * <p>
        * It is not possible to yield an <code>unknown value</code> from this
        * constructor without calling the method {@link #setUnknown} separately.
        *
        * @param   value
        *          The value to be used for this instance.
        */
       public decimal(int value)
       {
          this((long) value);
       }

       /**
        * Constructs an instance from an int value.  No loss of precision will
        * occur.
        * <p>
        * It is not possible to yield an <code>unknown value</code> from this
        * constructor without calling the method {@link #setUnknown} separately.
        *
        * @param   value
        *          The value to be used for this instance.
        * @param   precision
        *          The number of digits to maintain to the right of the decimal
        *          point.
        */
       public decimal(int value, int precision)
       {
          this(BigDecimal.valueOf(value));

          setPrecision(precision);
       }

       /**
        * Constructs an instance from an integer value and with precision set to
        * its maximum.  No loss of precision will occur.
        * <p>
        * If the parameter's represents the <code>unknown value</code>, this
        * instance will also represent the <code>unknown value</code>.
        *
        * @param   value
        *          The value to be used for this instance.
        */
       public decimal(integer value)
       {
          deepAssign(value);
       }

       /**
        * Constructs an instance from an int64 value and with precision set to
        * its maximum.  No loss of precision will occur.
        * <p>
        * If the parameter's represents the <code>unknown value</code>, this
        * instance will also represent the <code>unknown value</code>.
        *
        * @param   value
        *          The value to be used for this instance.
        */
       public decimal(int64 value)
       {
          deepAssign(value);
       }

       /**
        * Constructs an instance from a double value and with precision set to
        * its maximum.  Loss of precision may occur.
        * <p>
        * If the parameter's value is <code>NaN</code>, the instance will
        * represent the <code>unknown value</code>.
        *
        * @param   value
        *          The value to be used for this instance.
        */
       public decimal(double value)
       {
          setValue(value);
       }

       /**
        * Constructs an instance from a double value.  Loss of precision may
        * occur.
        * <p>
        * If the parameter's value is <code>NaN</code>, the instance will
        * represent the <code>unknown value</code>.
        *
        * @param   value
        *          The value to be used for this instance.
        * @param   precision
        *          The number of digits to maintain to the right of the decimal
        *          point.
        */
       public decimal(double value, int precision)
       {
          setValue(value);
          setPrecision(precision);
       }

       /**
        * Constructs an instance after copying the parameter into the internal
        * representation of this class.  No loss of precision will occur.
        * <p>
        * If the parameter's represents the <code>unknown value</code>, this
        * instance will also represent the <code>unknown value</code>.
        *
        * @param   value
        *          The value to be used for this instance.
        */
       public decimal(decimal value)
       {
          // the value may be a proxy; make sure to unwrap and work with the real value
          if (BaseDataType.isProxy(value))
          {
             initialize(value);
             return;
          }
          this.value = value.value;
          this.scale = value.scale;
       }

       /**
        * Constructs an instance after converting the string representation of a number into a
        * {@code BigDecimal}. Due to rounding, a loss of precision may occur in this conversion (at
        * the 10th digit to the right of the decimal point).
        * <p>
        * This conversion constructor always uses the current number format for parsing. Ex: in the
        * default (American) format "3,141" parses as an integer value of 3141, but on European
        * (-E Progress command line parameter) it will be parsed as an approximation of PI (3.141).
        * For parsing a decimal literal (in American format) regardless of current number format use
        * the {@link #fromLiteral(String)}.
        *
        * @param   value
        *          The value to be used for this instance.
        */
       public decimal(String value)
       {
          this(value, MAX_SCALE);
       }

       /**
        * Constructs an instance after converting the string representation of a number into a
        * {@code BigDecimal}. Due to rounding, a loss of precision may occur in this conversion.
        * <p>
        * This conversion constructor always uses the current number format for parsing. Ex: in the
        * default (American) format "3,141" parses as an integer value of 3141, but on European
        * (-E Progress command line parameter) it will be parsed as an approximation of PI (3.141).
        * For parsing a decimal literal (in American format) regardless of current number format use
        * the {@link #fromLiteral(String)}.
        *
        * @param   value
        *          The value to be used for this instance.
        * @param   precision
        *          The number of digits to maintain to the right of the decimal
        *          point.
        */
       public decimal(String value, int precision)
       {
          // we have to set precision (scale), regardless of unknown value
          setPrecision(precision);

          // special case: unknown value
          if (value == null || value.equals("?"))
          {
             setUnknown();

             return;
          }

          setValue(value);
       }

       /**
        * Constructs an instance after converting the string representation of a number into a
        * {@code BigDecimal}. Due to rounding, a loss of precision may occur in this conversion (at
        * the 10th digit to the right of the decimal point).
        * <p>
        * This conversion constructor always uses the current number format for parsing. Ex: in the
        * default (American) format "3,141" parses as an integer value of 3141, but on European
        * (-E Progress command line parameter) it will be parsed as an approximation of PI (3.141).
        * For parsing a decimal literal (in American format) regardless of current number format use
        * the {@link #fromLiteral(String)}.
        * <p>
        * If the parameter is {@code unknown value} the resulting decimal is the {@code unknown
        * value}.
        *
        * @param   value
        *          The value to be used for this instance.
        */
       public decimal(character value)
       {
          // the value may be a proxy; make sure to unwrap and work with the real value
          if (BaseDataType.isProxy(value))
          {
             if (value.val() instanceof character)
             {
                value = (character) value.val();
             }
             else
             {
                initialize(value.val());
             }
             return;
          }
          if (value.isUnknown())
          {
             setUnknown();
          }
          else
          {
             // special case: unknown value
             if (value.getValue().equals("?"))
             {
                setUnknown();

                return;
             }

             setValue(value.getValue());
             setPrecision(MAX_SCALE);
          }
       }

       /**
        * Constructs an instance after converting the string representation of a number into a
        * {@code BigDecimal}. Due to rounding, a loss of precision may occur in this conversion (at
        * the 10th digit to the right of the decimal point).
        * <p>
        * This conversion constructor always uses the current number format for parsing. Ex: in the
        * default (American) format "3,141" parses as an integer value of 3141, but on European
        * (-E Progress command line parameter) it will be parsed as an approximation of PI (3.141).
        * For parsing a decimal literal (in American format) regardless of current number format use
        * the {@link #fromLiteral(String)}.
        * <p>
        * If the parameter is {@code unknown value} the resulting decimal is the {@code unknown
        * value}.
        *
        * @param   value
        *          The value to be used for this instance.
        */
       public decimal(longchar value)
       {
          // the value may be a proxy; make sure to unwrap and work with the real value
          if (BaseDataType.isProxy(value))
          {
             if (value.val() instanceof longchar)
             {
                value = (longchar) value.val();
             }
             else
             {
                initialize(value.val());
             }
             return;
          }
          // unknown longchar results in 0 decimal value
          setValue(value.isUnknown() ? "0" : value.getValue());
          setPrecision(MAX_SCALE);
       }

       /**
        * Constructs an instance wth the given enum's value.
        * <p>
        * If the parameter is {@code unknown value} the resulting instance is the {@code unknown value}.
        *
        * @param    enm
        *           The enum whose value to be used for this instance.
        */
       public decimal(object<? extends LegacyEnum> enm)
       {
          if (enm.isUnknown())
          {
             setUnknown();
          }
          else
          {
             setValue(enm.ref()._getValue());
          }
       }

       /**
        * Constructs an instance after copying the parameter's data into the
        * internal representation of this class.
        * <p>
        * If the parameter's represents the <code>unknown value</code>, this
        * instance will also represent the <code>unknown value</code>.
        * <p>
        * When the parameter is <code>true</code> the resulting decimal value
        * will be 1. Otherwise the value is 0.
        *
        * @param   value
        *          The value to be used for this instance.
        */
       public decimal(logical value)
       {
          // the value may be a proxy; make sure to unwrap and work with the real value
          if (BaseDataType.isProxy(value))
          {
             if (value.val() instanceof logical)
             {
                value = (logical) value.val();
             }
             else
             {
                initialize(value.val());
             }
             return;
          }
          if (!value.isUnknown())
          {
             this.value = (value.booleanValue() ? ONE : ZERO);
          }
       }

       /**
        * Constructs an instance after copying the parameter's data into the
        * internal representation of this class.
        * <p>
        * When the parameter is <code>true</code> the resulting decimal value
        * will be 1. Otherwise the value is 0.
        *
        * @param   value
        *          The value to be used for this instance.
        */
       public decimal(boolean value)
       {
          this.value = (value ? ONE : ZERO);
       }

       /**
        * Constructs an instance after copying the parameter's data into the
        * internal representation of this class.
        * <p>
        * If the parameter represents the <code>unknown value</code>, this
        * instance will also represent the <code>unknown value</code>.
        *
        * @param   value
        *          The value to be used for this instance.
        */
       public decimal(date value)
       {
          // the value may be a proxy; make sure to unwrap and work with the real value
          if (BaseDataType.isProxy(value))
          {
             if (value.val() instanceof date)
             {
                value = (date) value.val();
             }
             else
             {
                initialize(value.val());
             }
             return;
          }
          if (!value.isUnknown())
          {
             if (value instanceof datetime)
             {
                ErrorManager.recordOrShowError(
                   5729, "Incompatible datatypes found during runtime conversion", false);
             }
             else
             {
                this.value = BigDecimal.valueOf(value.longValue());
                setPrecision(MAX_SCALE);
             }
          }
       }

       /**
        * Constructs an instance after copying the parameter's data into the
        * internal representation of this class.
        * <p>
        * If the parameter is <code>null</code>, this instance will represent
        * the <code>unknown value</code>.
        *
        * @param   value
        *          The value to be used for this instance.
        */
       public decimal(Number value)
       {
          if (value instanceof BigInteger)
          {
             // convert to decimal and then let the normal processing below complete
             value = new BigDecimal((BigInteger)value);
          }

          if (value instanceof BigDecimal)
          {
             BigDecimal bd = (BigDecimal) value;
             this.scale = bd.scale();
             setValue(bd);
          }
          else if (value instanceof Long)
          {
             setValue(value.longValue());
          }
          else if (value != null)
          {
             setValue(value.doubleValue());
          }
       }

       /**
        * Parses a Progress decimal literal and returns a {@code decimal} represented.
        * Unlike the conversion constructors that always uses the current number format for parsing,
        * this utility method will always use the American number format, regardless of current number
        * format in use. If you need to convert a language dependent string, use one of the conversion
        * constructors.
        * <p>
        * Ex: the "3,141" will always be parsed as a decimal with integer value of 3141.
        * The same string will be parsed by the conversion constructors as 3141 if the default
        * (American) number format is in use or as an approximation of PI (3.141) if the European
        * (-E Progress command line parameter) format was set.
        *
        * @param   decimalLiteral
        *          The string to be parsed.
        *
        * @return  the decimal value of the Progress literal represented by the
        *          {@code decimalLiteral}.
        */
       public static decimal fromLiteral(String decimalLiteral)
       {
          decimal ret = new decimal();

          // special case: unknown value
          if (decimalLiteral == null || "?".equals(decimalLiteral))
          {
             return ret; // unknown decimal
          }

          // this will also check for overflows, so it is safe to assign the result below
          String javaStr = parseDecimal(decimalLiteral, '.', ',', false); // literal separators
          if (javaStr != null)
          {
             ret.setValue(new BigDecimal(javaStr).setScale(MAX_SCALE, RoundingMode.HALF_UP));
             ret.setPrecision(MAX_SCALE);
          }
          return ret;
       }

       /**
        * This parses an unsigned hexadecimal whole number and returns a decimal instance that
        * represents the exact value.  This is needed because the Progress hexadecimal literal
        * is an unsigned 64-bit value and the Java hexadecimal literal is a signed 64-bit value.
        * By storing the value as a decimal instance, the full range of 4GL values can be
        * represented with fidelity.
        * <p>
        * Input format follows the 4GL hexadecimal literal rules:
        * <p>
        * <ol>
        *   <li> Optional prefixed minus sign.
        *   <li> The characters 0x or 0X.
        *   <li> Between 0 and 16 (inclusive) case-insensitive hexadecimal characters (0-9, A-F).
        *   <li> No whitespace or any other characters are allowed.
        *   <li> If no hexadecimal characters are provided, the value is 0.
        * </ol>
        *
        * @param   hex
        *          The hexadecimal string to be parsed. Must not be <code>null</code>. See above
        *          for the valid format.
        *
        * @return  The decimal value of the given 4GL hexadecimal literal.
        *
        * @throws   NullPointerException
        *           If <code>null</code> is passed.
        * @throws   NumberFormatException
        *           If the input string is invalid.
        */
       public static decimal fromUnsignedHexLiteral(String hex)
       {
          if (hex == null)
          {
             throw new NullPointerException("Invalid hexadecimal input.");
          }

          int     len  = hex.length();
          boolean sign = (hex.charAt(0) == '-');
          int     max  = sign ? 19 : 18;

          if (len == 0)
          {
             throw new NumberFormatException("Empty string input.");
          }

          if (len > max)
          {
             throw new NumberFormatException("Invalid chars in hexadecimal input '" + hex + "'");
          }

          String txt = hex;

          if (sign)
          {
             txt = hex.substring(1);
          }

          if (!"0x".equalsIgnoreCase(txt.substring(0, 2)))
          {
             throw new NumberFormatException("Missing 0x/0X in hexadecimal input '" + hex + "'");
          }

          txt = txt.substring(2);

          return new decimal(new BigInteger((sign ? "-" : "") + txt, 16));
       }

       /**
        * Get the type
        * @return type
        */
       public Type getType()
       {
          return Type.DECIMAL;
       }

       /**
        * Returns the largest instance from the list. This list may not include {@code null} values.
        *
        * @param    list
        *           The list of decimals and/or integers to check.
        *
        * @return   The largest instance or {@code unknown value} if any entries in the list are
        *           {@code unknown} or {@code null} if an empty list is passed.
        */
       public static decimal maximum(NumberType ... list)
       {
          NumberType num = (NumberType) CompareOps.maximum(list);
          if (num instanceof decimal)
          {
             // this includes the particular num.isUnknown() case
             return (decimal) num;
          }
          return num.isUnknown() ? new decimal() : new decimal(num.doubleValue());
       }

       /**
        * Returns the smallest instance from the list.  This list may not include
        * <code>null</code> values.
        *
        * @param    list
        *           The list of decimals and/or integers to check.
        *
        * @return   The smallest instance or <code>unknown value</code> if any
        *           entries in the list are unknown or <code>null</code> if
        *           an empty list is passed.
        */
       public static decimal minimum(NumberType ... list)
       {
          NumberType num = (NumberType) CompareOps.minimum(list);
          if (num instanceof decimal)
          {
             // this includes the particular num.isUnknown() case
             return (decimal) num;
          }
          return num.isUnknown() ? new decimal() : new decimal(num.doubleValue());
       }

       /**
        * Sets the number of digits that will be maintained to the right of
        * the decimal point in each element of the given array.  All assignments
        * to the value of those elements will be rounded/truncated to this number
        * of digits.  The value can range between 0 and 10 (inclusive).
        * <p>
        * The precision will become effective on the next value assignment. I.e.
        * the value will not change its precision right after this method is called.
        * @param    list
        *           array to be processed
        * @param    precision
        *           The precision which must range between 0 and 10 (inclusive).
        */
       public static void setPrecision(decimal[] list, int precision)
       {
          if (precision < 0 || precision > MAX_SCALE)
          {
             throw new IllegalArgumentException("Invalid precision " + precision + ".");
          }

          for (decimal item : list)
          {
             item.setPrecision(precision);
          }

          // set the decimal precision, for lengths > 0 the precision is stored
          // in the array components
          if (list.length == 0)
          {
             ArrayAssigner.setDecimalPrecision(list, precision);
          }
       }

       /**
        * Clear any cached formats using the custom group and decimal separators.
        */
       static void removeExplicitFormats()
       {
          WorkArea wa = work.get();
          wa.decimalFmt = null;
       }

       /**
        * Get the decimal format, which uses the custom group and decimal separators.
        *
        * @return   See above.
        */
       private static DecimalFormat getDecimalFormat()
       {
          WorkArea wa = work.get();

          if (wa.decimalFmt == null)
          {
             char decSep = getDecimalSeparator();
             char groupSep = getGroupSeparator();

             // here we must manually override the normal JVM decimal format
             // processing otherwise if custom group and decimal seps are defined
             // for this context, it would conflict with the default JVM version

             DecimalFormatSymbols dfs =
                 new DecimalFormatSymbols(Locale.getDefault(Locale.Category.FORMAT));
             dfs.setDecimalSeparator(decSep);
             dfs.setGroupingSeparator(groupSep);

             // the pattern must not be localized, use dot as dec separator
             wa.decimalFmt = new DecimalFormat("##################.##########", dfs);
          }

          return wa.decimalFmt;
       }

       /**
        * Dynamically builds the default format string for this given number type
        * which honors the given group and decimal separators.
        *
        * @return   The default format string for this type of number.
        */
       public String buildDefaultFormat()
       {
          return "->>" + FORMAT_GROUP_SEP + ">>9" + FORMAT_DEC_SEP + "99";
       }

       /**
        * Returns the default format string for this given number type (which
        * honors the given group and decimal separators), building that format
        * and caching it if this is the first call in this context.
        *
        * @return   The default format string for this type of number.
        */
       public String obtainDefaultFormat()
       {
          WorkArea wa = work.get();

          if (wa.defFmt == null)
          {
             wa.defFmt = buildDefaultFormat();
          }

          return wa.defFmt;
       }

       /**
        * Returns the export format string for this given number type which
        * honors the given group and decimal separators.
        *
        * @return   The export format string for this type of number or
        *           <code>null</code> if this type does not use an export
        *           format.
        */
       public String buildExportFormat()
       {
          return "-" +
                 StringHelper.repeatChar('>', 50) +
                 FORMAT_DEC_SEP +
                 StringHelper.repeatChar('<', 10);
       }

       /**
        * An equality test which handles the most common cases with the least
        * amount of overhead.  The superclass implementation is invoked otherwise.
        * <p>
        * This implementation is consistent with {@link #hashCode()}.
        *
        * @see com.goldencode.p2j.util.BaseDataType#equals(java.lang.Object)
        */
       public boolean equals(Object o)
       {
          if (this == o)
          {
             return true;
          }

          if (o instanceof decimal)
          {
             decimal that = (decimal) o;

             boolean u1 = (this.value == null);
             boolean u2 = (that.value == null);

             if (u1 && u2)
             {
                return true;
             }

             if (u1 || u2)
             {
                return false;
             }

             // BigDecimal.equals checks the scale, which may differ, even if the values are the same.
             // we need exact comparison for this.
             return this.value.compareTo(that.value) == 0;
          }

          return super.equals(o);
       }

       /**
        * Hash code implementation which is consistent with {@link
        * BaseDataType#equals}.
        *
        * @return  Hash code value for this object instance.
        */
       public int hashCode()
       {
          int result = 17;
          result = 37 * result + (value == null ? 0 : value.hashCode());

          return result;
       }

       /**
        * Returns the value of this instance as a <code>long</code>.  Loss of
        * precision may occur in this conversion (all digits to the right of
        * the decimal point will be truncated).
        * <p>
        * This method will fail if the instance represents the
        * <code>unknown value</code>.
        *
        * @return   The value cast into a <code>long</code>.
        *
        * @throws   NullPointerException
        *           If this instance represents the <code>unknown value</code>.
        */
       public long longValue()
       throws NullPointerException
       {
          if (isUnknown())
          {
             String errmsg = "Unknown value cannot be converted to long.";
             throw new NullPointerException(errmsg);
          }

          // round to nearest integral value.
          BigDecimal d = value.setScale(0, RoundingMode.HALF_UP);

          return d.longValue();
       }

       /**
        * Returns the value of this instance as an <code>int</code>.  Loss of
        * precision may occur in this conversion.  All digits to the right of
        * the decimal point will be truncated and some large numbers cannot be
        * represented in an <code>int</code>.
        * <p>
        * This method will fail if the instance represents the
        * <code>unknown value</code>.
        *
        * @return   The value cast into an <code>int</code>.
        *
        * @throws   NullPointerException
        *           If this instance represents the <code>unknown value</code>.
        * @throws   ErrorConditionException
        *           If the value is too large to fit in an integer.
        */
       public int intValue()
       throws NullPointerException,
              ErrorConditionException
       {
          if (isUnknown())
          {
             String errmsg = "Unknown value cannot be converted to int.";
             throw new NullPointerException(errmsg);
          }

          // round to nearest integral value.
          long result = value.setScale(0, RoundingMode.HALF_UP).longValue();

          // make sure we don't overflow/underflow
          if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE)
          {
             String errmsg = "Value too large for integer";
             ErrorManager.recordOrThrowError(78, errmsg);
             return 0;
          }

          return (int) result;
       }

       /**
        * Returns the value of this instance as an <code>integer</code>.  Loss of
        * precision may occur in this conversion.  All digits to the right of
        * the decimal point will be truncated and some large numbers cannot be
        * represented in an <code>integer</code>.
        * <p>
        * This method will maintain the <code>unknown value</code> in the
        * returned instance if this instance represents the
        * <code>unknown value</code>.
        *
        * @return   The value cast into an integral <code>integer</code>.
        */
       public integer integerValue()
       {
          return (new integer(this));
       }

       /**
        * Returns the value of this instance as a <code>double</code>. Loss
        * of precision may occur in this conversion, if the current value cannot
        * be represented in a <code>double</code>. This is the same as {@link
        * #getValue}. Use {@linkplain #toBigDecimal()} if keeping the precision
        * is the concern.
        * <p>
        * If this instance represents the <code>unknown value</code>, the
        * returned <code>double</code> will be set to <code>NaN</code>.
        *
        * @return   The double value of this decimal.
        */
       public double doubleValue()
       {
          if (isUnknown())
          {
             return Double.NaN;
          }

          return value.doubleValue();
       }

       /**
        * Returns the value of this instance as a <code>BigDecimal</code>. No loss
        * of precision will occur in this conversion.
        * <p>
        * If this instance represents the <code>unknown value</code>, the
        * return value will be <code>null</code>.
        *
        * @return   The <code>BigDecimal</code> instance, see above for details.
        */
       public BigDecimal toBigDecimal()
       {
          return isUnknown() ? null : value;
       }

       /**
        * Increments the value of this instance by 1.
        * <p>
        * If this instance represents the <code>unknown value</code>, nothing
        * is done.
        */
       public void increment()
       {
          if (isUnknown())
          {
             return;
          }

          BigDecimal newVal = value.add(BigDecimal.ONE);

          checkUndoable(true);

          value = newVal;
       }

       /**
        * Decrements the value of this instance by 1.
        * <p>
        * If this instance represents the <code>unknown value</code>, nothing
        * is done.
        */
       public void decrement()
       {
          if (isUnknown())
          {
             return;
          }

          BigDecimal newVal = value.subtract(BigDecimal.ONE);

          checkUndoable(true);

          value = newVal;
       }

       /**
        * Determines if this instance represents the <code>unknown value</code>.
        *
        * @return   <code>true</code> if this instance is set to the
        *           <code>unknown value</code>.
        */
       public boolean isUnknown()
       {
          return (value == null);
       }

       /**
        * Sets the state of this instance's <code>unknown value</code> state.
        * <p>
        * <b>Warning: this method is destructive. Any data previously stored
        * instance is set to the correct value.</b>
        */
       public void setUnknown()
       {
          if (value != null)
          {
             checkUndoable(true);
          }

          value = null;
       }

       /**
        * Creates a new instance of the same type that represents the
        * <code>unknown value</code>.
        *
        * @return   An instance that represents the <code>unknown value</code>.
        */
       public BaseDataType instantiateUnknown()
       {
          return new decimal();
       }

       /**
        * Creates a new instance of the same type that represents the
        * default initialized value.
        *
        * @return   An instance that represents the default value.
        */
       public BaseDataType instantiateDefault()
       {
          return new decimal("0");
       }

       /**
        * Does the same as standard <code>clone()</code> method but returns an
        * instance of <code>BaseDataType</code> and doesn't throw the
        * <code>CloneNotSupportedException</code>.
        *
        * @return   A clone of this instance.
        */
       public decimal duplicate()
       {
          return (new decimal(this));
       }

       @Override
       protected boolean isIncompatibleTypesOnConversion(BaseDataType value)
       {
          // invalid conversion is being thrown even if unknown
          if (value instanceof longchar || value instanceof clob || value instanceof blob)
          {
             incompatibleTypesOnConversion();
          }

          return super.isIncompatibleTypesOnConversion(value);
       }

       /**
        * Sets the state (data and unknown value) of this instance based on the
        * state of the passed instance.
        * <p>
        * If the value is not of type <code>NumberType</code>, the following automatic type
        * conversion will occur:
        * <p>
        * <ol>
        *    <li> if the value is a rowid or BinaryData (raw or memptr) it attempts to convert
        *         the string representation value to a decimal value</li>
        *    <li> if the value is a character or longchar value it attempts to convert the string
        *         representation value to a decimal value</li>
        *    <li> if the value is a non-unknown logical value, it attempts to convert the logical
        *         value to a decimal value as follow: 0.0 for <b>false</b> and 1.0 for <b>true</b>
        *         having scale equals to MAX_SCALE</li>
        *    <li> if the value is a non-unknown date or datetime value the number of days from
        *         1/1/4713 B.C. to that date are converted to decimal value.</li>
        *    <li> if the value is a non-unknown handle or comhandle value, the resource id when
        *         available is converted to a decimal value, when no resource or resource id the
        *         value is set to unknown.</li>
        *    <li> else, it attempts to convert the value's string representation to decimal</li>
        * </ol>
        * <p>
        * This variant is meant to handle the cases of built-in functions and methods in the 4GL
        * which have polymorphic return types (e.g. DYNAMIC-FUNCTION()). This should NOT be used for
        * non-polymorphic assignments.
        *
        * @param    value
        *           The instance from which to copy state.
        */
       public void assign(BaseDataType value)
       {
          if (value == null)
          {
             setUnknown();
             return;
          }

          // the value may be a proxy; make sure to unwrap and work with the real value
          value = value.val();
          if (value.isUnknown())
          {
             setUnknown();
          }
          else if (value instanceof NumberType)
          {
             // decimal, integer, int64, recid
             assign((NumberType) value);
          }
          else
          {
             // this path is only used when type morphing is needed
             if (value instanceof date && !(value instanceof datetime))
             {
                // date values only
                assign(((date) value).longValue());
             }
             else if (value instanceof handle)
             {
                // handle and com-handle
                Long id = ((handle) value).getResourceId();

                if (id != null)
                {
                   assign(id.longValue());
                }
                else
                {
                   setUnknown();
                }
             }
             else if (value instanceof logical)
             {
                assign(((logical) value).booleanValue() ? 1 : 0);
             }
             else if (value instanceof Text)
             {
                // character and long character
                assign(new decimal(value.toStringMessage()));
             }
             else if (value instanceof object)
             {
                assign((object<?>) value);
             }
             else if (value instanceof jobject)
             {
                assign((jobject<?>) value);
             }
             else
             {
                incompatibleTypesOnConversion();
             }
          }
       }

       /**
        * Sets the state (data and unknown value) of this instance based on the
        * state of the passed instance.
        *
        * @param    value
        *           The instance from which to copy state.
        */
       public void assign(NumberType value)
       {
          // the value may be a proxy; make sure to unwrap and work with the real value
          if (BaseDataType.isProxy(value))
          {
             assign(value.val());
             return;
          }

          if (value instanceof decimal)
          {
             decimal d = (decimal) value;

             // this handles truncation as set by our precision
             setValue(d.value);
          }
          else
          {
             // must be an integer / int64
             if (value.isUnknown())
             {
                setUnknown();
             }
             else
             {
                setValue(value.longValue());
             }
          }
       }

       /**
        * Sets the state (data/unknown value) of this instance based on the state
        * of the passed data.  The configuration (precision) of this
        * instance is not set.  Use {@link #deepAssign} if both state and
        * configuration must be set.
        * <p>
        * This method may result in a loss of precision if the number of
        * significant digits to the right of the decimal point is greater than
        * that specified by the <code>precision</code> instance member.  In this
        * case, the excess digits will be dropped using a rounding method (if
        * the leftmost lost digit is 0-4 it will round down and 5-9 it will round
        * up).
        *
        * @param    value
        *           The data from which to copy state. <code>NaN</code> will
        *           force this instance to be the unknown value.
        */
       public void assign(double value)
       {
          assign(new decimal(value));
       }

       /**
        * Sets the state (data) of this instance based on the input data.  The
        * configuration (precision) of this instance is not set.  Use
        * {@link #deepAssign} if both state and configuration must be set.
        *
        * @param    value
        *           The data from which to copy state.
        */
       public void assign(int value)
       {
          assign(new integer(value));
       }

       /**
        * Sets the state (data and unknown value) of this instance based on the
        * state of the passed instance.
        *
        * @param    value
        *           The instance from which to copy state.
        */
       public void assign(Undoable value)
       {
          assign((NumberType) value);
       }

       /**
        * Sets the state (data/unknown value) and configuration (precision) of
        * this instance based on the state of the passed instance.  No loss of
        * precision will occur since the resulting instance will be an identical
        * copy of the original value.
        *
        * @param    value
        *           The instance from which to copy state.
        */
       public void deepAssign(NumberType value)
       {
          if (value.val() instanceof decimal)
          {
             decimal d = (decimal) value.val();
             setValue(d.value);
          }
          else
          {
             // integer/int64 path which leverages common code
             assign(value.val());
          }
       }

       /**
        * Returns the number of digits that will be maintained to the right of
        * the decimal point in this instance.  By default, this will be 10. All
        * assignments to the value of this instance will be rounded/truncated to
        * this number of digits.  The value can range between 0 and 10
        * (inclusive).
        *
        * @return   The precision of this instance.
        */
       public int getPrecision()
       {
          // Note that internally, we refer to this as "scale", to match the
          // meaning in BigDecimal, while the API refers to it as "precision" to
          // match the legacy terminology.
          return scale;
       }

       /**
        * Sets the number of digits that will be maintained to the right of
        * the decimal point in this instance.  All assignments to the value of
        * this instance will be rounded/truncated to this number of digits.  The
        * value can range between 0 and 10 (inclusive).
        * <p>
        * This method is used to set the current value in a manner that does not
        * schedule an automated reset to the prior value later. Thus, this method
        * is suitable for the initial configuration of a new instance.  If a
        * temporary precision change is needed, use
        * {@link #setTemporaryPrecision}.
        * <p>
        * The precision will become effective on the next value assignment. I.e.
        * the value will not change its precision right after this method is called.
        *
        * @param    precision
        *           The precision for this instance which must range between 0
        *           and 10 (inclusive).
        */
       public void setPrecision(int precision)
       {
          if (precision < 0 || precision > MAX_SCALE)
          {
             throw new IllegalArgumentException("Invalid precision " + precision + ".");
          }

          if (this.scale != precision)
          {
             checkUndoable(true);
          }

          // Note that internally, we refer to this as "scale", to match the
          // meaning in BigDecimal, while the API refers to it as "precision" to
          // match the legacy terminology.
          scale = precision;

          // the precision will become effective on the next value assignment
       }

       /**
        * Sets the number of digits that will be maintained to the right of
        * the decimal point in this instance, for the durations of the current
        * scope in the calling program.  All assignments to the value of
        * this instance within the current scope of the calling program will be
        * rounded/truncated to this number of digits.  The value can range
        * between 0 and 10 (inclusive).  When the caller's current scope ends,
        * this instance's current precision will be reset to the prior value.
        * <p>
        * This method should only be used when importing references via the
        * {@link SharedVariableManager} as a local instance or a new shared
        * instance would not need to be reset, since they would have no prior
        * value to which to return.  To set the initial precision, please use
        * {@link #setPrecision}.
        * <p>
        * The {@link TransactionManager#registerFinalizable} facility is used to
        * obtain the callback necessary to reset the precision at end of the
        * caller's scope.
        * <p>
        * The precision will become effective on the next value assignment. I.e.
        * the value will not change its precision right after this method is called.
        *
        * @param    precision
        *           The precision for this instance which must range between 0
        *           and 10 (inclusive).
        */
       public void setTemporaryPrecision(int precision)
       {
          // do nothing if the request precision is the same as the current
          // precision
          if (precision != scale)
          {
             // remember the old value
             int old = scale;

             // reset (if there is some error, this will throw an exception)
             setPrecision(precision);

             // if no error, schedule the reset
             PrecisionResetter pr = new PrecisionResetter(old);
             TransactionManager.registerFinalizable(pr, false);
          }
       }

       /**
        * Compares this instance with the specified instance and returns a -1
        * if this instance is less than the specified, 0 if the two instances
        * are equal and 1 if this instance is greater than the specified
        * instance.  This is the implementation of the <code>Comparable</code>
        * interface.
        * <p>
        * The algorithm will fail to give meaningful results in the case where
        * one tries to sort against other objects that do not represent compatible
        * values.
        *
        * @param    obj
        *           The instance to compare against.
        *
        * @return   -1, 0 or 1 depending on if this instance is less than,
        *           equal to or greater (respectively) than the specified
        *           instance <code>obj</code>.
        */
       public int compareTo(Object obj)
       {
          boolean unknown = isUnknown();
          BigDecimal bd = null;

          if (obj instanceof decimal)
          {
             bd = ((decimal) obj).toBigDecimal();
          }
          else if (obj instanceof BaseDataType)
          {
             BaseDataType bdt = (BaseDataType) obj;

             decimal dec = new decimal(bdt);

             /*
              * An unknown value should be always converted to a unknown decimal value.
              * A non-unknown value should be always converted to a non-unknown decimal value.
              * If an unknown value is converted to a non-unknown value the conversion fails.
              * If a non-unknown value is converted to an unknown value the conversion fails.
              */
             if (dec.isUnknown() != bdt.isUnknown())
             {
                incompatibleTypesOnConversion();
             }

             bd = dec.toBigDecimal();
          }
          else if (obj instanceof BigDecimal)
          {
             bd = (BigDecimal) obj;
          }
          else if (obj instanceof Number)
          {
             bd = new BigDecimal(((Number) obj).doubleValue())
                     .setScale(MAX_SCALE, RoundingMode.HALF_UP);
          }
          else if (obj != null)
          {
             return -1;
          }

          if (unknown || bd == null)
          {
             if (value == bd)
             {
                return 0;
             }
             else if (unknown)
             {
                return -1;
             }
             else
             {
                return 1;
             }
          }

          return value.compareTo(bd);
       }

       /**
        * Returns the larger of the current or passed-in instance.
        *
        * @param    d
        *           The instance to compare against.
        *
        * @return   The largest instance.
        */
       public decimal maximum(decimal d)
       {
          return (decimal) super.maximum(d);
       }

       /**
        * Returns the smaller of the current or passed-in instance.
        *
        * @param    d
        *           The instance to compare against.
        *
        * @return   The smallest instance.
        */
       public decimal minimum(decimal d)
       {
          return (decimal) super.minimum(d);
       }

       /**
        * Return the current value as a Java wrapper.
        *
        * @return  Current value or <code>null</code> if unknown.
        */
       public BigDecimal toJavaType()
       {
          return toBigDecimal();
       }

       /**
        * Creates a string representation of the instance data using the default
        * Progress 4GL format of '-&gt;&gt;,&gt;&gt;9.99'.
        *
        * @return   The formatted string.
        */
       public String toString()
       {
          return toString(null);
       }

       /**
        * Creates a string representation of the instance data in a form that is
        * compatible with the <code>MESSAGE</code> language statement.  If the
        * instance represents the <code>unknown value</code>, a '?' will be
        * returned.
        *
        * @return   The 'message' formatted string.
        */
       public String toStringMessage()
       {
          return toStringExport();
       }

       /**
        * Creates a string representation of the instance data using the 'export'
        * format.
        *
        * @return   The 'export' formatted string.
        */
       public String toStringExport()
       {
          if (isUnknown())
          {
             return "?";
          }

          // quick out (actually this is not just performance but is needed
          // because the processing below will generate an empty string for
          // this case)
          if (value.compareTo(ZERO) == 0)
          {
             return "0";
          }

          String result = toString(getExportFormat(this));

          StringBuilder sb = new StringBuilder(result.trim());

          // some cleanups to match export format
          if (sb.charAt(sb.length() - 1) == getDecimalSeparator())
          {
             // get rid of trailing '.' chars (with no digits to right)
             result = sb.substring(0, (sb.length() - 1));
          }
          else if (sb.indexOf("0") == 0)
          {
             // get rid of leading '0' char (if there are no digits to left)
             // in a positive number
             result = sb.substring(1);
          }
          else if (sb.indexOf("-0") == 0 || sb.indexOf("- ") == 0)
          {
             // get rid of leading '0' or ' ' char (if there are no digits to
             // left) in a negative number
             result = sb.deleteCharAt(1).toString();
          }
          else
          {
             // if nothing else, make sure our trimmed result is what is returned
             result = sb.toString();
          }

          return result;
       }

       /**
        * Creates a string representation of the instance data using the user
        * specified format string (Progress 4GL compatible) or if no such
        * string is provided, the default format of '-&gt;&gt;,&gt;&gt;9.99'.
        *
        * @param    fmt
        *           The Progress 4GL format string or <code>null</code> if the
        *           default format is to be used.
        *
        * @return   The formatted string.
        */
       public String toString(String fmt)
       {
          return toString(fmt, true);
       }

       /**
        * Creates a string representation of the instance data using the user
        * specified format string (Progress 4GL compatible) or if no such
        * string is provided, the default format of '-&gt;&gt;,&gt;&gt;9.99'.
        *
        * @param    fmt
        *           The Progress 4GL format string or <code>null</code> if the
        *           default format is to be used.
        * @param    rightAlign
        *           Pad on the left to the full size of the format string if the
        *           format has '&gt;' characters that match the positions of
        *           leading zeroes.
        *
        * @return   The formatted string.
        */
       public String toString(String fmt, boolean rightAlign)
       {
          return toString(fmt, rightAlign, false);
       }

       /**
        * Creates a string representation of the instance data using the user
        * specified format string (Progress 4GL compatible) or if no such
        * string is provided, the default format of '-&gt;&gt;,&gt;&gt;9.99'.
        *
        * @param    fmt
        *           The Progress 4GL format string or <code>null</code> if the
        *           default format is to be used.
        * @param    rightAlign
        *           Pad on the left to the full size of the format string if the
        *           format has '&gt;' characters that match the positions of
        *           leading zeroes.
        * @param    forceNegative
        *           Flag indicating the forcing including the negative sign value
        *           in the case when the real value is 0.
        *
        * @return   The formatted string.
        */
       public String toString(String fmt, boolean rightAlign, boolean forceNegative)
       {
          if (isUnknown())
          {
             return "?";
          }

          if (fmt == null)
          {
             fmt = getDefaultFormat(this);
          }

          char   groupSep = getGroupSeparator();
          char   decSep   = getDecimalSeparator();
          String full     = getDecimalFormat().format(value.abs());
          int    pos      = full.indexOf(decSep);

          String left;
          String right;

          if (pos < 0)
          {
             // no decimal point
             left  = full;
             right = "";
          }
          else
          {
             // standard case
             left  = full.substring(0, pos);
             right = full.substring(pos + 1);
          }

          boolean negative = forceNegative ? true : (value.signum() < 0);
          String result = toString(left, right, negative, fmt, rightAlign);

          // possible missing sign or left side overflow
          if (result.indexOf('?') == 0)
          {
             // short-circuit processing and return immediately
             if (StringHelper.repeatChar('?', fmt.length()).equals(result))
             {
                return result;
             }
          }

          int decPt    = result.indexOf(decSep);
          int expected = right.length();
          int size     = 0;

          if (decPt >= 0)
          {
             int len = result.length();

             if (decPt < len - 1)
             {
                int  i = decPt + 1;
                char c = result.charAt(i);

                // calculate the total number of chars that was output on the
                // right side of the decimal point
                while ((c >='0' && c <= '9') || c == groupSep)
                {
                   // only count digits
                   if (c != groupSep)
                   {
                      size++;
                   }

                   i++;

                   if (i == len)
                      break;

                   c = result.charAt(i);
                }
             }
             else
             {
                expected = 0;
             }
          }

          // unfortunately, decimals MAY HAVE an ADDITIONAL rounding step based
          // on whether there are unprocessed digits left on the right side
          if (size < expected)
          {
             BigDecimal bd = this.value.setScale(size, RoundingMode.HALF_UP);
             negative = forceNegative ? true : (bd.signum() < 0);

             // redo everything again, with the value already rounded to the
             // right place
             full = getDecimalFormat().format(bd.abs());
             pos = full.indexOf(decSep);

             if (pos < 0)
             {
                // no decimal point --> this can happen in a case like 9999.995
                // and a format string of "$>,>>9.99", where expected will be 3
                // and size will be 2, thus the newVal is 10000.00, full is
                // "10000" and pos is thus -1
                left = full;
                right = "";
             }
             else
             {
                // standard case
                left = full.substring(0, pos);
                right = full.substring(pos + 1);
             }

             result = toString(left, right, negative, fmt, rightAlign);
          }

          return result;
       }

       /**
        * Obtain the length (in bytes) of this BDT will use when serialized.
        *
        * @return  the length of this BDT will use when serialized.
        */
       @Override
       public int getSize()
       {
          if (isUnknown() || value.compareTo(new BigDecimal(0)) == 0)
          {
             return 1;
          }

          String asStr = value.toPlainString();
          int significant = asStr.length();
          if (asStr.indexOf('.') >= 0)
          {
             // count trailing 0-s
             while (asStr.charAt(significant - 1) == '0')
             {
                -- significant;
             }
             -- significant; //count out the '.'
          }
          if (asStr.charAt(0) == '-')
          {
             -- significant; //count out the '-'
             if (asStr.charAt(1) == '0')
             {
                -- significant; //count out the heading '0'
             }
          }
          else if (asStr.charAt(0) == '0')
          {
             -- significant; //count out the heading '0'
          }

          return 2 + (significant + 1) / 2;
       }

       /**
        * Replacement for the default object reading method. The latest
        * state is read from the input source.
        *
        * @param    in
        *           The input source from which fields will be restored.
        *
        * @throws   IOException
        *           In case of I/O errors.
        * @throws   ClassNotFoundException
        *           If payload can't be instantiated.
        */
       public void readExternal(ObjectInput in)
       throws IOException,
              ClassNotFoundException
       {
          boolean unknown = in.readBoolean();
          scale = in.readInt();
          if (!unknown)
          {
             value = (BigDecimal) in.readObject();
          }
       }

       /**
        * Replacement for the default object writing method. The latest
        * state is written to the output destination.
        *
        * @param    out
        *           The output destination to which fields will be saved.
        *
        * @throws   IOException
        *           In case of I/O errors.
        */
       public void writeExternal(ObjectOutput out)
       throws IOException
       {
          boolean unknown = (isUnknown());
          out.writeBoolean(unknown);
          out.writeInt(scale);
          if (!unknown)
          {
             out.writeObject(value);
          }
       }

       /**
        * Worker method for setBits, this copies the bit representation of an
        * integer to a location with a given number of bits within the current integer variable.
        *
        * @param    data
        *           The bit representation of an integer to be copied.
        * @param    pos
        *           The 1-based index position to set, must be &gt; 0.
        * @param    len
        *           The number of bytes to take into consideration when performing the operation.
        */
       public void setBitsWorker(double data, double pos, double len)
       {
          // TODO Implement actual logic for decimal
          // throw error: Incompatible data types in expression or assignment(223)
       }

       /**
        * Worker method for getBits, this returns the number of bits from the
        * given position as an <code>integer</code>.
        *
        * @param  pos
        *         The 1-based index position to set, must be &gt; 0.
        * @param  len
        *         The number of bits to take into consideration when performing the operation.
        *
        * @return The number of bits from the given position as an <code>integer</code>.
        */
       public integer getBitsWorker(double pos, double len)
       {
          // TODO Implement actual logic for decimal
          // throw error: Incompatible data types in expression or assignment(223)
          return new integer();
       }

       /**
        * Test if the given input would cause an overflow error in the 4GL and raise that
        * error if needed.
        *
        * @param    bd
        *           The input to test.  It should already have been scaled to the target level.
        * @param    normal
        *           <code>true</code> if all trailing zeroes have been removed already.
        * @param    raise
        *           <code>true</code> if the error should be raised, <code>false</code> to
        *           bypass error generation and simply just return the calculated result.
        *
        * @return   <code>true</code> if an overflow would occur (the return in this case won't
        *           be seen unless silent error mode is on).  <code>false</code> otherwise.
        */
       public boolean isOverflow(BigDecimal bd, boolean normal, boolean raise)
       {
          boolean fail = false;

          int total = bd.precision();
          int fract = bd.scale();
          int left  = total - fract;

          // negative scale case
          if (fract < 0)
          {
             // there are no digits to the right of the decimal point but there are some
             // extra digits on the left (minus a minus is plus) => unscaled * 10 ^ -scale
             if (left > 50)
             {
                fail = true;
             }
          }
          else
          {
             // check for overflow
             if (left > 50 || (left > 40 && (total > 50)))
             {
                // if truncation occurred, it may have left trailing zeros which may have
                // caused us to detect overflow unnecessarily, use recursion to solve this;
                // by doing it this more complex way, we avoid the use of stripTrailingZeros
                // except in very rare cases; this code is in a performance sensitive path
                // so optimization is important (otherwise we could have just universally
                // stripped the trailing zeroes on entry to this method)
                fail = (normal || isOverflow(bd.stripTrailingZeros(), true, raise));
             }
          }

          if (fail && raise)
          {
             ErrorManager.recordOrThrowError(536, "Decimal number is too large");
          }

          return fail;
       }

       /**
        * Returns the value of this instance as a <code>double</code>.  Loss
        * of precision may occur in this conversion, if the current value cannot
        * be represented in a <code>double</code>.  This is the same as {@link #doubleValue}.
        * <p>
        * If this instance represents the <code>unknown value</code>, the
        * returned <code>double</code> will be set to <code>NaN</code>.
        *
        * @return   The value cast into a <code>double</code>.
        */
       double getValue()
       {
          return doubleValue();
       }

       /**
        * Get the default initialization for an extent variable of this type.
        *
        * @return   See above.
        */
       @Override
       protected BaseDataType instantiateDefaultExtent()
       {
          return new decimal(0);
       }

       /**
        * Direct-assign the specified non-fractional number (string representation), using the given
        * scale.  After a {@link BigDecimal} instance is created, the value is divided by
        * {@code 10^scale}.  This instance's {@link #scale} value will not be changed.
        *
        * @param    num
        *           The non-fractional number.
        * @param    scale
        *           The fractional scale.
        */
       void setValue(String num, int scale)
       {
          BigDecimal bd = new BigDecimal(num);
          bd.setScale(scale);
          bd = bd.divide(new BigDecimal("1e" + scale));

          this.value = bd;
       }

       /**
        * Set the value of this instance from the given numeric value.  A loss
        * of precision will occur in this conversion if the passed value has
        * more significant digits than this instance's current scale.  The
        * resulting value will be rounded to the proper number of digits.
        * <p>
        * If the passed-in value is <code>null</code>, the instance will
        * represent the <code>unknown value</code>.  If this instance previously
        * represented the <code>unknown value</code> and <code>null</code> is not
        * passed, the <code>unknown value</code> state will be
        * <code>false</code>.
        *
        * @param    value
        *           The value to be represented in this instance.
        */
       private void setValue(BigDecimal value)
       {
          if (value == null)
          {
             if (this.value != null)
             {
                checkUndoable(true);
             }

             this.value = null;
             return;
          }

          if (isAssignDirect())
          {
             this.value = value;
             return;
          }

          BigDecimal bd = null;

          if (scale == value.scale())
          {
             // it is safe to assign reference because BigDecimal is immutable
             bd = value;
          }
          else
          {
             // preserve original scale instead of picking up new one
             bd = value.setScale(scale, RoundingMode.HALF_UP);
          }

          if (!isOverflow(bd, false, true))
          {
             if (this.value == null || this.value.compareTo(bd) != 0)
             {
                checkUndoable(true);
             }

             this.value = bd;
          }
       }

       /**
        * Set the value of this instance from a Progress-formatted string.  A
        * loss of precision will occur in this conversion if the passed value has
        * more significant digits than the setting of the <code>scale</code>
        * instance member of this object.  The resulting value will be rounded
        * to the proper number of digits.
        * <p>
        * If the passed-in value is <code>null</code>, the instance will
        * represent the <code>unknown value</code>.  If this instance previously
        * represented the <code>unknown value</code> and <code>null</code> is not
        * passed, the <code>unknown value</code> state will be
        * <code>false</code>.
        *
        * @param    value
        *           The value to be represented in this instance.
        */
       private void setValue(String value)
       {
          if (value == null)
          {
             if (this.value != null)
             {
                checkUndoable(true);
             }

             this.value = null;
             return;
          }

          // this will also check for overflows, so it is safe to assign the result below
          String dstr = parseDecimal(value, false);

          if (dstr != null)
          {
             BigDecimal newVal = new BigDecimal(dstr).setScale(scale, RoundingMode.HALF_UP);

             if (this.value == null || this.value.compareTo(newVal) != 0)
             {
                checkUndoable(true);
             }

             this.value = newVal;
          }
       }

       /**
        * Set the value of this instance from 64-bit signed integer.  No loss of precision will occur. This
        * method directly avoids transforming the value into a Java double, which might have caused a loss of
        * precision since not all possible signed 64-bit integer values can be safely represented in a 64-bit
        * double.
        *
        * @param    value
        *           The value to be represented in this instance.
        */
       private void setValue(long value)
       {
          BigDecimal newVal = new BigDecimal(value).setScale(scale, RoundingMode.HALF_UP);

          if (this.value == null || this.value.compareTo(newVal) != 0)
          {
             checkUndoable(true);
          }

          this.value = newVal;
       }

       /**
        * Set the value of this instance as a <code>double</code>.  A loss
        * of precision will occur in this conversion if the passed value has
        * more significant digits than the setting of the <code>precision</code>
        * instance member of this object.  The resulting value will be rounded
        * to the proper number of digits.
        * <p>
        * If the passed-in value is <code>NaN</code> or infinity (positive or negative), the
        * instance will represent 0, which is how the 4GL deals with these values when reading
        * them directly from memory (e.g. when passed back via a native API call).  Actually
        * with infinity, the 4GL goes into an infinite loop and needs to be hard-killed, but
        * this code won't duplicate that "feature".
        *
        * @param    value
        *           The value to be represented in this instance.
        */
       private void setValue(double value)
       {
          // handle boundary conditions (BigDecimal won't like these as inputs)
          if (Double.isNaN(value) || Double.isInfinite(value))
          {
             value = 0;
          }

          BigDecimal bd = new BigDecimal(value).setScale(scale, RoundingMode.HALF_UP);

          if (!isOverflow(bd, false, true))
          {
             if (this.value == null || this.value.compareTo(bd) != 0)
             {
                checkUndoable(true);
             }

             this.value = bd;
          }
       }

       /**
        * Helper to reset the associated instance's precision to a prior value
        * based on a notification that a particular scope has exited.  This
        * scope notification support is provided by
        * {@link TransactionManager#registerFinalizable} and the {@link
        * Finalizable} interface.
        */
       private class PrecisionResetter
       implements Finalizable
       {
          /** The precision to which the containing instance will be reset. */
          int oldPrecision = 0;

          /**
           * Construct an instance which can reset the containing instance's
           * precision when a notification is received.  See
           * {@link decimal#setTemporaryPrecision}.
           *
           * @param    oldPrecision
           *           The precision to which the containing instance must be
           *           reset.
           */
          PrecisionResetter(int oldPrecision)
          {
             this.oldPrecision = oldPrecision;
          }

          /**
           * Processes the end of scope notification and resets the associated
           * instance's precision.  This notification is provided by the
           * {@link TransactionManager}.
           */
          public void finished()
          {
             setPrecision(oldPrecision);
          }

          /**
           * Provides a notification that the external program scope in which the object is registered is
           * is being deleted and the object's reference will be lost after this method is called.
           * <p>
           * This is a no-op in this case.
           */
          @Override
          public void deleted()
          {
             // no-op
          }

          /**
           * Processes the iterate notification provided by the {@link
           * TransactionManager}.  This implementation does nothing.
           */
          public void iterate()
          {
          }

          /**
           * Processes the retry notification provided by the {@link
           * TransactionManager}.  This implementation does nothing.
           */
          public void retry()
          {
          }
       }

       /**
        * Stores global data relating to the state of the current context.
        */
       private static class WorkArea
       {
          /**
           * Stores the context-specific default format string (which honors
           * the user's defined group and decimal separators). Initialization
           * is delegated to the implementation class.
           */
          private String defFmt = null;

          /** The default decimal formatter. */
          private DecimalFormat decimalFmt = null;
       }

       /**
        * Simple container that stores and returns a context-local instance of
        * the global work area.
        */
       private static class ContextContainer
       extends ContextLocal<WorkArea>
       {
          /**
           * Initializes the work area, the first time it is requested within a
           * new context.
           *
           * @return   The newly instantiated work area.
           */
          protected WorkArea initialValue()
          {
             return new WorkArea();
          }
       }

       /**
        * Simple command line harness to test the toString() method.
        *
        * @param   args
        *          1. numeric value
        *          2. format string
        */
       public static void main(String[] args)
       {
          try
          {
             decimal d = new decimal(args[0]);

             System.out.println("Formatted = '" + d.toString(args[1]) +
                                "', Export = " + d.toStringExport());
          }
          catch (Exception exc)
          {
             LOG.severe("", exc);
          }
       }
    }