Package client.net.sf.saxon.ce.value

Source Code of client.net.sf.saxon.ce.value.DecimalValue

package client.net.sf.saxon.ce.value;

import client.net.sf.saxon.ce.om.StandardNames;
import client.net.sf.saxon.ce.trans.Err;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.tree.util.FastStringBuffer;
import client.net.sf.saxon.ce.type.*;
import com.google.gwt.regexp.shared.RegExp;

import java.math.BigDecimal;
import java.math.BigInteger;


/**
* A decimal value
*/

public class DecimalValue extends NumericValue {

    public static final int DIVIDE_PRECISION = 18;

    private BigDecimal value;

    public static final BigDecimal BIG_DECIMAL_ONE_MILLION = BigDecimal.valueOf(1000000);
    public static final BigDecimal BIG_DECIMAL_MAX_INT = BigDecimal.valueOf(Integer.MAX_VALUE);
    public static final BigDecimal BIG_DECIMAL_MIN_INT = BigDecimal.valueOf(Integer.MIN_VALUE);

    public static final DecimalValue ZERO = new DecimalValue(BigDecimal.valueOf(0));
    public static final DecimalValue ONE = new DecimalValue(BigDecimal.valueOf(1));
    public static final DecimalValue TWO = new DecimalValue(BigDecimal.valueOf(2));
    public static final DecimalValue THREE = new DecimalValue(BigDecimal.valueOf(3));

    /**
    * Constructor supplying a BigDecimal
    * @param value the value of the DecimalValue
    */

    public DecimalValue(BigDecimal value) {
      this.value = stripTrailingZeros(value);
        //this.value = value.stripTrailingZeros();
        //this.value = value; // GWT bug 6110 prevents stripTrailingZeros (2011-03-14)
        typeLabel = BuiltInAtomicType.DECIMAL;
    }
   
    /**
     *
     * @param value a BigDecimal that may have trailing zeros
     * @return a value that has trailing zeros trimmed
     * Used instead of BigDecimal's own method as this has a
     * GWT bug 6110 reported causing infinite recursion
     */
   
    public static BigDecimal stripTrailingZeros(BigDecimal value) {
      String str = value.toString();
     
      int dotPos  = str.indexOf('.');
      if (dotPos < 0) {
        return value;
      }
      int expPos = str.indexOf('E');
      if (expPos > -1) {
        return value; // e.g. 12.3E+7
      }
      int lastZeroPos = -1;
      char zeroCh = '0';
      for (int i = str.length() -1; i > -1; i--) {
        char ch = str.charAt(i);
        if (ch != zeroCh) break;
        lastZeroPos = i;
      }
      if(lastZeroPos > -1) {
        if (lastZeroPos - dotPos == 1) lastZeroPos = dotPos;
        String strippedValue = str.substring(0, lastZeroPos);
        return new BigDecimal(strippedValue);
      } else {
        return value;
      }
    }

    private static final RegExp decimalPattern = RegExp.compile("(\\-|\\+)?((\\.[0-9]+)|([0-9]+(\\.[0-9]*)?))");

    /**
     * Factory method to construct a DecimalValue from a string
     * @param in the value of the DecimalValue
     * @return the required DecimalValue if the input is valid, or a ValidationFailure encapsulating the error
     * message if not.
     */

    public static ConversionResult makeDecimalValue(CharSequence in) {

        try {
            FastStringBuffer digits = new FastStringBuffer(in.length());
            int scale = 0;
            int state = 0;
            // 0 - in initial whitespace; 1 - after sign
            // 3 - after decimal point; 5 - in final whitespace
            boolean foundDigit = false;
            int len = in.length();
            for (int i=0; i<len; i++) {
                char c = in.charAt(i);
                switch (c) {
                    case ' ':
                    case '\t':
                    case '\r':
                    case '\n':
                        if (state != 0) {
                            state = 5;
                        }
                        break;
                    case '+':
                        if (state != 0) {
                            throw new NumberFormatException("unexpected sign");
                        }
                        state = 1;
                        break;
                    case '-':
                        if (state != 0) {
                            throw new NumberFormatException("unexpected sign");
                        }
                        state = 1;
                        digits.append(c);
                        break;
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                        if (state == 0) {
                            state = 1;
                        } else if (state >= 3) {
                            scale++;
                        }
                        if (state == 5) {
                            throw new NumberFormatException("contains embedded whitespace");
                        }
                        digits.append(c);
                        foundDigit = true;
                        break;
                    case '.':
                        if (state == 5) {
                            throw new NumberFormatException("contains embedded whitespace");
                        }
                        if (state >= 3) {
                            throw new NumberFormatException("more than one decimal point");
                        }
                        state = 3;
                        break;
                    default:
                        throw new NumberFormatException("invalid character '" + c + "'");
                }

            }

            if (!foundDigit) {
                throw new NumberFormatException("no digits in value");
            }

            // remove insignificant trailing zeroes
            while (scale > 0) {
                if (digits.charAt(digits.length()-1) == '0') {
                    digits.setLength(digits.length() - 1);
                    scale--;
                } else {
                    break;
                }
            }
            if (digits.length() == 0 || (digits.length() == 1 && digits.charAt(0) == '-')) {
                return DecimalValue.ZERO;
            }
            BigInteger bigInt = new BigInteger(digits.toString());
            BigDecimal bigDec = new BigDecimal(bigInt, scale);
            return new DecimalValue(bigDec);
        } catch (NumberFormatException err) {
            ValidationFailure e = new ValidationFailure(
                    "Cannot convert string " + Err.wrap(Whitespace.trim(in), Err.VALUE) +
                            " to xs:decimal: " + err.getMessage());
            e.setErrorCode("FORG0001");
            return e;
        }                                                                                                           
    }

