1 package net.sf.saxon.value; 2 import net.sf.saxon.Err; 3 import net.sf.saxon.ConversionContext; 4 import net.sf.saxon.om.FastStringBuffer; 5 import net.sf.saxon.expr.Token; 6 import net.sf.saxon.expr.XPathContext; 7 import net.sf.saxon.trans.DynamicError; 8 import net.sf.saxon.trans.XPathException; 9 import net.sf.saxon.type.BuiltInAtomicType; 10 import net.sf.saxon.type.ItemType; 11 import net.sf.saxon.type.Type; 12 import net.sf.saxon.type.ValidationException; 13 14 import java.math.BigDecimal ; 15 import java.math.BigInteger ; 16 import java.util.regex.Pattern ; 17 18 21 22 public final class DecimalValue extends NumericValue { 23 24 private static final int DIVIDE_PRECISION = 18; 25 26 private BigDecimal value; 27 28 32 33 public DecimalValue(BigDecimal value) { 34 this.value = value; 35 loseTrailingZeros(); 36 } 37 38 private static final Pattern decimalPattern = Pattern.compile("(\\-|\\+)?((\\.[0-9]+)|([0-9]+(\\.[0-9]*)?))"); 39 40 47 48 public static AtomicValue makeDecimalValue(CharSequence in, boolean validate) { 49 String trimmed = trimWhitespace(in).toString(); 50 try { 51 if (validate) { 52 if (!decimalPattern.matcher(trimmed).matches()) { 53 ValidationException err = new ValidationException( 54 "Cannot convert string " + Err.wrap(trimmed, Err.VALUE) + " to xs:decimal"); 55 err.setErrorCode("FORG0001"); 56 return new ValidationErrorValue(err); 57 } 58 } 59 BigDecimal val = new BigDecimal (trimmed); 60 DecimalValue value = new DecimalValue(val); 61 if (trimmed.charAt(trimmed.length()-1) == '0') { 62 value.loseTrailingZeros(); 63 } 64 return value; 65 } catch (NumberFormatException err) { 66 ValidationException e = new ValidationException( 67 "Cannot convert string " + Err.wrap(trimmed, Err.VALUE) + " to xs:decimal"); 68 e.setErrorCode("FORG0001"); 69 return new ValidationErrorValue(e); 70 } 71 } 72 73 77 78 public DecimalValue(double in) throws ValidationException { 79 try { 80 this.value = new BigDecimal (in); 81 loseTrailingZeros(); 82 } catch (NumberFormatException err) { 83 ValidationException e = new ValidationException( 85 "Cannot convert double " + Err.wrap(in+"", Err.VALUE) + " to decimal"); 86 e.setErrorCode("FORG0001"); 87 throw e; 88 } 89 } 90 91 95 96 public DecimalValue(long in) { 97 this.value = BigDecimal.valueOf(in); 98 } 99 100 104 105 private void loseTrailingZeros() { 106 int scale = value.scale(); 107 if (scale > 0) { 108 BigInteger i = value.unscaledValue(); 109 while (true) { 110 BigInteger [] dr = i.divideAndRemainder(TEN); 111 if (dr[1].equals(BigInteger.ZERO)) { 112 i = dr[0]; 113 scale--; 114 if (scale==0) { 115 break; 116 } 117 } else { 118 break; 119 } 120 } 121 if (scale != value.scale()) { 122 value = new BigDecimal (i, scale); 123 } 124 } 125 } 126 127 private static final BigInteger TEN = BigInteger.valueOf(10); 128 129 132 133 public BigDecimal getValue() { 134 return value; 135 } 136 137 141 142 public int hashCode() { 143 BigDecimal round = value.setScale(0, BigDecimal.ROUND_DOWN); 144 long value = round.longValue(); 145 if (value > Integer.MIN_VALUE && value < Integer.MAX_VALUE) { 146 return (int)value; 147 } else { 148 return new Double (this.getDoubleValue()).hashCode(); 149 } 150 } 151 152 public boolean effectiveBooleanValue(XPathContext context) { 153 return value.signum() != 0; 154 } 155 156 159 160 public AtomicValue convertPrimitive(BuiltInAtomicType requiredType, boolean validate, ConversionContext conversion) { 161 switch(requiredType.getPrimitiveType()) { 162 case Type.BOOLEAN: 163 return BooleanValue.get(value.signum()!=0); 165 case Type.NUMBER: 166 case Type.DECIMAL: 167 case Type.ATOMIC: 168 case Type.ITEM: 169 return this; 170 case Type.INTEGER: 171 return BigIntegerValue.makeValue(value.toBigInteger()); 172 case Type.DOUBLE: 173 return new DoubleValue(value.doubleValue()); 174 case Type.FLOAT: 175 return new FloatValue(value.floatValue()); 176 case Type.STRING: 177 return new StringValue(getStringValueCS()); 178 case Type.UNTYPED_ATOMIC: 179 return new UntypedAtomicValue(getStringValueCS()); 180 default: 181 ValidationException err = new ValidationException("Cannot convert decimal to " + 182 requiredType.getDisplayName()); 183 err.setErrorCode("FORG0001"); 185 return new ValidationErrorValue(err); 186 } 187 } 188 189 193 194 public String getStringValue() { 195 if (value.scale() <= 0) { 198 return value.toString(); 199 } else { 200 boolean negative = value.signum() < 0; 201 String s = value.abs().unscaledValue().toString(); 202 int len = s.length(); 203 int scale = value.scale(); 204 FastStringBuffer sb = new FastStringBuffer(len+1); 205 if (negative) { 206 sb.append('-'); 207 } 208 if (scale >= len) { 209 sb.append("0."); 210 for (int i=len; i<scale; i++) { 211 sb.append('0'); 212 } 213 sb.append(s); 214 } else { 215 sb.append(s.substring(0, len-scale)); 216 sb.append('.'); 217 sb.append(s.substring(len-scale)); 218 } 219 return sb.toString(); 220 } 221 } 222 223 227 228 public ItemType getItemType() { 229 return Type.DECIMAL_TYPE; 230 } 231 232 235 236 public NumericValue negate() { 237 return new DecimalValue(value.negate()); 238 } 239 240 243 244 public NumericValue floor() { 245 return new DecimalValue(value.setScale(0, BigDecimal.ROUND_FLOOR)); 246 } 247 248 251 252 public NumericValue ceiling() { 253 return new DecimalValue(value.setScale(0, BigDecimal.ROUND_CEILING)); 254 } 255 256 259 260 public NumericValue round() { 261 265 268 switch (value.signum()) { 269 case -1: 270 return new DecimalValue(value.setScale(0, BigDecimal.ROUND_HALF_DOWN)); 271 case 0: 272 return this; 273 case +1: 274 return new DecimalValue(value.setScale(0, BigDecimal.ROUND_HALF_UP)); 275 default: 276 return this; 278 } 279 280 } 281 282 285 286 public NumericValue roundToHalfEven(int scale) { 287 if (scale<0) { 288 try { 289 AtomicValue val = convert(Type.INTEGER, null); 290 if (val instanceof IntegerValue) { 291 return ((IntegerValue)val).roundToHalfEven(scale); 292 } else { 293 return ((BigIntegerValue)val).roundToHalfEven(scale); 294 } 295 } catch (XPathException err) { 296 throw new IllegalArgumentException ("internal error in integer-decimal conversion"); 297 } 298 } else { 299 return new DecimalValue(value.setScale(scale, BigDecimal.ROUND_HALF_EVEN)); 300 } 301 } 302 303 307 308 public double signum() { 309 return value.signum(); 310 } 311 312 316 317 public boolean isWholeNumber() { 318 return value.scale()==0 || 319 value.equals(value.setScale(0, BigDecimal.ROUND_DOWN)); 320 } 321 322 325 326 public NumericValue arithmetic(int operator, NumericValue other, XPathContext context) throws XPathException { 327 if (other instanceof DecimalValue) { 328 try { 329 switch(operator) { 330 case Token.PLUS: 331 return new DecimalValue(value.add(((DecimalValue)other).value)); 332 case Token.MINUS: 333 return new DecimalValue(value.subtract(((DecimalValue)other).value)); 334 case Token.MULT: 335 return new DecimalValue(value.multiply(((DecimalValue)other).value)); 336 case Token.DIV: 337 int scale = Math.max(DIVIDE_PRECISION, 338 Math.max(value.scale(), ((DecimalValue)other).value.scale())); 339 BigDecimal result = value.divide(((DecimalValue)other).value, scale, BigDecimal.ROUND_HALF_DOWN); 341 return new DecimalValue(result); 342 case Token.IDIV: 343 if (((DecimalValue)other).value.signum() == 0) { 344 DynamicError e = new DynamicError("Integer division by zero"); 345 e.setErrorCode("FOAR0001"); 346 e.setXPathContext(context); 347 throw e; 348 } 349 BigInteger quot = value.divide(((DecimalValue)other).value, 0, BigDecimal.ROUND_DOWN).toBigInteger(); 350 return BigIntegerValue.makeValue(quot); 351 case Token.MOD: 352 BigDecimal quotient = value.divide(((DecimalValue)other).value, 0, BigDecimal.ROUND_DOWN); 354 BigDecimal remainder = value.subtract(quotient.multiply(((DecimalValue)other).value)); 355 return new DecimalValue(remainder); 356 default: 357 throw new AssertionError ("Unknown operator"); 358 } 359 } catch (ArithmeticException err) { 360 throw new DynamicError(err); 361 } 362 } else if (NumericValue.isInteger(other)) { 363 return arithmetic(operator, (DecimalValue)other.convert(Type.DECIMAL, context), context); 364 } else { 365 NumericValue n = (NumericValue)convert(other.getItemType().getPrimitiveType(), context); 366 return n.arithmetic(operator, other, context); 367 } 368 } 369 370 373 374 public int compareTo(Object other) { 375 if ((NumericValue.isInteger((NumericValue)other))) { 376 try { 378 return compareTo(((NumericValue)other).convert(Type.DECIMAL, null)); 379 } catch (XPathException err) { 380 throw new AssertionError ("Conversion of integer to decimal should never fail"); 381 } 382 } else if (other instanceof BigIntegerValue) { 383 return value.compareTo(((BigIntegerValue)other).asDecimal()); 384 } else if (other instanceof DecimalValue) { 385 return value.compareTo(((DecimalValue)other).value); 386 } else { 387 return super.compareTo(other); 388 } 389 } 390 391 398 399 public boolean schemaEquals(Value obj) { 400 if (obj instanceof AtomicValue) { 401 obj = ((AtomicValue)obj).getPrimitiveValue(); 402 } 403 try { 404 if (obj instanceof DecimalValue) { 405 return value.equals(((DecimalValue)obj).value); 406 } else if (obj instanceof IntegerValue) { 407 return schemaEquals(((IntegerValue)obj).convert(Type.DECIMAL, null)); 408 } else if (obj instanceof BigIntegerValue) { 409 return schemaEquals(((BigIntegerValue)obj).convert(Type.DECIMAL, null)); 410 } else { 411 return false; 412 } 413 } catch (XPathException e) { 414 return false; 415 } 416 } 417 418 421 422 public Object convertToJava(Class target, XPathContext context) throws XPathException { 423 if (target==Object .class || target.isAssignableFrom(BigDecimal .class)) { 424 return value; 425 } else if (target.isAssignableFrom(DecimalValue.class)) { 426 return this; 427 } else if (target==boolean.class) { 428 BooleanValue bval = (BooleanValue)convert(Type.BOOLEAN, context); 429 return Boolean.valueOf(bval.getBooleanValue()); 430 } else if (target==Boolean .class) { 431 BooleanValue bval = (BooleanValue)convert(Type.BOOLEAN, context); 432 return Boolean.valueOf(bval.getBooleanValue()); 433 } else if (target==String .class || target==CharSequence .class) { 434 return getStringValue(); 435 } else if (target==double.class || target==Double .class) { 436 return new Double (value.doubleValue()); 437 } else if (target==float.class || target==Float .class) { 438 return new Float (value.floatValue()); 439 } else if (target==long.class || target==Long .class) { 440 return new Long (value.longValue()); 441 } else if (target==int.class || target==Integer .class) { 442 return new Integer (value.intValue()); 443 } else if (target==short.class || target==Short .class) { 444 return new Short (value.shortValue()); 445 } else if (target==byte.class || target==Byte .class) { 446 return new Byte (value.byteValue()); 447 } else if (target==char.class || target==Character .class) { 448 return new Character ((char)value.intValue()); 449 } else { 450 Object o = super.convertToJava(target, context); 451 if (o == null) { 452 throw new DynamicError("Conversion of decimal to " + target.getName() + 453 " is not supported"); 454 } 455 return o; 456 } 457 } 458 459 460 } 461 462 480 | Popular Tags |