API Docs for: 1.0.0
Show:

File: nablarch-js-util-bigdecimal\ui_public\js\nablarch\util\BigDecimal.js

/**
 * @module nablarch.util
 */
define(function () {

  "use strict";
  
  function BigDecimal(value, /* option */ scale) {
    if (overflow(value)) {
      return BigDecimal.NaN;
    }

    // 非数値の場合は、BigDecimal.NaNとする
    if (!isNumber(value)) {
      return BigDecimal.NaN;
    }

    /** 文字列に変換した値 */
    this._value = value.toString();

    /** スケール(スケールが指定されない場合は、値を元に導出する) */
    this._scale = isNumber(scale) ? Math.floor(scale) : getScale(this._value);
  }

  /**
   * Javascriptによる簡易BigDecimal実装
   * ------------------------------------
   * 
   * **許容される10進桁数**  
   * javascriptの数値(number)は全て浮動小数点によって内部的に保持される。
   * このBigDecimalの実装では、数値部分をjavascriptのnumberとして保持するので、
   * 数値部分の計算で浮動小数の実数部の最大値(=2^53)を超過すると精度が下がり、
   * 計算結果がずれる。
   * 
   * このため、計算結果の数値部分が一定の桁数を越えた場合はNaNを返す。
   * log(2^53) = 15.95 なので、デフォルトの設定値では10^15未満の数(15桁の)数を許容する。
   * 
   * @class nablarch.util.BigDecimal
   * @author Hisaaki Sioiri
   * @since 1.2
   */
  BigDecimal.prototype = {
    /**
     * コンストラクタ。
     * @param {Number} value 値。(数値以外の値が指定された場合はNaNに変換する。)
     * @param {Number} scale スケール(数値以外の値が指定された場合は、0に変換する。)
     * @constructor
     */  
    constructor: BigDecimal,
    /**
     * 加算を行う。
     * 
     * @method add
     * @param {Number|String} value 加算する値
     * @param {Number|String} [scale] 加算後のスケール  
     * (未指定の場合は、このオブジェクトのスケールとaddValueのスケールの大きいスケールが適用される。)
     * @return {BigDecimal} 加算後の値
     */    
    add:BigDecimal_add,
    /**
     * 減算を行う。
     * @method subtract
     * @param {Number|String} value 減算する値
     * @param {Number|String} [scale] 減算後のスケール  
     * (未指定の場合は、このオブジェクトのスケールとaddValueのスケールの大きいスケールが適用される。)
     * @return {BigDecimal} 減算後の値
     */
    subtract:BigDecimal_subtract,
    /**
     * 乗算を行う。
     * @method multiply
     * @param {Number|String} value 乗算する値
     * @param {Number|String} [scale] 乗算後のスケール  
     * (未指定の場合は、このオブジェクトのスケールとaddValueのスケールの大きいスケールが適用される。)
     * @return {BigDecimal} 乗算後の値
     */
    multiply:BigDecimal_multiply,
    /**
     * 文字列に変換する。
     * @method toString
     * @return {String} 変換後の文字列
     */    
    toString:BigDecimal_toString,
    /**
     * 数値に変換する。
     * @method toNumber
     * @return {Number} 変換後の数値
     */
    toNumber:BigDecimal_toNumber
  };
  
  /**
   * 最大数値
   * @property maxDigit
   * @static
   * @final
   */
  BigDecimal.maxDigit =  1000 * 1000 * 1000 * 1000 * 1000 - 1; // 最大数値
  
  /**
   * 最小数値
   * @property minDigit
   * @static
   * @final
   */  
  BigDecimal.minDigit = -1000 * 1000 * 1000 * 1000 * 1000 + 1; // 最小数値
  
  function overflow(digit) {
    if (typeof digit === "string") {
      digit = toInteger(digit);
    }
    return (digit > BigDecimal.maxDigit)
        || (digit < BigDecimal.minDigit);
  }
  
  /**
   *  Not A Number
   *  @class nablarch.util.BigDecimal.NaN
   *  @final
   *  @for nablarch.util.BigDecimal
   */
  BigDecimal.NaN = new BigDecimal(0,0);
  BigDecimal.NaN.add = function() {return this;};
  BigDecimal.NaN.subtract = function() {return this;};
  BigDecimal.NaN.multiply = function() {return this;};
  BigDecimal.NaN.toString = function() {return "NaN";};
  BigDecimal.NaN.toNumber = function() {return Number.NaN;};
  
  return BigDecimal;

  function BigDecimal_add(value, /* option */ scale) {
    return expression(this, value, scale,
        function (v1, v2) {
          return v1 + v2;
        },
        function (scale) {
          return scale;
        });
  }

  function BigDecimal_subtract(value, /* option */ scale) {
    return expression(this, value, scale,
        function (v1, v2) {
          return v1 - v2;
        },
        function (scale) {
          return scale;
        });
  }

  function BigDecimal_multiply(value, /* option */ scale) {
    return expression(this, value, scale,
        function (v1, v2) {
          return v1 * v2;
        }, function (scale) {
          return scale * 2;
        });
  }

  function BigDecimal_toString() {
    var str = this._value,
    sign = "";
    if (str.substring(0,1) === "-"){
      str = str.substring(1);
      if (str.match(/[^0.]/)){
        sign = "-";
      }
    }
    var split = str.split(".");
    split[0] = split[0].length == 0 ? "0" : split[0];
    if (this._scale === 0) {
      return sign + split[0];
    } else if (split.length === 1) {
      return sign + split[0] + "." + addZero("", this._scale);
    } else if (split[1].length < this._scale) {
      return sign + split[0] + "." + addZero(split[1], this._scale);
    } else {
      return sign + split[0] + "." + split[1].substring(0, this._scale);
    }
  }

  function BigDecimal_toNumber() {
    return Number(this.toString());
  }

  /**
   * 計算を行う。
   * @param value1 計算対象の値1
   * @param value2 計算対象の値2
   * @param scale 計算時のスケール
   * @param expressionInner 計算処理を行う関数
   * @return {BigDecimal} 計算後の値
   */
  function expression(value1, value2, scale, expressionInner, newScale) {
    var thisNumber,
        addNumber,
        maxScale,
        resultNumber;

    if (!(value2 instanceof BigDecimal)) {
      value2 = new BigDecimal(value2);
    }

    if (value2 === BigDecimal.NaN) {
      return BigDecimal.NaN;
    }
    maxScale = Math.max(value1._scale, value2._scale);
    thisNumber   = toInteger(value1._value, maxScale);
    addNumber    = toInteger(value2._value, maxScale);
    if (overflow(thisNumber) || overflow(addNumber)) {
      return BigDecimal.NaN;
    }
    resultNumber = expressionInner(thisNumber, addNumber);
    if (overflow(resultNumber)) {
      return BigDecimal.NaN;
    }
    return new BigDecimal(toBigDecimalString(resultNumber, newScale(maxScale)),
        isNumber(scale) ? scale : maxScale);
  }

  /**
   * 文字列値からスケールを導出する。
   * @param value 値
   * @return {Number} スケール
   */
  function getScale(value) {
    if (value.indexOf(".") === -1) {
      return 0;
    }
    return value.split(".")[1].length;
  }

  /**
   * スケール分桁をずらした数値に変換する。
   * @param value 値
   * @param scale スケール
   * @return {Number} 変換後の値
   */
  function toInteger(value, scale) {
    var split = value.split(".");
    if (split.length === 1) {
      return Number(value + addZero("", scale));
    }
    if (split[1].length >= scale) {
      return Number(split[0] + split[1].substring(0, scale));
    } else {
      return Number(split[0] + addZero(split[1], scale));
    }
  }

  /**
   * 指定されたスケールを持つ数値文字列に変換する。
   * 
   * 数値部が BigDecimal.maxDigit を超過した場合は"NaN" を返す。
   * 
   * @param value 値
   * @param scale スケール
   * @return {String}
   */
  function toBigDecimalString(value, scale) {
    var str = value.toString();
    if (scale === 0) {
      return str;
    }
    var sign = "";
    if (str.substring(0,1) === "-"){
      str = str.substring(1);
      if (str !== "0"){
        sign = "-";
      }
    }
    if (str.length <= scale){
      str = addZero("", scale - str.length + 1) + str;
    }
    return sign + str.substring(0, str.length - scale) + "." + str.substring(str.length - scale);
  }

  /**
   * 指定した長さになるように末尾に「0」を付加する。
   * @param value 値
   * @param length 長さ
   * @return {String} 指定された長さの値
   */
  function addZero(value, length) {
    var i,
        ret = value;
    for (i = ret.length; i < length; i += 1) {
      ret += "0";
    }
    return ret;
  }

  /**
   * 指定した値が数値か否か。
   * @param value 値
   * @return {Boolean} 数値の場合は、true
   */
  function isNumber(value) {
    if (value === undefined) {
      return false;
    }
    if (value.toString().length === 0) {
      return false;
    }
    return !isNaN(value);
  }
});