javaweb中關於金額數值的計算

話很少說; 直接上教程java

第一: 數據庫字段類型(保留2個小數)ios

第二:封裝類採用BigDecimal類型接收數據庫

第三:計算採用Money對象工具類計算:如加減乘除,比大小等等apache

package com.tsou.comm.bean;

/**
 * winchance by 2010-now
 */


import java.io.Serializable;
import java.math.BigDecimal;

import org.apache.commons.lang.StringUtils;

/**
 * 單幣種貨幣類,處理貨幣算術、幣種和取整。
 *
 * <p>
 * 貨幣類中封裝了貨幣金額和幣種。目前金額在內部是long類型表示,
 * 單位是所屬幣種的最小貨幣單位(對人民幣是分)。
 *
 * <p>
 * 目前,貨幣實現瞭如下主要功能:<br>
 * <ul>
 *   <li>支持貨幣對象與double(float)/long(int)/String/BigDecimal之間相互轉換。
 *   <li>貨幣類在運算中提供與JDK中的BigDecimal相似的運算接口,
 *       BigDecimal的運算接口支持任意指定精度的運算功能,可以支持各類
 *       可能的財務規則。
 *   <li>貨幣類在運算中也提供一組簡單運算接口,使用這組運算接口,則在
 *       精度處理上使用缺省的處理規則。
 *   <li>推薦使用Money,不建議直接使用BigDecimal的緣由之一在於,
 *       使用BigDecimal,一樣金額和幣種的貨幣使用BigDecimal存在多種可能
 *       的表示,例如:new BigDecimal("10.5")與new BigDecimal("10.50")
 *       不相等,由於scale不等。使得Money類,一樣金額和幣種的貨幣只有
 *       一種表示方式,new Money("10.5")和new Money("10.50")應該是相等的。
 *   <li>不推薦直接使用BigDecimal的另外一緣由在於, BigDecimal是Immutable,
 *       一旦建立就不可更改,對BigDecimal進行任意運算都會生成一個新的
 *       BigDecimal對象,所以對於大批量統計的性能不夠滿意。Money類是
 *       mutable的,對大批量統計提供較好的支持。
 *   <li>提供基本的格式化功能。
 *   <li>Money類中不包含與業務相關的統計功能和格式化功能。業務相關的功能
 *       建議使用utility類來實現。
 *   <li>Money類實現了Serializable接口,支持做爲遠程調用的參數和返回值。
 *   <li>Money類實現了equals和hashCode方法。
 * </ul>
 * 
 * TODO: 必須處理運算中的溢出情形
 * 
 * @author 卡內齊 (kaneiqi@dajiaok.com)
 *
 * @version $Id: Money.java, v 0.1 2014-5-29 下午03:11:49 kaneiqi Exp $
 */
public class Money implements Serializable, Comparable {

    /**
    * Comment for <code>serialVersionUID</code>
    */
    private static final long serialVersionUID = 6009335074727417445L;

    /**
     * 金額,以分爲單位。
     */
    private long              cent;

    // 構造器 ====================================================

    /**
     * 缺省構造器。
     *
     * <p>
     * 建立一個具備缺省金額(0)和缺省幣種的貨幣對象。
     */
    public Money() {
        this(0);
    }

    /**
     * 構造器。
     *
     * <p>
     * 建立一個具備金額<code>yuan</code>元<code>cent</code>分和指定幣種的貨幣對象。
     *
     * @param yuan 金額元數。
     * @param cent 金額分數。
     */
    public Money(long yuan, int cent) {

        this.cent = (yuan * 100) + (cent % 100);
    }

    /** 
     * <p class="detail">
     * 功能:注意以分爲單位的金額
     * </p>
     * 
     * @param cent 金額分數。
     */
    public Money(long cent) {
        this.cent = cent;
    }

    /**
     * 構造器。
     *
     * <p>
     * 建立一個具備金額<code>amount</code>元和指定幣種<code>currency</code>的貨幣對象。
     *
     * @param amount 金額,以元爲單位。
     * @param currency 幣種。
     */
    public Money(String amount) {
        this(new BigDecimal(StringUtils.isNotBlank(amount) ? amount : "0"));
    }

