/* * eXist Open Source Native XML Database * Copyright (C) 2001-2007 The eXist Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Id$ */ package org.exist.xquery.value; import org.exist.util.FastStringBuffer; import org.exist.util.FloatingPointConverter; import org.exist.xquery.Constants; import org.exist.xquery.ErrorCodes; import org.exist.xquery.XPathException; import java.math.BigDecimal; import java.text.Collator; public class DoubleValue extends NumericValue { // m × 2^e, where m is an integer whose absolute value is less than 2^53, // and e is an integer between -1075 and 970, inclusive. // In addition also -INF, +INF and NaN. public final static DoubleValue ZERO = new DoubleValue(0.0E0); public final static DoubleValue POSITIVE_INFINITY = new DoubleValue(Double.POSITIVE_INFINITY); public final static DoubleValue NEGATIVE_INFINITY = new DoubleValue(Double.NEGATIVE_INFINITY); public final static DoubleValue NaN = new DoubleValue(Double.NaN); private double value; public DoubleValue(double value) { this.value = value; } public DoubleValue(AtomicValue otherValue) throws XPathException { this(otherValue.getStringValue()); } public DoubleValue(String stringValue) throws XPathException { try { if ("INF".equals(stringValue)) { value = Double.POSITIVE_INFINITY; } else if ("-INF".equals(stringValue)) { value = Double.NEGATIVE_INFINITY; } else if ("NaN".equals(stringValue)) { value = Double.NaN; } else { value = Double.parseDouble(stringValue); } } catch (final NumberFormatException e) { throw new XPathException(ErrorCodes.FORG0001, "cannot construct " + Type.getTypeName(this.getItemType()) + " from '" + stringValue + "'"); } } /* (non-Javadoc) * @see org.exist.xquery.value.AtomicValue#getType() */ public int getType() { return Type.DOUBLE; } public String getStringValue() { final FastStringBuffer sb = new FastStringBuffer(20); //0 is a dummy parameter FloatingPointConverter.appendDouble(sb, value).getNormalizedString(0); return sb.toString(); } public double getValue() { return value; } public void setValue(double val) { value = val; } public boolean hasFractionalPart() { if (isNaN()) { return false; } if (isInfinite()) { return false; } return new DecimalValue(new BigDecimal(value)).hasFractionalPart(); } public Item itemAt(int pos) { return pos == 0 ? this : null; } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#isNaN() */ public boolean isNaN() { return Double.isNaN(value); } public boolean isInfinite() { return Double.isInfinite(value); } public boolean isZero() { return Double.compare(Math.abs(value), 0.0) == Constants.EQUAL; } public boolean isNegative() { return (Double.compare(value, 0.0) < Constants.EQUAL); } public boolean isPositive() { return (Double.compare(value, 0.0) > Constants.EQUAL); } /* (non-Javadoc) * @see org.exist.xquery.value.AtomicValue#convertTo(int) */ public AtomicValue convertTo(int requiredType) throws XPathException { switch (requiredType) { case Type.ATOMIC: case Type.ITEM: case Type.NUMBER: case Type.DOUBLE: return this; case Type.FLOAT: //if (Float.compare(value, 0.0f) && (value < Float.MIN_VALUE || value > Float.MAX_VALUE) // throw new XPathException("Value is out of range for type xs:float"); //return new FloatValue((float) value); return new FloatValue((float) value); case Type.UNTYPED_ATOMIC: return new UntypedAtomicValue(getStringValue()); case Type.STRING: return new StringValue(getStringValue()); case Type.DECIMAL: if (isNaN()) { throw new XPathException(ErrorCodes.FORG0001, "can not convert " + Type.getTypeName(getType()) + "('" + getStringValue() + "') to " + Type.getTypeName(requiredType)); } if (isInfinite()) { throw new XPathException(ErrorCodes.FORG0001, "can not convert " + Type.getTypeName(getType()) + "('" + getStringValue() + "') to " + Type.getTypeName(requiredType)); } return new DecimalValue(new BigDecimal(value)); case Type.INTEGER: case Type.NON_POSITIVE_INTEGER: case Type.NEGATIVE_INTEGER: case Type.LONG: case Type.INT: case Type.SHORT: case Type.BYTE: case Type.NON_NEGATIVE_INTEGER: case Type.UNSIGNED_LONG: case Type.UNSIGNED_INT: case Type.UNSIGNED_SHORT: case Type.UNSIGNED_BYTE: case Type.POSITIVE_INTEGER: if (isNaN()) { throw new XPathException(ErrorCodes.FORG0001, "can not convert " + Type.getTypeName(getType()) + "('" + getStringValue() + "') to " + Type.getTypeName(requiredType)); } if (Double.isInfinite(value)) { throw new XPathException(ErrorCodes.FORG0001, "can not convert " + Type.getTypeName(getType()) + "('" + getStringValue() + "') to " + Type.getTypeName(requiredType)); } if (value > Integer.MAX_VALUE) { throw new XPathException(ErrorCodes.FOCA0003, "Value is out of range for type xs:integer"); } return new IntegerValue((long) value, requiredType); case Type.BOOLEAN: return new BooleanValue(this.effectiveBooleanValue()); default: throw new XPathException(ErrorCodes.FORG0001, "cannot cast '" + Type.getTypeName(this.getItemType()) + "(\"" + getStringValue() + "\")' to " + Type.getTypeName(requiredType)); } } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#getDouble() */ public double getDouble() throws XPathException { return value; } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#getInt() */ public int getInt() throws XPathException { return (int) Math.round(value); } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#getLong() */ public long getLong() throws XPathException { return Math.round(value); } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#ceiling() */ public NumericValue ceiling() throws XPathException { return new DoubleValue(Math.ceil(value)); } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#floor() */ public NumericValue floor() throws XPathException { return new DoubleValue(Math.floor(value)); } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#round() */ public NumericValue round() throws XPathException { if (Double.isNaN(value) || Double.isInfinite(value) || value == 0.0) { return this; } if (value >= -0.5 && value < 0.0) { return new DoubleValue(-0.0); } if (value > Long.MIN_VALUE && value < Long.MAX_VALUE) { return new DoubleValue(Math.round(value)); } //too big return original value unchanged return this; } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#round(org.exist.xquery.value.IntegerValue) */ public NumericValue round(IntegerValue precision) throws XPathException { if (precision == null) { return round(); } if (Double.isNaN(value) || Double.isInfinite(value) || value == 0.0) { return this; } /* use the decimal rounding method */ return (DoubleValue) ((DecimalValue) convertTo(Type.DECIMAL)).round(precision).convertTo(Type.DOUBLE); } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#minus(org.exist.xquery.value.NumericValue) */ public ComputableValue minus(ComputableValue other) throws XPathException { if (Type.subTypeOf(other.getType(), Type.DOUBLE)) { return new DoubleValue(value - ((DoubleValue) other).value); } else { return minus((ComputableValue) other.convertTo(getType())); } } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#plus(org.exist.xquery.value.NumericValue) */ public ComputableValue plus(ComputableValue other) throws XPathException { if (Type.subTypeOf(other.getType(), Type.DOUBLE)) { return new DoubleValue(value + ((DoubleValue) other).value); } else { return plus((ComputableValue) other.convertTo(getType())); } } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#mult(org.exist.xquery.value.NumericValue) */ public ComputableValue mult(ComputableValue other) throws XPathException { switch (other.getType()) { case Type.DOUBLE: return new DoubleValue(value * ((DoubleValue) other).value); case Type.DAY_TIME_DURATION: case Type.YEAR_MONTH_DURATION: return other.mult(this); default: return mult((ComputableValue) other.convertTo(getType())); } } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#div(org.exist.xquery.value.NumericValue) */ public ComputableValue div(ComputableValue other) throws XPathException { if (Type.subTypeOf(other.getType(), Type.NUMBER)) { //Positive or negative zero divided by positive or negative zero returns NaN. if (this.isZero() && ((NumericValue) other).isZero()) { return NaN; } //A negative number divided by positive zero returns -INF. if (this.isNegative() && ((NumericValue) other).isZero() && ((NumericValue) other).isPositive()) { return NEGATIVE_INFINITY; } //A negative number divided by positive zero returns -INF. if (this.isNegative() && ((NumericValue) other).isZero() && ((NumericValue) other).isNegative()) { return POSITIVE_INFINITY; } //Division of Positive by negative zero returns -INF and INF, respectively. if (this.isPositive() && ((NumericValue) other).isZero() && ((NumericValue) other).isNegative()) { return NEGATIVE_INFINITY; } if (this.isPositive() && ((NumericValue) other).isZero() && ((NumericValue) other).isPositive()) { return POSITIVE_INFINITY; } //Also, INF or -INF divided by INF or -INF returns NaN. if (this.isInfinite() && ((NumericValue) other).isInfinite()) { return NaN; } } if (Type.subTypeOf(other.getType(), Type.DOUBLE)) { return new DoubleValue(value / ((DoubleValue) other).value); } else { return div((ComputableValue) other.convertTo(getType())); } } public IntegerValue idiv(NumericValue other) throws XPathException { final ComputableValue result = div(other); return new IntegerValue(((IntegerValue) result.convertTo(Type.INTEGER)).getLong()); /* if (Type.subTypeOf(other.getType(), Type.DOUBLE)) { double result = value / ((DoubleValue) other).value; if (result == Double.NaN || result == Double.POSITIVE_INFINITY || result == Double.NEGATIVE_INFINITY) throw new XPathException("illegal arguments to idiv"); return new IntegerValue(new BigDecimal(result).toBigInteger(), Type.INTEGER); } throw new XPathException("idiv called with incompatible argument type: " + getType() + " vs " + other.getType()); */ } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#mod(org.exist.xquery.value.NumericValue) */ public NumericValue mod(NumericValue other) throws XPathException { if (Type.subTypeOf(other.getType(), Type.DOUBLE)) { return new DoubleValue(value % ((DoubleValue) other).value); } else { return mod((NumericValue) other.convertTo(getType())); } } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#negate() */ public NumericValue negate() throws XPathException { return new DoubleValue(-value); } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#abs() */ public NumericValue abs() throws XPathException { return new DoubleValue(Math.abs(value)); } /* (non-Javadoc) * @see org.exist.xquery.value.NumericValue#max(org.exist.xquery.value.AtomicValue) */ public AtomicValue max(Collator collator, AtomicValue other) throws XPathException { if (Type.subTypeOf(other.getType(), Type.DOUBLE)) { return new DoubleValue(Math.max(value, ((DoubleValue) other).value)); } else { return new DoubleValue( Math.max(value, ((DoubleValue) other.convertTo(Type.DOUBLE)).value)); } } public AtomicValue min(Collator collator, AtomicValue other) throws XPathException { if (Type.subTypeOf(other.getType(), Type.DOUBLE)) { return new DoubleValue(Math.min(value, ((DoubleValue) other).value)); } else { return new DoubleValue( Math.min(value, ((DoubleValue) other.convertTo(Type.DOUBLE)).value)); } } /* (non-Javadoc) * @see org.exist.xquery.value.Item#conversionPreference(java.lang.Class) */ public int conversionPreference(Class<?> javaClass) { if (javaClass.isAssignableFrom(DoubleValue.class)) { return 0; } if (javaClass == Long.class || javaClass == long.class) { return 3; } if (javaClass == Integer.class || javaClass == int.class) { return 4; } if (javaClass == Short.class || javaClass == short.class) { return 5; } if (javaClass == Byte.class || javaClass == byte.class) { return 6; } if (javaClass == Double.class || javaClass == double.class) { return 1; } if (javaClass == Float.class || javaClass == float.class) { return 2; } if (javaClass == String.class) { return 7; } if (javaClass == Boolean.class || javaClass == boolean.class) { return 8; } if (javaClass == Object.class) { return 20; } return Integer.MAX_VALUE; } /* (non-Javadoc) * @see org.exist.xquery.value.Item#toJavaObject(java.lang.Class) */ @SuppressWarnings("unchecked") public <T> T toJavaObject(final Class<T> target) throws XPathException { if (target.isAssignableFrom(DoubleValue.class)) { return (T) this; } else if (target == Double.class || target == double.class) { return (T) Double.valueOf(value); } else if (target == Float.class || target == float.class) { return (T) new Float(value); } else if (target == Long.class || target == long.class) { return (T) Long.valueOf(((IntegerValue) convertTo(Type.LONG)).getValue()); } else if (target == Integer.class || target == int.class) { final IntegerValue v = (IntegerValue) convertTo(Type.INT); return (T) Integer.valueOf((int) v.getValue()); } else if (target == Short.class || target == short.class) { final IntegerValue v = (IntegerValue) convertTo(Type.SHORT); return (T) Short.valueOf((short) v.getValue()); } else if (target == Byte.class || target == byte.class) { final IntegerValue v = (IntegerValue) convertTo(Type.BYTE); return (T) Byte.valueOf((byte) v.getValue()); } else if (target == String.class) { return (T) getStringValue(); } else if (target == Boolean.class) { return (T) Boolean.valueOf(effectiveBooleanValue()); } throw new XPathException( "cannot convert value of type " + Type.getTypeName(getType()) + " to Java object of type " + target.getName()); } /** * size writen by {link #serialize(short, boolean)} */ public int getSerializedSize() { return 1 + 8; } /* (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(Object o) { final AtomicValue other = (AtomicValue) o; if (Type.subTypeOf(other.getType(), Type.DOUBLE)) { return Double.compare(value, ((DoubleValue) other).value); } else { return getType() < other.getType() ? Constants.INFERIOR : Constants.SUPERIOR; } } @Override public int hashCode() { return Double.valueOf(value).hashCode(); } }