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);
      }
   }
}