    /**
     * 構造器。
     *
     * <p>
     * 建立一個具備金額<code>amount</code>元和指定幣種<code>currency</code>的貨幣對象。
     * 若是金額不能轉換爲整數分,則使用指定的取整模式<code>roundingMode</code>取整。
     *
     * @param amount 金額,以元爲單位。
     * @param currency 幣種。
     * @param roundingMode 取整模式。
     */
    public Money(String amount, int roundingMode) {
        this(new BigDecimal(amount), roundingMode);
    }

    /**
     * 構造器。
     *
     * <p>
     * 建立一個具備金額<code>amount</code>和指定幣種的貨幣對象。
     * 若是金額不能轉換爲整數分,則使用四捨五入方式取整。
     *
     * <p>
     * 注意:因爲double類型運算中存在偏差,使用四捨五入方式取整的
     * 結果並不肯定,所以,應儘可能避免使用double類型建立貨幣類型。
     * 例:
     * <code>
     * assertEquals(999, Math.round(9.995 * 100));
     * assertEquals(1000, Math.round(999.5));
     * money = new Money((9.995));
     * assertEquals(999, money.getCent());
     * money = new Money(10.005);
     * assertEquals(1001, money.getCent());
     * </code>
     *
     * @param amount 金額,以元爲單位。
     * @param currency 幣種。
     */
    public Money(double amount) {
        this.cent = Math.round(amount * 100);
    }

    /**
     * 構造器。
     *
     * <p>
     * 建立一個具備金額<code>amount</code>和缺省幣種的貨幣對象。
     * 若是金額不能轉換爲整數分,則使用缺省取整模式<code>DEFAULT_ROUNDING_MODE</code>取整。
     *
     * @param amount 金額,以元爲單位。
     */
    public Money(BigDecimal amount) {
        this(null != amount ? amount : new BigDecimal(0), BigDecimal.ROUND_HALF_EVEN);
    }

    /**
     * 構造器。
     *
     * <p>
     * 建立一個具備金額<code>amount</code>和指定幣種的貨幣對象。
     * 若是金額不能轉換爲整數分,則使用指定的取整模式<code>roundingMode</code>取整。
     *
     * @param amount 金額,以元爲單位。
     * @param currency 幣種。
     * @param roundingMode 取整模式。
     */
    public Money(BigDecimal amount, int roundingMode) {
        this.cent = rounding(amount.movePointRight(BigDecimal.ROUND_CEILING), roundingMode);
    }

    // Bean方法 ====================================================

    /**
     * 獲取本貨幣對象表明的金額數。
     *
     * @return 金額數,以元爲單位。
     */
    public BigDecimal getAmount() {
        return BigDecimal.valueOf(cent, BigDecimal.ROUND_CEILING);
    }

    /**
     * 設置本貨幣對象表明的金額數。
     * 
     * @param amount 金額數,以元爲單位。
     */
    public void setAmount(BigDecimal amount) {
        if (amount != null) {
            cent = rounding(amount.movePointRight(BigDecimal.ROUND_CEILING),
                BigDecimal.ROUND_HALF_EVEN);
        }
    }

    // 基本對象方法 ===================================================

    /**
     * 判斷本貨幣對象與另外一對象是否相等。
     *
     * <p>
     * 本貨幣對象與另外一對象相等的充分必要條件是:<br>
     *  <ul>
     *   <li>另外一對象也屬貨幣對象類。
     *   <li>金額相同。
     *   <li>幣種相同。
     *  </ul>
     *
     * @param other 待比較的另外一對象。
     * @return <code>true</code>表示相等,<code>false</code>表示不相等。
     *
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object other) {
        return (other instanceof Money) && equals((Money) other);
    }

    /**
     * 判斷本貨幣對象與另外一貨幣對象是否相等。
     *
     * <p>
     * 本貨幣對象與另外一貨幣對象相等的充分必要條件是:<br>
     *  <ul>
     *   <li>金額相同。
     *   <li>幣種相同。
     *  </ul>
     *
     * @param other 待比較的另外一貨幣對象。
     * @return <code>true</code>表示相等,<code>false</code>表示不相等。
     */
    public boolean equals(Money other) {
        return (cent == other.cent);
    }

