DecimalNumber.java

package com.github.mygreen.cellformatter.number;

import java.math.RoundingMode;
import java.text.DecimalFormat;

import com.github.mygreen.cellformatter.ConditionNumberFormatter;


/**
 * 小数表現を含む数値を表現するクラス。
 * <p>{@link ConditionNumberFormatter}でフォーマットする際に利用する。
 * 
 * @author T.TSUCHIE
 *
 */
public class DecimalNumber extends FormattedNumber {
    
    /**
     * 精度(小数の精度)
     * ・小数を持たない場合は、ゼロを設定する。
     */
    protected final int scale;
    
    /**
     * 整数部分。
     * ・0以下の場合は、空文字。
     * ・符号はつかない。
     */
    protected String integerPart;
    
    /**
     * 小数部分。
     * ・精度が0以上場合は、空文字。
     */
    protected String decimalPart;
    
    /**
     * 千分率(パーミル)の次数。
     * ・0のとき、何もしない。
     * ・1のとき、1000で割る。
     * ・2のとき、1000^2で割る。
     */
    protected int permilles;
    
    public DecimalNumber(final double value, final int scale, final int permilles) {
        super(value);
        this.scale = scale > 0 ? scale : 0;
        this.permilles = permilles > 0 ? permilles : 0;
        
        init();
    }
    
    public DecimalNumber(final double value, final int scale) {
        this(value, scale, 0);
    }
    
    /**
     * 値を精度に従い整数部と小数部に分解する。
     */
    protected void init() {
        
        if(isZero()) {
            this.integerPart = "";
            this.decimalPart = "";
            return;
        }
        
        final StringBuilder sb = new StringBuilder();
        sb.append("#");
        if(scale > 0) {
            sb.append(".");
        }
        
        // 小数の精度分、書式を追加する。
        for(int i=0; i < scale; i++) {
            sb.append("#");
        }
        
        final DecimalFormat format = new DecimalFormat(sb.toString());
        format.setRoundingMode(RoundingMode.HALF_UP);
        
        double num = Math.abs(getValue());
        if(getPermilles() > 0) {
            num /= Math.pow(1000, getPermilles());
        }
        
        String str = format.format(num);
        
        // 数値を小数部と整数部に分割する。
        setupIntegerAndDecimalPart(str);
        
    }
    
    /**
     * 文字列形式の数値を整数部と小数部に分割し、各フィールドに設定する
     * @param str
     */
    protected void setupIntegerAndDecimalPart(final String str) {
        
        final int dotIdx = str.indexOf(".");
        if(dotIdx < 0) {
            // 整数部のみの場合
            this.integerPart = str;
            this.decimalPart = "";
        } else {
            this.integerPart = str.substring(0, dotIdx);
            if(this.integerPart.equals("0")) {
                this.integerPart = "";
            }
            
            this.decimalPart = str.substring(dotIdx+1);
            
        }
    }
    
    /**
     * 小数の精度を取得する。
     * ・小数を持たない場合は、0を返す。
     * @return
     */
    public int getScale() {
        return scale;
    }
    
    /**
     * 整数部のフォーマットした値を取得する。
     * @return
     */
    public String getIntegerPart() {
        return integerPart;
    }
    
    /**
     * 整数部の指定した桁の値を取得する。
     * @param digit 1から始まる。
     * @return その桁の値がない場合は空文字を返す。
     */
    public String getIntegerPart(final int digit) {
        
        final int length = integerPart.length();
        if(length < digit || digit <= 0) {
            return "";
        }
        
        String num = String.valueOf(integerPart.charAt(length - digit));
        if(isUseSeparator()) {
            if(digit >= 4 && (digit-1) % 3 == 0) {
                num += ",";
            }
        }
        
        return num;
        
    }
    
    /**
     * 整数部の指定した桁以降の値を取得する。
     * @param digit 1から始まる。
     * @return その桁の値がない場合は空文字を返す。
     */
    public String getIntegerPartAfter(final int digit) {
        
        final int length = integerPart.length();
        if(length < digit || digit <= 0) {
            return "";
        }
        
        String num = integerPart.substring(0, (length - digit + 1));
        if(isUseSeparator() && digit >= 4) {
            // 区切り文字を入れるために分割する。
            StringBuilder sb = new StringBuilder();
            for(int i=0; i < num.length(); i++) {
                char c = num.charAt(i);
                sb.append(c);
                
                // 現在の処理中の桁数
                int itemDigit = length -i;
                if((itemDigit >= 3) && (itemDigit -1) % 3 == 0) {
                    sb.append(",");
                }
            }
            
            num = sb.toString();
        }
        
        return num;
        
    }
    
    /**
     * 小数部のフォーマットした値を取得する。
     * @return
     */
    public String getDecimalPart() {
        return decimalPart;
    }
    
    /**
     * 小数部の指定した桁の値を取得する。
     * @param digit 1から始まる。
     * @return その桁の値がない場合は空文字を返す。
     */
    public String getDecimalPart(final int digit) {
        
        final int length = decimalPart.length();
        if(length < digit || digit <= 0) {
            return "";
        }
        
        return String.valueOf(decimalPart.charAt(digit-1));
        
    }
    
    
    
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        
        if(isNegative()) {
            sb.append("-");
        }
        
        if(getIntegerPart().isEmpty()) {
            sb.append("0");
        }
        
        if(!getDecimalPart().isEmpty()) {
            sb.append(".").append(getDecimalPart());
        }
        
        return sb.toString();
    }
    
    /**
     * 千分率(パーミル)の次数を取得する
     * @return
     */
    public int getPermilles() {
        return permilles;
    }
    
}