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    }