    /**
     * 計算本貨幣對象的雜湊值。
     *
     * @return 本貨幣對象的雜湊值。
     *
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
        return (int) (cent ^ (cent >>> 32));
    }

    // Comparable接口 ========================================

    /**
     * 對象比較。
     *
     * <p>
     * 比較本對象與另外一對象的大小。
     * 若是待比較的對象的類型不是<code>Money</code>,則拋出<code>java.lang.ClassCastException</code>。
     * 若是待比較的兩個貨幣對象的幣種不一樣,則拋出<code>java.lang.IllegalArgumentException</code>。
     * 若是本貨幣對象的金額少於待比較貨幣對象,則返回-1。
     * 若是本貨幣對象的金額等於待比較貨幣對象,則返回0。
     * 若是本貨幣對象的金額大於待比較貨幣對象,則返回1。
     *
     * @param other 另外一對象。
     * @return -1表示小於,0表示等於,1表示大於。
     *
     * @exception ClassCastException 待比較貨幣對象不是<code>Money</code>。
     *            IllegalArgumentException 待比較貨幣對象與本貨幣對象的幣種不一樣。
     *
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    public int compareTo(Object other) {
        return compareTo((Money) other);
    }

    /**
     * 貨幣比較。
     *
     * <p>
     * 比較本貨幣對象與另外一貨幣對象的大小。
     * 若是待比較的兩個貨幣對象的幣種不一樣,則拋出<code>java.lang.IllegalArgumentException</code>。
     * 若是本貨幣對象的金額少於待比較貨幣對象,則返回-1。
     * 若是本貨幣對象的金額等於待比較貨幣對象,則返回0。
     * 若是本貨幣對象的金額大於待比較貨幣對象,則返回1。
     *
     * @param other 另外一對象。
     * @return -1表示小於,0表示等於,1表示大於。
     *
     * @exception IllegalArgumentException 待比較貨幣對象與本貨幣對象的幣種不一樣。
     */
    public int compareTo(Money other) {

        if (cent < other.cent) {
            return -1;
        } else if (cent == other.cent) {
            return 0;
        } else {
            return 1;
        }
    }

    /**
     * 貨幣比較。
     *
     * <p>
     * 判斷本貨幣對象是否大於另外一貨幣對象。
     * 若是待比較的兩個貨幣對象的幣種不一樣,則拋出<code>java.lang.IllegalArgumentException</code>。
     * 若是本貨幣對象的金額大於待比較貨幣對象,則返回true,不然返回false。
     *
     * @param other 另外一對象。
     * @return true表示大於,false表示不大於(小於等於)。
     *
     * @exception IllegalArgumentException 待比較貨幣對象與本貨幣對象的幣種不一樣。
     */
    public boolean greaterThan(Money other) {
        return compareTo(other) > 0;
    }

    // 貨幣算術 ==========================================

    /**
     * 貨幣加法。
     *
     * <p>
     * 若是兩貨幣幣種相同,則返回一個新的相同幣種的貨幣對象,其金額爲
     * 兩貨幣對象金額之和,本貨幣對象的值不變。
     * 若是兩貨幣對象幣種不一樣,拋出<code>java.lang.IllegalArgumentException</code>。
     *
     * @param other 做爲加數的貨幣對象。
     *
     * @exception IllegalArgumentException 若是本貨幣對象與另外一貨幣對象幣種不一樣。
     *
     * @return 相加後的結果。
     */
    public Money add(Money other) {

        return newMoneyWithSameCurrency(cent + other.cent);
    }

    /**
     * 貨幣累加。
     *
     * <p>
     * 若是兩貨幣幣種相同,則本貨幣對象的金額等於兩貨幣對象金額之和,並返回本貨幣對象的引用。
     * 若是兩貨幣對象幣種不一樣,拋出<code>java.lang.IllegalArgumentException</code>。
     *
     * @param other 做爲加數的貨幣對象。
     *
     * @exception IllegalArgumentException 若是本貨幣對象與另外一貨幣對象幣種不一樣。
     *
     * @return 累加後的本貨幣對象。
     */
    public Money addTo(Money other) {

        this.cent += other.cent;

        return this;
    }

