001: package net.sf.saxon.value;
002:
003: import net.sf.saxon.Err;
004: import net.sf.saxon.expr.Token;
005: import net.sf.saxon.expr.XPathContext;
006: import net.sf.saxon.om.FastStringBuffer;
007: import net.sf.saxon.trans.DynamicError;
008: import net.sf.saxon.trans.XPathException;
009: import net.sf.saxon.type.*;
010:
011: import java.math.BigDecimal;
012: import java.util.regex.Matcher;
013:
014: /**
015: * A numeric (double precision floating point) value
016: */
017:
018: public final class DoubleValue extends NumericValue {
019:
020: public static final DoubleValue ZERO = new DoubleValue(0.0);
021: public static final DoubleValue NaN = new DoubleValue(Double.NaN);
022:
023: private double value;
024:
025: /**
026: * Constructor supplying a string
027: * @throws ValidationException if the string does not have the correct lexical form for a double.
028: * Note that the error will contain no error code or context information.
029: */
030:
031: public DoubleValue(CharSequence val) throws ValidationException {
032: try {
033: value = Value.stringToNumber(val);
034: } catch (NumberFormatException e) {
035: throw new ValidationException("Cannot convert string "
036: + Err.wrap(val, Err.VALUE) + " to a double");
037: }
038: }
039:
040: /**
041: * Constructor supplying a double
042: * @param value the value of the NumericValue
043: */
044:
045: public DoubleValue(double value) {
046: this .value = value;
047: }
048:
049: /**
050: * Return this numeric value as a double
051: * @return the value as a double
052: */
053:
054: public double getDoubleValue() {
055: return value;
056: }
057:
058: /**
059: * Get the hashCode. This must conform to the rules for other NumericValue hashcodes
060: * @see NumericValue#hashCode
061: */
062:
063: public int hashCode() {
064: if (value > Integer.MIN_VALUE && value < Integer.MAX_VALUE) {
065: return (int) value;
066: } else {
067: return new Double(value).hashCode();
068: }
069: }
070:
071: /**
072: * Test whether the value is the double/float value NaN
073: */
074:
075: public boolean isNaN() {
076: return Double.isNaN(value);
077: }
078:
079: /**
080: * Get the effective boolean value
081: * @param context
082: * @return the effective boolean value (true unless the value is zero or NaN)
083: */
084: public boolean effectiveBooleanValue(XPathContext context) {
085: return (value != 0.0 && !Double.isNaN(value));
086: }
087:
088: /**
089: * Convert to target data type
090: * @param requiredType an integer identifying the required atomic type
091: * @param context
092: * @return an AtomicValue, a value of the required type
093: */
094:
095: public AtomicValue convertPrimitive(BuiltInAtomicType requiredType,
096: boolean validate, XPathContext context) {
097: switch (requiredType.getPrimitiveType()) {
098: case Type.BOOLEAN:
099: return BooleanValue.get(effectiveBooleanValue(null));
100: case Type.DOUBLE:
101: case Type.NUMBER:
102: case Type.ANY_ATOMIC:
103: case Type.ITEM:
104: return this ;
105: case Type.INTEGER:
106: if (Double.isNaN(value)) {
107: ValidationException err = new ValidationException(
108: "Cannot convert double NaN to an integer");
109: //err.setXPathContext(context);
110: err.setErrorCode("FORG0001");
111: return new ValidationErrorValue(err);
112: }
113: if (Double.isInfinite(value)) {
114: ValidationException err = new ValidationException(
115: "Cannot convert double infinity to an integer");
116: //err.setXPathContext(context);
117: err.setErrorCode("FORG0001");
118: return new ValidationErrorValue(err);
119: }
120: if (value > Long.MAX_VALUE || value < Long.MIN_VALUE) {
121: return new BigIntegerValue(new BigDecimal(value)
122: .toBigInteger());
123: }
124: return new IntegerValue((long) value);
125: case Type.DECIMAL:
126: try {
127: return new DecimalValue(value);
128: } catch (ValidationException e) {
129: return new ValidationErrorValue(e);
130: }
131: case Type.FLOAT:
132: return new FloatValue((float) value);
133: case Type.STRING:
134: return new StringValue(getStringValueCS());
135: case Type.UNTYPED_ATOMIC:
136: return new UntypedAtomicValue(getStringValueCS());
137: default:
138: ValidationException err = new ValidationException(
139: "Cannot convert double to "
140: + requiredType.getDisplayName());
141: err.setErrorCode("XPTY0004");
142: err.setIsTypeError(true);
143: return new ValidationErrorValue(err);
144: }
145: }
146:
147: static java.util.regex.Pattern nonExponentialPattern = java.util.regex.Pattern
148: .compile("(-?[0-9])([0-9]+?)(0*)\\.([0-9]*)");
149:
150: /**
151: * Convert the double to a string according to the XPath 2.0 rules
152: * @return the string value
153: */
154: public String getStringValue() {
155: return doubleToString(value, Double.toString(value));
156: }
157:
158: /**
159: * Internal method used for conversion of a double to a string
160: * @param value the actual value
161: * @param javaString the result of converting the double to a string using the Java conventions.
162: * This value is adjusted as necessary to cater for the differences between the Java and XPath rules.
163: * @return the value converted to a string, according to the XPath casting rules.
164: */
165:
166: static String doubleToString(double value, String javaString) {
167: if (value == 0.0) {
168: if (javaString.charAt(0) == '-') {
169: return "-0";
170: } else {
171: return "0";
172: }
173: }
174: if (Double.isInfinite(value)) {
175: return (value > 0 ? "INF" : "-INF");
176: }
177: if (Double.isNaN(value)) {
178: return "NaN";
179: }
180: final double absval = Math.abs(value);
181: String s = javaString;
182: if (absval < 1.0e-6 || absval >= 1.0e+6) {
183: if (s.indexOf('E') < 0) {
184: // need to use scientific notation, but Java isn't using it
185: // (Java's cutoff is 1.0E7, while XPath's is 1.0E6)
186: // So we have for example -2000000.0 rather than -2.0e6
187: FastStringBuffer sb = new FastStringBuffer(32);
188: Matcher matcher = nonExponentialPattern.matcher(s);
189: if (matcher.matches()) {
190: sb.append(matcher.group(1));
191: sb.append('.');
192: sb.append(matcher.group(2));
193: final String fraction = matcher.group(4);
194: if ("0".equals(fraction)) {
195: sb.append("E"
196: + (matcher.group(2).length() + matcher
197: .group(3).length()));
198: return sb.toString();
199: } else {
200: sb.append(matcher.group(3));
201: sb.append(matcher.group(4));
202: sb.append("E"
203: + (matcher.group(2).length() + matcher
204: .group(3).length()));
205: return sb.toString();
206: }
207: } else {
208: // fallback, this shouldn't happen
209: return s;
210: }
211: } else {
212: return s;
213: }
214: }
215: int len = s.length();
216: if (s.endsWith("E0")) {
217: s = s.substring(0, len - 2);
218: }
219: if (s.endsWith(".0")) {
220: return s.substring(0, len - 2);
221: }
222: int e = s.indexOf('E');
223: if (e < 0) {
224: // For some reason, Double.toString() in Java can return strings such as "0.0040"
225: // so we remove any trailing zeros
226: while (s.charAt(len - 1) == '0' && s.charAt(len - 2) != '.') {
227: s = s.substring(0, --len);
228: }
229: return s;
230: }
231: int exp = Integer.parseInt(s.substring(e + 1));
232: String sign;
233: if (s.charAt(0) == '-') {
234: sign = "-";
235: s = s.substring(1);
236: --e;
237: } else {
238: sign = "";
239: }
240: int nDigits = e - 2;
241: if (exp >= nDigits) {
242: return sign + s.substring(0, 1) + s.substring(2, e)
243: + zeros(exp - nDigits);
244: } else if (exp > 0) {
245: return sign + s.substring(0, 1) + s.substring(2, 2 + exp)
246: + '.' + s.substring(2 + exp, e);
247: } else {
248: while (s.charAt(e - 1) == '0')
249: e--;
250: return sign + "0." + zeros(-1 - exp) + s.substring(0, 1)
251: + s.substring(2, e);
252: }
253: }
254:
255: static String zeros(int n) {
256: char[] buf = new char[n];
257: for (int i = 0; i < n; i++)
258: buf[i] = '0';
259: return new String(buf);
260: }
261:
262: /**
263: * Determine the data type of the expression
264: * @return Type.DOUBLE
265: * @param th
266: */
267:
268: public ItemType getItemType(TypeHierarchy th) {
269: return Type.DOUBLE_TYPE;
270: }
271:
272: /**
273: * Negate the value
274: */
275:
276: public NumericValue negate() {
277: return new DoubleValue(-value);
278: }
279:
280: /**
281: * Implement the XPath floor() function
282: */
283:
284: public NumericValue floor() {
285: return new DoubleValue(Math.floor(value));
286: }
287:
288: /**
289: * Implement the XPath ceiling() function
290: */
291:
292: public NumericValue ceiling() {
293: return new DoubleValue(Math.ceil(value));
294: }
295:
296: /**
297: * Implement the XPath round() function
298: */
299:
300: public NumericValue round() {
301: if (Double.isNaN(value)) {
302: return this ;
303: }
304: if (Double.isInfinite(value)) {
305: return this ;
306: }
307: if (value == 0.0) {
308: return this ; // handles the negative zero case
309: }
310: if (value > -0.5 && value < 0.0) {
311: return new DoubleValue(-0.0);
312: }
313: if (value > Long.MIN_VALUE && value < Long.MAX_VALUE) {
314: return new DoubleValue(Math.round(value));
315: }
316:
317: // A double holds fewer significant digits than a long. Therefore,
318: // if the double is outside the range of a long, it cannot have
319: // any signficant digits after the decimal point. So in this
320: // case, we return the original value unchanged
321:
322: return this ;
323: }
324:
325: /**
326: * Implement the XPath round-to-half-even() function
327: */
328:
329: public NumericValue roundToHalfEven(int scale) {
330: if (Double.isNaN(value))
331: return this ;
332: if (Double.isInfinite(value))
333: return this ;
334: if (value == 0.0)
335: return this ; // handles the negative zero case
336:
337: // Convert to a scaled integer, by multiplying by 10^scale
338:
339: double factor = Math.pow(10, scale + 1);
340: double d = Math.abs(value * factor);
341:
342: // Now apply any rounding needed, using the "round half to even" rule
343:
344: double rem = d % 10;
345: if (rem > 5) {
346: d += (10 - rem);
347: } else if (rem < 5) {
348: d -= rem;
349: } else {
350: // round half to even - check the last bit
351: if ((d % 20) == 15) {
352: d += 5;
353: } else {
354: d -= 5;
355: }
356: }
357:
358: // Now convert back to the original magnitude
359:
360: d /= factor;
361: if (value < 0) {
362: d = 0.0 - d;
363: }
364: return new DoubleValue(d);
365:
366: }
367:
368: /**
369: * Determine whether the value is negative, zero, or positive
370: * @return -1 if negative, 0 if zero (including negative zero), +1 if positive, NaN if NaN
371: */
372:
373: public double signum() {
374: if (Double.isNaN(value)) {
375: return value;
376: }
377: if (value > 0)
378: return 1;
379: if (value == 0)
380: return 0;
381: return -1;
382: }
383:
384: /**
385: * Determine whether the value is a whole number, that is, whether it compares
386: * equal to some integer
387: */
388:
389: public boolean isWholeNumber() {
390: return value == Math.floor(value) && !Double.isInfinite(value);
391: }
392:
393: /**
394: * Evaluate a binary arithmetic operator.
395: */
396:
397: public NumericValue arithmetic(int operator, NumericValue other,
398: XPathContext context) throws XPathException {
399: if (other instanceof DoubleValue) {
400: switch (operator) {
401: case Token.PLUS:
402: return new DoubleValue(value
403: + ((DoubleValue) other).value);
404: case Token.MINUS:
405: return new DoubleValue(value
406: - ((DoubleValue) other).value);
407: case Token.MULT:
408: return new DoubleValue(value
409: * ((DoubleValue) other).value);
410: case Token.DIV:
411: return new DoubleValue(value
412: / ((DoubleValue) other).value);
413: case Token.IDIV:
414: if (((DoubleValue) other).value == 0.0) {
415: DynamicError e = new DynamicError(
416: "Integer division by zero");
417: e.setErrorCode("FOAR0001");
418: e.setXPathContext(context);
419: throw e;
420: }
421: return (NumericValue) (new DoubleValue(value
422: / ((DoubleValue) other).value).convert(
423: Type.INTEGER, context));
424: case Token.MOD:
425: return new DoubleValue(value
426: % ((DoubleValue) other).value);
427: default:
428: throw new UnsupportedOperationException(
429: "Unknown operator");
430: }
431: } else {
432: return arithmetic(operator, (DoubleValue) other.convert(
433: Type.DOUBLE, context), context);
434: }
435: }
436:
437: /**
438: * Compare two values for equality. This supports identity constraints in XML Schema,
439: * which allow list-valued elements and attributes to participate in key and uniqueness constraints.
440: * This method returns false if any error occurs during the comparison, or if any of the items
441: * in either sequence is a node rather than an atomic value. The default implementation of
442: * schemaEquals() is the same as equals(), but subclasses can override this.
443: */
444:
445: public boolean schemaEquals(Value obj) {
446: if (obj instanceof AtomicValue) {
447: obj = ((AtomicValue) obj).getPrimitiveValue();
448: }
449: if (obj instanceof DoubleValue) {
450: return value == ((DoubleValue) obj).value;
451: } else {
452: return false;
453: }
454: }
455:
456: /**
457: * Convert to Java object (for passing to external functions)
458: */
459:
460: public Object convertToJava(Class target, XPathContext context)
461: throws XPathException {
462: if (target == Object.class) {
463: return new Double(value);
464: } else if (target.isAssignableFrom(DoubleValue.class)) {
465: return this ;
466: } else if (target == boolean.class) {
467: return Boolean.valueOf(effectiveBooleanValue(context));
468: } else if (target == Boolean.class) {
469: return Boolean.valueOf(effectiveBooleanValue(context));
470: } else if (target == String.class
471: || target == CharSequence.class) {
472: return getStringValue();
473: } else if (target == double.class || target == Double.class) {
474: return new Double(value);
475: } else if (target == float.class || target == Float.class) {
476: return new Float(value);
477: } else if (target == long.class || target == Long.class) {
478: return new Long((long) value);
479: } else if (target == int.class || target == Integer.class) {
480: return new Integer((int) value);
481: } else if (target == short.class || target == Short.class) {
482: return new Short((short) value);
483: } else if (target == byte.class || target == Byte.class) {
484: return new Byte((byte) value);
485: } else if (target == char.class || target == Character.class) {
486: return new Character((char) value);
487: } else {
488: Object o = super .convertToJava(target, context);
489: if (o == null) {
490: DynamicError err = new DynamicError(
491: "Conversion of double to " + target.getName()
492: + " is not supported");
493: err.setXPathContext(context);
494: err.setErrorCode("SAXON:0000");
495: }
496: return o;
497: }
498: }
499:
500: // public static void main(String[] args) {
501: // double d = Double.parseDouble("-1.75e-3");
502: // System.err.println(d);
503: // BigDecimal dec = new BigDecimal(d);
504: // System.err.println(dec);
505: // }
506:
507: }
508:
509: //
510: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
511: // you may not use this file except in compliance with the License. You may obtain a copy of the
512: // License at http://www.mozilla.org/MPL/
513: //
514: // Software distributed under the License is distributed on an "AS IS" basis,
515: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
516: // See the License for the specific language governing rights and limitations under the License.
517: //
518: // The Original Code is: all this file except the asStringXT() and zeros() methods (not currently used).
519: //
520: // The Initial Developer of the Original Code is Michael H. Kay.
521: //
522: // Portions created by (xt) are Copyright (C) (James Clark). All Rights Reserved.
523: //
524: // Contributor(s): none.
525: //
|