    /**
     * Test whether a string is castable to a decimal value
     * @param in the string to be tested
     * @return true if the string has the correct format for a decimal
     */

    public static boolean castableAsDecimal(CharSequence in) {
        CharSequence trimmed = Whitespace.trimWhitespace(in);
        return decimalPattern.exec(trimmed.toString()) != null;
    }

    /**
    * Constructor supplying a double
    * @param in the value of the DecimalValue
    */

    public DecimalValue(double in) throws XPathException {
        try {
            BigDecimal d = new BigDecimal(in);
            //value = d.stripTrailingZeros();
            value = d; // GWT bug 6110 prevents stripTrailingZeros (2011-03-14)
        } catch (NumberFormatException err) {
            // Must be a special value such as NaN or infinity
            XPathException e = new XPathException(
                    "Cannot convert double " + Err.wrap(in+"", Err.VALUE) + " to decimal");
            e.setErrorCode("FOCA0002");
            throw e;
        }
        typeLabel = BuiltInAtomicType.DECIMAL;
    }

    /**
    * Constructor supplying a long integer
    * @param in the value of the DecimalValue
    */

    public DecimalValue(long in) {
        value = BigDecimal.valueOf(in);
        typeLabel = BuiltInAtomicType.DECIMAL;
    }

    /**
     * Determine the primitive type of the value. This delivers the same answer as
     * getItemType().getPrimitiveItemType(). The primitive types are
     * the 19 primitive types of XML Schema, plus xs:integer, xs:dayTimeDuration and xs:yearMonthDuration,
     * and xs:untypedAtomic. For external objects, the result is AnyAtomicType.
     */

    public BuiltInAtomicType getPrimitiveType() {
        return BuiltInAtomicType.DECIMAL;
    }


    /**
    * Get the value
    */

    public BigDecimal getDecimalValue() {
        return value;
    }

    /**
     * Get the hashCode. This must conform to the rules for other NumericValue hashcodes
     * @see NumericValue#hashCode
     */

    public int hashCode() {
        BigDecimal round = value.setScale(0, BigDecimal.ROUND_DOWN);
        long value = round.longValue();
        if (value > Integer.MIN_VALUE && value < Integer.MAX_VALUE) {
            return (int)value;
        } else {
            return new Double(getDoubleValue()).hashCode();
        }
    }

    public boolean effectiveBooleanValue() {
        return value.signum() != 0;
    }

    /**
    * Convert to target data type
    */

    public ConversionResult convertPrimitive(BuiltInAtomicType requiredType, boolean validate) {
        switch(requiredType.getFingerprint()) {
        case StandardNames.XS_BOOLEAN:
                // 0.0 => false, anything else => true
            return BooleanValue.get(value.signum()!=0);
        case StandardNames.XS_NUMERIC:
        case StandardNames.XS_DECIMAL:
        case StandardNames.XS_ANY_ATOMIC_TYPE:
            return this;
        case StandardNames.XS_INTEGER:
            return IntegerValue.decimalToInteger(value);
        case StandardNames.XS_DOUBLE:
            return new DoubleValue(value.doubleValue());
        case StandardNames.XS_FLOAT:
            return new FloatValue(value.floatValue());
        case StandardNames.XS_STRING:
            return new StringValue(getStringValueCS());
        case StandardNames.XS_UNTYPED_ATOMIC:
            return new UntypedAtomicValue(getStringValueCS());
        default:
            ValidationFailure err = new ValidationFailure("Cannot convert decimal to " +
                                     requiredType.getDisplayName());
            err.setErrorCode("XPTY0004");
            return err;
        }
    }

    /**
     * Get the value of the item as a CharSequence. This is in some cases more efficient than
     * the version of the method that returns a String.
     */

//    public CharSequence getStringValueCS() {
//        return decimalToString(value, new FastStringBuffer(20));
//    }

    /**
    * Get the value as a String
    * @return a String representation of the value
    */

    public CharSequence getPrimitiveStringValue() {
        return decimalToString(value, new FastStringBuffer(FastStringBuffer.TINY));
    }