    /**
     * 貨幣減法。
     *
     * <p>
     * 若是兩貨幣幣種相同,則返回一個新的相同幣種的貨幣對象,其金額爲
     * 本貨幣對象的金額減去參數貨幣對象的金額。本貨幣對象的值不變。
     * 若是兩貨幣幣種不一樣,拋出<code>java.lang.IllegalArgumentException</code>。
     *
     * @param other 做爲減數的貨幣對象。
     *
     * @exception IllegalArgumentException 若是本貨幣對象與另外一貨幣對象幣種不一樣。
     *
     * @return 相減後的結果。
     */
    public Money subtract(Money other) {

        return newMoneyWithSameCurrency(cent - other.cent);
    }

    /**
     * 貨幣累減。
     *
     * <p>
     * 若是兩貨幣幣種相同,則本貨幣對象的金額等於兩貨幣對象金額之差,並返回本貨幣對象的引用。
     * 若是兩貨幣幣種不一樣,拋出<code>java.lang.IllegalArgumentException</code>。
     *
     * @param other 做爲減數的貨幣對象。
     *
     * @exception IllegalArgumentException 若是本貨幣對象與另外一貨幣對象幣種不一樣。
     *
     * @return 累減後的本貨幣對象。
     */
    public Money subtractFrom(Money other) {

        this.cent -= other.cent;

        return this;
    }

    /**
     * 貨幣乘法。
     *
     * <p>
     * 返回一個新的貨幣對象,幣種與本貨幣對象相同,金額爲本貨幣對象的金額乘以乘數。
     * 本貨幣對象的值不變。
     *
     * @param val 乘數
     *
     * @return 乘法後的結果。
     */
    public Money multiply(long val) {
        return newMoneyWithSameCurrency(cent * val);
    }

    /**
     * 貨幣累乘。
     *
     * <p>
     * 本貨幣對象金額乘以乘數,並返回本貨幣對象。
     *
     * @param val 乘數
     *
     * @return 累乘後的本貨幣對象。
     */
    public Money multiplyBy(long val) {
        this.cent *= val;

        return this;
    }

    /**
     * 貨幣乘法。
     *
     * <p>
     * 返回一個新的貨幣對象,幣種與本貨幣對象相同,金額爲本貨幣對象的金額乘以乘數。
     * 本貨幣對象的值不變。若是相乘後的金額不能轉換爲整數分,則四捨五入。
     *
     * @param val 乘數
     *
     * @return 相乘後的結果。
     */
    public Money multiply(double val) {
        return newMoneyWithSameCurrency(Math.round(cent * val));
    }

    /**
     * 貨幣累乘。
     *
     * <p>
     * 本貨幣對象金額乘以乘數,並返回本貨幣對象。
     * 若是相乘後的金額不能轉換爲整數分,則使用四捨五入。
     *
     * @param val 乘數
     *
     * @return 累乘後的本貨幣對象。
     */
    public Money multiplyBy(double val) {
        this.cent = Math.round(this.cent * val);

        return this;
    }

    /**
     * 貨幣乘法。
     *
     * <p>
     * 返回一個新的貨幣對象,幣種與本貨幣對象相同,金額爲本貨幣對象的金額乘以乘數。
     * 本貨幣對象的值不變。若是相乘後的金額不能轉換爲整數分,使用缺省的取整模式
     * <code>DEFUALT_ROUNDING_MODE</code>進行取整。
     *
     * @param val 乘數
     *
     * @return 相乘後的結果。
     */
    public Money multiply(BigDecimal val) {
        return multiply(val, BigDecimal.ROUND_HALF_EVEN);
    }

    /**
     * 貨幣累乘。
     *
     * <p>
     * 本貨幣對象金額乘以乘數,並返回本貨幣對象。
     * 若是相乘後的金額不能轉換爲整數分,使用缺省的取整方式
     * <code>DEFUALT_ROUNDING_MODE</code>進行取整。
     *
     * @param val 乘數
     *
     * @return 累乘後的結果。
     */
    public Money multiplyBy(BigDecimal val) {
        return multiplyBy(val, BigDecimal.ROUND_HALF_EVEN);
    }

