001 package hirondelle.web4j.model; 002 003 import java.util.*; 004 import java.io.Serializable; 005 import java.io.IOException; 006 import java.io.ObjectInputStream; 007 import java.io.ObjectOutputStream; 008 import java.math.BigDecimal; 009 import java.math.RoundingMode; 010 011 /** 012 Represent an immutable number, using a natural, compact syntax. 013 The number may have a decimal portion, or it may not. 014 015 <P>Decimal amounts are typically used to represent two kinds of items : 016 <ul> 017 <li>monetary amounts 018 <li>measurements such as temperature, distance, and so on 019 </ul> 020 021 <P>Your applications are not obliged to use this class to represent decimal amounts. 022 You may choose to use {@link BigDecimal} instead (perhaps along with an <tt>Id</tt> 023 to store a currency, if needed). 024 025 <P>This class exists for these reasons: 026 <ul> 027 <li>to simplify calculations, and build on top of what's available from the {@link BigDecimal} class 028 <li>to allow your code to read at a higher level 029 <li>to define a more natural, pleasing syntax 030 <li>to help you avoid floating-point types, 031 which have many <a href='http://www.ibm.com/developerworks/java/library/j-jtp0114/'>pitfalls</a> 032 </ul> 033 034 <P><tt>Decimal</tt> objects are immutable. 035 Many operations return new <tt>Decimal</tt> objects. 036 037 <h3>Currency Is Unspecified</h3> 038 This class can be used to model amounts of money. 039 <P><em>Many will be surprised that this class does not make any reference to currency.</em> 040 The reason for this is adding currency would render this class a poor <em>building block</em>. 041 Building block objects such as <tt>Date</tt>, <tt>Integer</tt>, and so on, are 042 <em>atomic</em>, in the sense of representing a <em>single</em> piece of data. 043 They correspond to a single column in a table, or a single form control. If the currency 044 were included in this class, then it would no longer be atomic, and it could not be 045 treated by WEB4J as any other building block class. 046 However, allowing this class to be treated like any other building block class is 047 highly advantageous. 048 049 <P>If a feature needs to explicitly distinguish between <em>multiple</em> currencies 050 such as US Dollars and Japanese Yen, then a <tt>Decimal</tt> object 051 will need to be paired by the caller with a <em>second</em> item representing the 052 underlying currency (perhaps modeled as an <tt>Id</tt>). 053 See the {@link Currency} class for more information. 054 055 <h3>Number of Decimal Places</h3> 056 To validate the number of decimals in your Model Objects, 057 call the {@link Check#numDecimalsAlways(int)} or {@link Check#numDecimalsMax(int)} methods. 058 059 <h3>Different Numbers of Decimals</h3> 060 <P>As usual, operations can be performed on two items having a different number of decimal places. 061 For example, these operations are valid (using an informal, <em>ad hoc</em> notation) : 062 <PRE>10 + 1.23 = 11.23 063 10.00 + 1.23 = 11.23 064 10 - 1.23 = 8.77 065 (10 > 1.23) => true </PRE> 066 This corresponds to typical user expectations. 067 068 <P>The {@link #eq(Decimal)} is usually to be preferred over the {@link #equals(Object)} method. 069 The {@link #equals(Object)} is unusual, in that it's the only method sensitive to the exact 070 number of decimal places, while {@link #eq(Decimal)} is not. That is, 071 <PRE>10.equals(10.00) => false 072 10.eq(10.00) => true</PRE> 073 074 <h3>Terse Method Names</h3> 075 Various methods in this class have unusually terse names, such as 076 <tt>lt</tt> for 'less than', and <tt>gt</tt> for 'greater than', and so on. 077 The intent of such names is to improve the legibility of mathematical 078 expressions. 079 080 <P>Example: 081 <PRE>if (amount.lt(hundred)) { 082 cost = amount.times(price); 083 }</PRE> 084 085 <h3>Prefer Decimal forms</h3> 086 <P>Many methods in this class are overloaded to perform the same operation with various types: 087 <ul> 088 <li>Decimal 089 <li>long (which will also accept an int) 090 <li>double (which will also accept a float) 091 </ul> 092 Usually, you should prefer the Decimal form. The long and double forms are usually convenience methods, which simply call the Decimal 093 version as part of their implementations; they're intended for cases when you simply wish to specify a hard-coded constant value. 094 095 <h3>Extends Number</h3> 096 <P>This class extends {@link Number}. This allows other parts of the JDK to treat a <tt>Decimal</tt> just like any other 097 <tt>Number</tt>. 098 */ 099 public final class Decimal extends Number implements Comparable<Decimal>, Serializable { 100 101 /** 102 The default rounding mode used by this class (<tt>HALF_EVEN</tt>). 103 This rounding style results in the least bias. 104 */ 105 public static final RoundingMode ROUNDING = RoundingMode.HALF_EVEN; 106 107 /** 108 Constructor. 109 @param aAmount required, any number of decimals. 110 */ 111 public Decimal(BigDecimal aAmount){ 112 fAmount = aAmount; 113 validateState(); 114 } 115 116 /** 117 Convenience factory method. Leading zeroes are allowed. 118 <P>Instead of : 119 <PRE>Decimal decimal = new Decimal(new BigDecimal("100"));</PRE> 120 one may instead use this more compact form: 121 <PRE>Decimal decimal = Decimal.from("100");</PRE> 122 which is a bit more legible. 123 */ 124 public static Decimal from(String aAmount){ 125 return new Decimal(new BigDecimal(aAmount)); 126 } 127 128 /** Convenience factory method. */ 129 public static Decimal from(long aAmount){ 130 return new Decimal(new BigDecimal(aAmount)); 131 } 132 133 /** Convenience factory method. */ 134 public static Decimal from(double aAmount){ 135 return new Decimal(BigDecimal.valueOf(aAmount)); 136 } 137 138 /** 139 Renders this <tt>Decimal</tt> in a style suitable for debugging. 140 <em>Intended for debugging only.</em> 141 142 <P>Returns the amount in the format defined by {@link BigDecimal#toPlainString()}. 143 */ 144 public String toString(){ 145 return fAmount.toPlainString(); 146 } 147 148 /** 149 Equals, sensitive to scale of the underlying BigDecimal. 150 151 <P>That is, <tt>10</tt> and <tt>10.00</tt> are <em>not</em> 152 considered equal by this method. <b>Such behavior is often undesired; in most 153 practical cases, it's likely best to use the {@link #eq(Decimal)} method instead.</b>, 154 which has no such monkey business. 155 156 <P>This implementation imitates {@link BigDecimal#equals(java.lang.Object)}, 157 which is also sensitive to the number of decimals (or 'scale'). 158 */ 159 public boolean equals(Object aThat){ 160 if (this == aThat) return true; 161 if (! (aThat instanceof Decimal) ) return false; 162 Decimal that = (Decimal)aThat; 163 //the object fields are never null : 164 boolean result = (this.fAmount.equals(that.fAmount) ); 165 return result; 166 } 167 168 public int hashCode(){ 169 if ( fHashCode == 0 ) { 170 fHashCode = HASH_SEED; 171 fHashCode = HASH_FACTOR * fHashCode + fAmount.hashCode(); 172 } 173 return fHashCode; 174 } 175 176 /** 177 Implements the {@link Comparable} interface. 178 179 <P>It's possible to use this method as a general replacement for a large number of methods which compare numbers: 180 lt, eq, lteq, and so on. However, it's recommended that you use those other methods, since they have greater clarity 181 and concision. 182 */ 183 public int compareTo(Decimal aThat) { 184 final int EQUAL = 0; 185 if ( this == aThat ) return EQUAL; 186 //the object field is never null 187 int comparison = this.fAmount.compareTo(aThat.fAmount); 188 if ( comparison != EQUAL ) return comparison; 189 return EQUAL; 190 } 191 192 /** Return the amount as a BigDecimal. */ 193 public BigDecimal getAmount() { return fAmount; } 194 195 /** The suffix is needed to distinguish from the public field. Declared 'early' since compiler complains.*/ 196 private static final BigDecimal ZERO_BD = BigDecimal.ZERO; 197 private static final BigDecimal ONE_BD = BigDecimal.ONE; 198 private static final BigDecimal MINUS_ONE_BD = new BigDecimal("-1"); 199 200 /** 201 Zero <tt>Decimal</tt> amount, a simple convenience constant. 202 203 <P>Like {@link BigDecimal#ZERO}, this item has no explicit decimal. 204 In most cases that will not matter, since only the {@link #equals(Object)} method is sensitive to 205 exact decimals. All other methods, including {@link #eq(Decimal)}, are not sensitive to exact decimals. 206 */ 207 public static final Decimal ZERO = new Decimal(ZERO_BD); 208 209 /** Convenience constant. */ 210 public static final Decimal ONE = new Decimal(ONE_BD); 211 212 /** Convenience constant. */ 213 public static final Decimal MINUS_ONE = new Decimal(MINUS_ONE_BD); 214 215 /** 216 An approximation to the number pi, to 15 decimal places. 217 Pi is the ratio of the circumference of a circle to its radius. 218 It's also rumoured to <a href='http://en.wikipedia.org/wiki/Pi_Day'>taste good</a> as well. 219 */ 220 public static final Decimal PI = new Decimal(new BigDecimal("3.141592653589793")); 221 222 /** 223 An approximation to Euler's number, to 15 decimal places. 224 Euler's number is the base of the natural logarithms. 225 */ 226 public static final Decimal E = new Decimal(new BigDecimal( "2.718281828459045")); 227 228 /** 229 Return the number of decimals in this value. More accurately, this returns the 'scale' of the 230 underlying BigDecimal. Negative scales are possible; they represent the number of zeros to be 231 adding on to the end of an integer. 232 */ 233 public int getNumDecimals(){ 234 return fAmount.scale(); 235 } 236 237 /** 238 Return <tt>true</tt> only if this Decimal is an integer. 239 For example, 2 and 2.00 are integers, but 2.01 is not. 240 */ 241 public boolean isInteger(){ 242 return round().minus(this).eq(ZERO); 243 } 244 245 /** Return <tt>true</tt> only if the amount is positive. */ 246 public boolean isPlus(){ 247 return fAmount.compareTo(ZERO_BD) > 0; 248 } 249 250 /** Return <tt>true</tt> only if the amount is negative. */ 251 public boolean isMinus(){ 252 return fAmount.compareTo(ZERO_BD) < 0; 253 } 254 255 /** Return <tt>true</tt> only if the amount is zero. */ 256 public boolean isZero(){ 257 return fAmount.compareTo(ZERO_BD) == 0; 258 } 259 260 /** 261 Equals (insensitive to number of decimals). 262 That is, <tt>10</tt> and <tt>10.00</tt> are considered equal by this method. 263 264 <P>Return <tt>true</tt> only if the amounts are equal. 265 This method is <em>not</em> synonymous with the <tt>equals</tt> method, 266 since the {@link #equals(Object)} method is sensitive to the exact number of decimal places (or, more 267 precisely, the scale of the underlying BigDecimal.) 268 */ 269 public boolean eq(Decimal aThat) { 270 return compareAmount(aThat) == 0; 271 } 272 public boolean eq(long aThat) { 273 return eq(Decimal.from(aThat)); 274 } 275 public boolean eq(double aThat) { 276 return eq(Decimal.from(aThat)); 277 } 278 279 /** 280 Greater than. 281 <P>Return <tt>true</tt> only if 'this' amount is greater than 282 'that' amount. 283 */ 284 public boolean gt(Decimal aThat) { 285 return compareAmount(aThat) > 0; 286 } 287 public boolean gt(long aThat) { 288 return gt(Decimal.from(aThat)); 289 } 290 public boolean gt(double aThat) { 291 return gt(Decimal.from(aThat)); 292 } 293 294 /** 295 Greater than or equal to. 296 <P>Return <tt>true</tt> only if 'this' amount is 297 greater than or equal to 'that' amount. 298 */ 299 public boolean gteq(Decimal aThat) { 300 return compareAmount(aThat) >= 0; 301 } 302 public boolean gteq(long aThat) { 303 return gteq(Decimal.from(aThat)); 304 } 305 public boolean gteq(double aThat) { 306 return gteq(Decimal.from(aThat)); 307 } 308 309 /** 310 Less than. 311 <P>Return <tt>true</tt> only if 'this' amount is less than 312 'that' amount. 313 */ 314 public boolean lt(Decimal aThat) { 315 return compareAmount(aThat) < 0; 316 } 317 public boolean lt(long aThat) { 318 return lt(Decimal.from(aThat)); 319 } 320 public boolean lt(double aThat) { 321 return lt(Decimal.from(aThat)); 322 } 323 324 /** 325 Less than or equal to. 326 <P>Return <tt>true</tt> only if 'this' amount is less than or equal to 327 'that' amount. 328 */ 329 public boolean lteq(Decimal aThat) { 330 return compareAmount(aThat) <= 0; 331 } 332 public boolean lteq(long aThat) { 333 return lteq(Decimal.from(aThat)); 334 } 335 public boolean lteq(double aThat) { 336 return lteq(Decimal.from(aThat)); 337 } 338 339 /** 340 Add <tt>aThat</tt> <tt>Decimal</tt> to this <tt>Decimal</tt>. 341 */ 342 public Decimal plus(Decimal aThat){ 343 return new Decimal(fAmount.add(aThat.fAmount)); 344 } 345 public Decimal plus(long aThat){ 346 return plus(Decimal.from(aThat)); 347 } 348 public Decimal plus(double aThat){ 349 return plus(Decimal.from(aThat)); 350 } 351 352 /** 353 Subtract <tt>aThat</tt> <tt>Decimal</tt> from this <tt>Decimal</tt>. 354 */ 355 public Decimal minus(Decimal aThat){ 356 return new Decimal(fAmount.subtract(aThat.fAmount)); 357 } 358 public Decimal minus(long aThat){ 359 return minus(Decimal.from(aThat)); 360 } 361 public Decimal minus(double aThat){ 362 return minus(Decimal.from(aThat)); 363 } 364 365 /** 366 Sum a collection of <tt>Decimal</tt> objects. 367 368 @param aDecimals collection of <tt>Decimal</tt> objects. 369 If the collection is empty, then a zero value is returned. 370 */ 371 public static Decimal sum(Collection<Decimal> aDecimals){ 372 Decimal sum = new Decimal(ZERO_BD); 373 for(Decimal decimal : aDecimals){ 374 sum = sum.plus(decimal); 375 } 376 return sum; 377 } 378 379 /** Multiply this <tt>Decimal</tt> by a factor. */ 380 public Decimal times(Decimal aFactor){ 381 BigDecimal newAmount = fAmount.multiply(aFactor.getAmount()); 382 return new Decimal(newAmount); 383 } 384 public Decimal times(long aFactor){ 385 return times(Decimal.from(aFactor)); 386 } 387 public Decimal times(double aFactor){ 388 return times(Decimal.from(aFactor)); 389 } 390 391 /** 392 Divide this <tt>Decimal</tt> by a divisor. 393 <p>If the division results in a number which will never terminate, then this method 394 will round the result to 20 decimal places, using the default {@link #ROUNDING}. 395 */ 396 public Decimal div(Decimal aDivisor){ 397 BigDecimal newAmount = null; 398 try { 399 newAmount = fAmount.divide(aDivisor.fAmount); 400 } 401 catch(ArithmeticException ex){ 402 // non-terminating decimal 403 // need to apply a policy for where and how to round 404 newAmount = fAmount.divide(aDivisor.fAmount, DECIMALS, ROUNDING); 405 } 406 return new Decimal(newAmount); 407 } 408 public Decimal div(long aDivisor){ 409 return div(Decimal.from(aDivisor)); 410 } 411 public Decimal div(double aDivisor){ 412 return div(Decimal.from(aDivisor)); 413 } 414 415 /** Return the absolute value of the amount. */ 416 public Decimal abs(){ 417 return isPlus() ? this : times(-1); 418 } 419 420 /** Return this amount x (-1). */ 421 public Decimal negate(){ 422 return times(-1); 423 } 424 425 /** Round to an integer value, using the default {@link #ROUNDING} style. */ 426 public Decimal round(){ 427 BigDecimal amount = fAmount.setScale(0, ROUNDING); 428 return new Decimal(amount); 429 } 430 431 /** 432 Round to 0 or more decimal places, using the default {@link #ROUNDING} style. 433 @param aNumberOfDecimals must 0 or more. 434 */ 435 public Decimal round(int aNumberOfDecimals){ 436 if( aNumberOfDecimals < 0 ){ 437 throw new IllegalArgumentException("Number of decimals is negative: " + quote(aNumberOfDecimals)); 438 } 439 BigDecimal amount = fAmount.setScale(aNumberOfDecimals, ROUNDING); 440 return new Decimal(amount); 441 } 442 443 /** 444 Round to 0 or more decimal places, using the given rounding style. 445 @param aNumberOfDecimals must 0 or more. 446 */ 447 public Decimal round(int aNumberOfDecimals, RoundingMode aRoundingMode){ 448 if( aNumberOfDecimals < 0 ){ 449 throw new IllegalArgumentException("Number of decimals is negative: " + quote(aNumberOfDecimals)); 450 } 451 BigDecimal amount = fAmount.setScale(aNumberOfDecimals, aRoundingMode); 452 return new Decimal(amount); 453 } 454 455 /** 456 Round a number to the nearest multiple of the given interval. 457 For example: 458 <tt> 459 Decimal amount = Decimal.from("1710.12"); 460 amount.round2(0.05); // 1710.10 461 amount.round2(100); // 1700 462 </tt> 463 @param aInterval must be greater than zero 464 */ 465 public Decimal round2(Decimal aInterval){ 466 if( ! aInterval.isPlus() ){ 467 throw new IllegalArgumentException("Interval is negative or zero : " + quote(aInterval)); 468 } 469 BigDecimal result = fAmount.divide(aInterval.fAmount).setScale(0, ROUNDING).multiply(aInterval.fAmount); 470 return new Decimal(result); 471 } 472 public Decimal round2(long aInterval){ 473 return round2(Decimal.from(aInterval)); 474 } 475 public Decimal round2(double aInterval){ 476 return round2(Decimal.from(aInterval)); 477 } 478 479 /** 480 Raise this number to an <b>integral</b> power; the power can be of either sign. 481 482 <P>Special cases regarding 0: 483 <ul> 484 <li> <tt>0^-n</tt> is undefined (<tt>n > 0</tt>). 485 <li> <tt>x^0<tt/> always returns 1, even for <tt>x = 0</tt>. 486 </ul> 487 488 @param aPower is in the range -999,999,999..999,999,999, inclusive. (This reflects a restriction on 489 the underlying {@link BigDecimal#pow(int)} method. 490 */ 491 public Decimal pow(int aPower){ 492 BigDecimal newAmount = null; 493 if (aPower == 0){ 494 newAmount = ONE_BD; 495 } 496 else if (aPower == 1){ 497 newAmount = fAmount; 498 } 499 else if (aPower > 0){ 500 newAmount = fAmount.pow(aPower); 501 } 502 else if (aPower < 0 && this.eq(ZERO)){ 503 throw new RuntimeException("Raising 0 to a negative power is undefined."); 504 } 505 else if (aPower < 0){ 506 newAmount = fAmount.pow(-1 * aPower); 507 newAmount = ONE_BD.divide(newAmount); 508 } 509 return new Decimal(newAmount); 510 } 511 512 /** This implementation uses {@link Math#pow(double, double)}. */ 513 public Decimal pow(double aPower){ 514 double value = Math.pow(fAmount.doubleValue(), aPower); 515 return Decimal.from(value); 516 } 517 518 /** 519 Raise this Decimal to a Decimal power. 520 <P>This method calls either {@link #pow(int)} or {@link #pow(double)}, according to the return value of 521 {@link #isInteger()}. 522 */ 523 public Decimal pow(Decimal aPower){ 524 Decimal result = ZERO; 525 if (aPower.isInteger()){ 526 result = pow(aPower.intValue()); 527 } 528 else { 529 result = pow(aPower.doubleValue()); 530 } 531 return result; 532 } 533 534 /** 535 Required by {@link Number}. 536 537 <P><em>Use of floating point data is highly discouraged.</em> 538 This method is provided only because it's required by <tt>Number</tt>. 539 */ 540 @Override public double doubleValue() { 541 return fAmount.doubleValue(); 542 } 543 544 /** 545 Required by {@link Number}. 546 547 <P><em>Use of floating point data is highly discouraged.</em> 548 This method is provided only because it's required by <tt>Number</tt>. 549 */ 550 @Override public float floatValue() { 551 return fAmount.floatValue(); 552 } 553 554 /** Required by {@link Number}. */ 555 @Override public int intValue() { 556 return fAmount.intValue(); 557 } 558 559 /** Required by {@link Number}. */ 560 @Override public long longValue() { 561 return fAmount.longValue(); 562 } 563 564 // PRIVATE 565 566 /** 567 The decimal amount. 568 Never null. 569 @serial 570 */ 571 private BigDecimal fAmount; 572 573 /** Number of decimals to use when a division operation blows up into a non-terminating decimal. */ 574 private static final int DECIMALS = 20; 575 576 /** @serial */ 577 private int fHashCode; 578 private static final int HASH_SEED = 23; 579 private static final int HASH_FACTOR = 37; 580 581 /** 582 Determines if a deserialized file is compatible with this class. 583 584 Maintainers must change this value if and only if the new version 585 of this class is not compatible with old versions. See Sun docs 586 for <a href=http://java.sun.com/products/jdk/1.1/docs/guide 587 /serialization/spec/version.doc.html> details. </a> 588 589 Not necessary to include in first version of the class, but 590 included here as a reminder of its importance. 591 */ 592 private static final long serialVersionUID = 7526471155622776147L; 593 594 /** 595 Always treat de-serialization as a full-blown constructor, by 596 validating the final state of the de-serialized object. 597 */ 598 private void readObject( 599 ObjectInputStream aInputStream 600 ) throws ClassNotFoundException, IOException { 601 //always perform the default de-serialization first 602 aInputStream.defaultReadObject(); 603 //defensive copy for mutable date field 604 //BigDecimal is not technically immutable, since its non-final 605 fAmount = new BigDecimal( fAmount.toPlainString() ); 606 //ensure that object state has not been corrupted or tampered with maliciously 607 validateState(); 608 } 609 610 private void writeObject(ObjectOutputStream aOutputStream) throws IOException { 611 //perform the default serialization for all non-transient, non-static fields 612 aOutputStream.defaultWriteObject(); 613 } 614 615 private void validateState(){ 616 if( fAmount == null ) { 617 throw new IllegalArgumentException("Amount cannot be null"); 618 } 619 } 620 621 /** Ignores scale: 0 same as 0.00 */ 622 private int compareAmount(Decimal aThat){ 623 return this.fAmount.compareTo(aThat.fAmount); 624 } 625 626 private static String quote(Object aText){ 627 return "'" + String.valueOf(aText) + "'"; 628 } 629 }