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