    /**
     * Convert a decimal value to a string, using the XPath rules for formatting
     * @param value the decimal value to be converted
     * @param fsb the FastStringBuffer to which the value is to be appended
     * @return the supplied FastStringBuffer, suitably populated
     */

    public static FastStringBuffer decimalToString(BigDecimal value, FastStringBuffer fsb) {
        // Can't use the plain BigDecimal#toString() under JDK 1.5 because this produces values like "1E-5".
        // JDK 1.5 offers BigDecimal#toPlainString() which might do the job directly
        int scale = value.scale();
        if (scale == 0) {
            fsb.append(value.toString());
            return fsb;
        } else if (scale < 0) {
            String s = value.abs().unscaledValue().toString();
            if (s.equals("0")) {
                fsb.append('0');
                return fsb;
            }
            //FastStringBuffer sb = new FastStringBuffer(s.length() + (-scale) + 2);
            if (value.signum() < 0) {
                fsb.append('-');
            }
            fsb.append(s);
            for (int i=0; i<(-scale); i++) {
                fsb.append('0');
            }
            return fsb;
        } else {
            String s = value.abs().unscaledValue().toString();
            if (s.equals("0")) {
                fsb.append('0');
                return fsb;
            }
            int len = s.length();
            //FastStringBuffer sb = new FastStringBuffer(len+1);
            if (value.signum() < 0) {
                fsb.append('-');
            }
            if (scale >= len) {
                fsb.append("0.");
                for (int i=len; i<scale; i++) {
                    fsb.append('0');
                }
                fsb.append(s);
            } else {
                fsb.append(s.substring(0, len-scale));
                fsb.append('.');
                fsb.append(s.substring(len-scale));
            }
            return fsb;
        }
    }

    /**
    * Negate the value
    */

    public NumericValue negate() {
        return new DecimalValue(value.negate());
    }

    /**
    * Implement the XPath floor() function
    */

    public NumericValue floor() {
        return new DecimalValue(value.setScale(0, BigDecimal.ROUND_FLOOR));
    }

    /**
    * Implement the XPath ceiling() function
    */

    public NumericValue ceiling() {
        return new DecimalValue(value.setScale(0, BigDecimal.ROUND_CEILING));
    }

    /**
    * Implement the XPath round() function
    */

    public NumericValue round() {
        // The XPath rules say that we should round to the nearest integer, with .5 rounding towards
        // positive infinity. Unfortunately this is not one of the rounding modes that the Java BigDecimal
        // class supports, so we need different rules depending on the value.

        // If the value is positive, we use ROUND_HALF_UP; if it is negative, we use ROUND_HALF_DOWN (here "UP"
        // means "away from zero")

        switch (value.signum()) {
            case -1:
                return new DecimalValue(value.setScale(0, BigDecimal.ROUND_HALF_DOWN));
            case 0:
                return this;
            case +1:
                return new DecimalValue(value.setScale(0, BigDecimal.ROUND_HALF_UP));
            default:
                // can't happen
                return this;
        }

    }

    /**
    * Implement the XPath round-half-to-even() function
    */

    public NumericValue roundHalfToEven(int scale) {
        BigDecimal scaledValue = value.setScale(scale, BigDecimal.ROUND_HALF_EVEN);
        return new DecimalValue(scaledValue);
    }

    /**
     * Determine whether the value is negative, zero, or positive
     * @return -1 if negative, 0 if zero, +1 if positive, NaN if NaN
     */

    public double signum() {
        return value.signum();
    }

    /**
    * Determine whether the value is a whole number, that is, whether it compares
    * equal to some integer
    */

    public boolean isWholeNumber() {
        return value.scale()==0 ||
               value.compareTo(value.setScale(0, BigDecimal.ROUND_DOWN)) == 0;
    }

    /**
     * Get the absolute value as defined by the XPath abs() function
     * @return the absolute value
     * @since 9.2
     */

    public NumericValue abs() {
        if (value.signum() > 0) {
            return this;
        } else {
            return new DecimalValue(value.negate());
        }
    }

    /**
    * Compare the value to another numeric value
    */

    public int compareTo(Object other) {
        if (other instanceof DecimalValue) {
            // including xs:integer
            return value.compareTo(((DecimalValue)other).value);
        } else if (other instanceof FloatValue) {
            try {
                return ((FloatValue)convertPrimitive(BuiltInAtomicType.FLOAT, true).asAtomic()).compareTo(other);
            } catch (XPathException err) {
                throw new AssertionError("Conversion of decimal to float should never fail");
            }
        } else {
            return super.compareTo(other);
        }
    }

    /**
     * Compare the value to a long
     * @param other the value to be compared with
     * @return -1 if this is less, 0 if this is equal, +1 if this is greater or if this is NaN
     */

    public int compareTo(long other) {
        if (other == 0) {
            return value.signum();
        }
        return value.compareTo(BigDecimal.valueOf(other));
    }

}

// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.

TOP

Related Classes of client.net.sf.saxon.ce.value.DecimalValue

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.