    /**
     * 貨幣乘法。
     *
     * <p>
     * 返回一個新的貨幣對象,幣種與本貨幣對象相同,金額爲本貨幣對象的金額乘以乘數。
     * 本貨幣對象的值不變。若是相乘後的金額不能轉換爲整數分,使用指定的取整方式
     * <code>roundingMode</code>進行取整。
     *
     * @param val 乘數
     * @param roundingMode 取整方式
     *
     * @return 相乘後的結果。
     */
    public Money multiply(BigDecimal val, int roundingMode) {
        BigDecimal newCent = BigDecimal.valueOf(cent).multiply(val);

        return newMoneyWithSameCurrency(rounding(newCent, roundingMode));
    }

    /**
     * 貨幣累乘。
     *
     * <p>
     * 本貨幣對象金額乘以乘數,並返回本貨幣對象。
     * 若是相乘後的金額不能轉換爲整數分,使用指定的取整方式
     * <code>roundingMode</code>進行取整。
     *
     * @param val 乘數
     * @param roundingMode 取整方式
     *
     * @return 累乘後的結果。
     */
    public Money multiplyBy(BigDecimal val, int roundingMode) {
        BigDecimal newCent = BigDecimal.valueOf(cent).multiply(val);

        this.cent = rounding(newCent, roundingMode);

        return this;
    }

    /**
     * 貨幣除法。
     *
     * <p>
     * 返回一個新的貨幣對象,幣種與本貨幣對象相同,金額爲本貨幣對象的金額除以除數。
     * 本貨幣對象的值不變。若是相除後的金額不能轉換爲整數分,使用四捨五入方式取整。
     *
     * @param val 除數
     *
     * @return 相除後的結果。
     */
    public Money divide(double val) {
        return newMoneyWithSameCurrency(Math.round(cent / val));
    }

    /**
     * 貨幣累除。
     *
     * <p>
     * 本貨幣對象金額除以除數,並返回本貨幣對象。
     * 若是相除後的金額不能轉換爲整數分,使用四捨五入方式取整。
     *
     * @param val 除數
     *
     * @return 累除後的結果。
     */
    public Money divideBy(double val) {
        this.cent = Math.round(this.cent / val);

        return this;
    }

    /**
     * 貨幣除法。
     *
     * <p>
     * 返回一個新的貨幣對象,幣種與本貨幣對象相同,金額爲本貨幣對象的金額除以除數。
     * 本貨幣對象的值不變。若是相除後的金額不能轉換爲整數分,使用缺省的取整模式
     * <code>DEFAULT_ROUNDING_MODE</code>進行取整。
     *
     * @param val 除數
     *
     * @return 相除後的結果。
     */
    public Money divide(BigDecimal val) {
        return divide(val, BigDecimal.ROUND_HALF_EVEN);
    }

    /**
     * 貨幣除法。
     *
     * <p>
     * 返回一個新的貨幣對象,幣種與本貨幣對象相同,金額爲本貨幣對象的金額除以除數。
     * 本貨幣對象的值不變。若是相除後的金額不能轉換爲整數分,使用指定的取整模式
     * <code>roundingMode</code>進行取整。
     *
     * @param val 除數
     * @param roundingMode 取整
     *
     * @return 相除後的結果。
     */
    public Money divide(BigDecimal val, int roundingMode) {
        BigDecimal newCent = BigDecimal.valueOf(cent).divide(val, roundingMode);

        return newMoneyWithSameCurrency(newCent.longValue());
    }

    /**
     * 貨幣累除。
     *
     * <p>
     * 本貨幣對象金額除以除數,並返回本貨幣對象。
     * 若是相除後的金額不能轉換爲整數分,使用缺省的取整模式
     * <code>DEFAULT_ROUNDING_MODE</code>進行取整。
     *
     * @param val 除數
     *
     * @return 累除後的結果。
     */
    public Money divideBy(BigDecimal val) {
        return divideBy(val, BigDecimal.ROUND_HALF_EVEN);
    }

    /**
     * 貨幣累除。
     *
     * <p>
     * 本貨幣對象金額除以除數,並返回本貨幣對象。
     * 若是相除後的金額不能轉換爲整數分,使用指定的取整模式
     * <code>roundingMode</code>進行取整。
     *
     * @param val 除數
     *
     * @return 累除後的結果。
     */
    public Money divideBy(BigDecimal val, int roundingMode) {
        BigDecimal newCent = BigDecimal.valueOf(cent).divide(val, roundingMode);

        this.cent = newCent.longValue();

        return this;
    }

    /**
     * 貨幣分配。
     *
     * <p>
     * 將本貨幣對象儘量平均分配成<code>targets</code>份。
     * 若是不能平均分配盡,則將零頭放到開始的若干份中。分配
     * 運算可以確保不會丟失金額零頭。
     *
     * @param targets 待分配的份數
     *
     * @return 貨幣對象數組,數組的長度與分配份數相同,數組元素
     *         從大到小排列,全部貨幣對象的金額最多隻相差1分。
     */
    public Money[] allocate(int targets) {
        Money[] results = new Money[targets];

        Money lowResult = newMoneyWithSameCurrency(cent / targets);
        Money highResult = newMoneyWithSameCurrency(lowResult.cent + 1);

        int remainder = (int) cent % targets;

        for (int i = 0; i < remainder; i++) {
            results[i] = highResult;
        }

        for (int i = remainder; i < targets; i++) {
            results[i] = lowResult;
        }

        return results;
    }

    /**
     * 貨幣分配。
     *
     * <p>
     * 將本貨幣對象按照規定的比例分配成若干份。分配所剩的零頭
     * 從第一份開始順序分配。分配運算確保不會丟失金額零頭。
     *
     * @param ratios 分配比例數組,每個比例是一個長整型,表明
     *               相對於總數的相對數。
     *
     * @return 貨幣對象數組,數組的長度與分配比例數組的長度相同。
     */
    public Money[] allocate(long[] ratios) {
        Money[] results = new Money[ratios.length];

        long total = 0;

        for (int i = 0; i < ratios.length; i++) {
            total += ratios[i];
        }

        long remainder = cent;

        for (int i = 0; i < results.length; i++) {
            results[i] = newMoneyWithSameCurrency((cent * ratios[i]) / total);
            remainder -= results[i].cent;
        }

        for (int i = 0; i < remainder; i++) {
            results[i].cent++;
        }

        return results;
    }

    /**
     * 建立一個幣種相同,具備指定金額的貨幣對象。
     *
     * @param cent 金額,以分爲單位
     *
     * @return 一個新建的幣種相同,具備指定金額的貨幣對象
     */
    protected Money newMoneyWithSameCurrency(long cent) {
        Money money = new Money(0);

        money.cent = cent;

        return money;
    }

    // 格式化方法 =================================================

    /**
     * 生成本對象的缺省字符串表示
     */
    public String toString() {
        return getAmount().toString();
    }

    // 內部方法 ===================================================

    /**
     * 對BigDecimal型的值按指定取整方式取整。
     *
     * @param val 待取整的BigDecimal值
     * @param roundingMode 取整方式
     *
     * @return 取整後的long型值
     */
    protected long rounding(BigDecimal val, int roundingMode) {
        return val.setScale(0, roundingMode).longValue();
    }

    // 調試方式 ==================================================

    /**
     * 生成本對象內部變量的字符串表示,用於調試。
     *
     * @return 本對象內部變量的字符串表示。
     */
    public String dump() {
        String lineSeparator = System.getProperty("line.separator");

        StringBuffer sb = new StringBuffer();

        sb.append("cent = ").append(cent).append(lineSeparator);

        return sb.toString();
    }

    /**
     * 獲取本貨幣對象表明的金額數。
     *
     * @return 金額數,以分爲單位。
     */
    public long getCent() {
        return cent;
    }

    /**
     * 設置貨幣的分值。
     * 
     * @param l
     */
    public void setCent(long l) {
        cent = l;
    }

}

 

測試結果查看:數組

相關文章
相關標籤/搜索