白猫のメモ帳

C#とかJavaとかJavaScriptとかHTMLとか機械学習とか。

Javaで遅延計算

丸めのタイミングって結構問題になることが多くて、
あとから設定できたらいいなと思ったりする。

あとついでにそこまでの計算結果が見えたらいいのになとか思って作ってみたもの。

package practice;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.Map;

/**
 * 遅延計算BigDecimal
 */
public class LazyBigDecimal {

	/** 演算子の左側 */
	private LazyBigDecimal left = null;

	/** 演算子の右側 */
	private LazyBigDecimal right = null;

	/** 演算子 */
	private Operator operator = null;

	/***/
	private BigDecimal value = null;

	/** セーブポイント用キー */
	private String savekey = null;

	/**
	 * コンストラクタ
	 */
	public <T extends Number> LazyBigDecimal(T value) {
		this.value = new BigDecimal(value.toString());
	}

	/**
	 * コンストラクタ
	 */
	private LazyBigDecimal(LazyBigDecimal left, Operator operator, LazyBigDecimal right) {
		this.left = left;
		this.operator = operator;
		this.right = right;
	}

	/**
	 * コンストラクタ
	 */
	public LazyBigDecimal(LazyBigDecimal value) {
		this.left = value;
	}

	/**
	 * 加算
	 */
	public <T extends Number> LazyBigDecimal add(T val) {
		return this.add(new LazyBigDecimal(val));
	}

	/**
	 * 加算
	 */
	public LazyBigDecimal add(LazyBigDecimal val) {
		return new LazyBigDecimal(this, Operator.ADD, val);
	}

	/**
	 * 減算
	 */
	public <T extends Number> LazyBigDecimal subtract(T val) {
		return this.subtract(new LazyBigDecimal(val));
	}

	/**
	 * 減算
	 */
	public LazyBigDecimal subtract(LazyBigDecimal val) {
		return new LazyBigDecimal(this, Operator.SUBTRACT, val);
	}

	/**
	 * 乗算
	 */
	public <T extends Number> LazyBigDecimal multiply(T val) {
		return this.multiply(new LazyBigDecimal(val));
	}

	/**
	 * 乗算
	 */
	public LazyBigDecimal multiply(LazyBigDecimal val) {
		return new LazyBigDecimal(this, Operator.MULTIPLY, val);
	}

	/**
	 * 除算
	 */
	public <T extends Number> LazyBigDecimal divide(T val) {
		return this.divide(new LazyBigDecimal(val));
	}

	/**
	 * 除算
	 */
	public LazyBigDecimal divide(LazyBigDecimal val) {
		return new LazyBigDecimal(this, Operator.DIVIDE, val);
	}

	/**
	 * 冪乗
	 */
	public <T extends Number> LazyBigDecimal pow(T val) {
		return this.pow(new LazyBigDecimal(val));
	}

	/**
	 * 冪乗
	 */
	public LazyBigDecimal pow(LazyBigDecimal val) {
		return new LazyBigDecimal(this, Operator.POW, val);
	}

	/**
	 * 剰余
	 */
	public <T extends Number> LazyBigDecimal remainder(T val) {
		return this.remainder(new LazyBigDecimal(val));
	}

	/**
	 * 剰余
	 */
	public LazyBigDecimal remainder(LazyBigDecimal val) {
		return new LazyBigDecimal(this, Operator.REMAINDER, val);
	}

	/**
	 * 計算
	 */
	public BigDecimal eval() {
		return this.eval(Collections.<String, Scale>emptyMap());
	}

	/**
	 * 計算
	 */
	public BigDecimal eval(Map<String, Scale> scaleMap) {

		// 演算子の左側が式の場合には計算する、値であればそのまま返す
		BigDecimal left = (this.left == null ? this.value : this.left.eval(scaleMap));

		// 演算子の右側がない場合には左側を返す
		if (this.right == null) {
			return left;

		// 演算子の右側がある場合には左側と右側を計算して返す
		} else {
			return this.operator.eval(left, this.right.eval(scaleMap), scaleMap.get(this.savekey));
		}
	}

	/**
	 * セーブポイントを設定
	 *
	 * <pre>
	 * 設定しておくと、その部分までの式をデバッグできる。
	 * 丸める可能性がある場合には設定しておく。
	 * 対応する丸め設定がなければ無視される。
	 * 同じキーで登録すればスケールの使いまわしは可。
	 * </pre>
	 */
	public LazyBigDecimal setSavePoint(String savekey) {
		this.savekey = savekey;
		return this;
	}

	/**
	 * 文字列表現
	 */
	@Override
	public String toString() {

		// かっこが必要か判定
		boolean isBracketLeft = this.left != null && this.operator.isBracketLeft(this.left);
		boolean isBracketRight = this.right != null && this.operator.isBracketRight(this.right);

		StringBuilder sb = new StringBuilder();

		// 演算子の左側
		sb.append(isBracketLeft ? "(" : "");
		sb.append(this.left == null ? this.value.toPlainString() : this.left.toString());
		sb.append(isBracketLeft ? ")" : "");

		// 演算子
		sb.append(this.operator == null ? "" : this.operator.toString());

		// 演算子の右側
		sb.append(isBracketRight ? "(" : "");
		sb.append(this.right == null ? "" : this.right.toString());
		sb.append(isBracketRight ? ")" : "");

		return sb.toString();
	}

	/**
	 * デバッグ用
	 */
	public String toStringDebug(Map<String, Scale> scaleMap) {

		StringBuilder sb = new StringBuilder();

		// 演算子の左側と右側を出力
		sb.append(this.left == null ? "" : this.left.toStringDebug(scaleMap));
		sb.append(this.right == null ? "" : this.right.toStringDebug(scaleMap));

		// セーブポイントの場合
		if (this.savekey != null) {

			// セーブポイント名
			sb.append("セーブポイント:").append(this.savekey).append("\n");

			// 式と結果を出力
			sb.append(this).append("=").append(this.eval(scaleMap)).append("  \n");

			// 丸めがあれば丸め設定を出力
			if (scaleMap.get(this.savekey) != null) {
				sb.append(scaleMap.get(this.savekey)).append("\n");
			}

			// 区切り
			sb.append("------------------------------------------------------\n");
		}

		return sb.toString();
	}

	/**
	 * 演算子
	 */
	private enum Operator {

		/** 足す */
		ADD("+") {
			@Override
			BigDecimal eval(BigDecimal left, BigDecimal right, Scale scale) {
				return super.setScale(left.add(right), scale);
			}
		}

		/** 引く */
		, SUBTRACT("-") {
			@Override
			BigDecimal eval(BigDecimal left, BigDecimal right, Scale scale) {
				return super.setScale(left.subtract(right), scale);
			}
			@Override
			boolean isBracketRight(LazyBigDecimal right) {
				return right.operator == ADD || right.operator == SUBTRACT;
			}
		}

		/** 掛ける */
		, MULTIPLY("*") {
			@Override
			BigDecimal eval(BigDecimal left, BigDecimal right, Scale scale) {
				return super.setScale(left.multiply(right), scale);
			}
			@Override
			boolean isBracketLeft(LazyBigDecimal left) {
				return left.operator == ADD || left.operator == SUBTRACT;
			}
			@Override
			boolean isBracketRight(LazyBigDecimal right) {
				return right.operator == ADD || right.operator == SUBTRACT;
			}
		}

		/** 割る */
		, DIVIDE("/") {
			@Override
			BigDecimal eval(BigDecimal left, BigDecimal right, Scale scale) {
				return scale != null ? left.divide(right, scale.scale, scale.roundingMode) : left.divide(right);
			}
			@Override
			boolean isBracketLeft(LazyBigDecimal left) {
				return left.operator == ADD || left.operator == SUBTRACT;
			}
			@Override
			boolean isBracketRight(LazyBigDecimal right) {
				return right.operator != null;
			}
		}

		/** 累乗 */
		, POW("^") {
			@Override
			BigDecimal eval(BigDecimal left, BigDecimal right, Scale scale) {
				return super.setScale(left.pow(right.intValue()), scale);
			}
			@Override
			boolean isBracketLeft(LazyBigDecimal left) {
				return left.operator != POW;
			}
			@Override
			boolean isBracketRight( LazyBigDecimal right) {
				return right.operator != POW;
			}
		}

		/** 余り */
		, REMAINDER("%") {
			@Override
			BigDecimal eval(BigDecimal left, BigDecimal right, Scale scale) {
				return super.setScale(left.remainder(right), scale);
			}
			@Override
			boolean isBracketLeft(LazyBigDecimal left) {
				return left.operator == ADD || left.operator == SUBTRACT;
			}
			@Override
			boolean isBracketRight(LazyBigDecimal right) {
				return right.operator == ADD || right.operator == SUBTRACT;
			}
		}
		;

		/** 演算子(文字列表現) */
		private String operator;

		/**
		 * コンストラクタ
		 */
		private Operator(String operator) {
			this.operator = operator;
		}

		/** 計算 */
		abstract BigDecimal eval(BigDecimal left, BigDecimal right, Scale scale);

		/** 丸め処理 */
		BigDecimal setScale(BigDecimal eval, Scale scale) {
			return scale != null ? eval.setScale(scale.scale, scale.roundingMode) : eval;

		}

		/** 演算子の左側に括弧が必要か */
		boolean isBracketLeft(LazyBigDecimal left) {
			return false;
		}

		/** 演算子の右側に括弧が必要か */
		boolean isBracketRight(LazyBigDecimal right) {
			return false;
		}

		/**
		 * 文字列表現
		 */
		@Override
		public String toString() {
			return this.operator;
		}
	}

	/**
	 * スケールを作成
	 */
	public static Scale scaleOf(int scale, RoundingMode roundingMode) {
		return new Scale(scale, roundingMode);
	}

	/**
	 * スケール
	 */
	public static class Scale {

		/** スケール */
		private int scale;

		/** 丸めモード */
		private RoundingMode roundingMode;

		/**
		 * コンストラクタ
		 */
		Scale(int scale, RoundingMode roundingMode) {
			this.scale = scale;
			this.roundingMode = roundingMode;
		}

		/**
		 * 文字列表現
		 */
		@Override
		public String toString() {
			return "スケール:" + scale + ", " + "丸めモード:" + roundingMode;
		}
	}
}

こんな風に使うと、

public static void main(String... args) {

		// 初期化
		LazyBigDecimal lbd = new LazyBigDecimal(3);
		System.out.println(lbd);

		// 足してみる
		lbd = lbd.add(4);
		System.out.println(lbd);

		// 引いてみる
		lbd = lbd.subtract(2);
		System.out.println(lbd);

		// 掛けてみる
		lbd = lbd.multiply(1.33);
		lbd.setSavePoint("セーブポイント1");
		System.out.println(lbd);

		// 割ってみる
		lbd = lbd.divide(0.73);
		lbd.setSavePoint("セーブポイント2");
		System.out.println(lbd);

		// 計算してみると除算に丸め設定をしていないので例外になる
//		System.out.println(lbd.eval());

		// 丸め設定を作る
		Map<String, Scale> scaleMap = new HashMap<String, Scale>();
		scaleMap.put("セーブポイント1", LazyBigDecimal.scaleOf(0, RoundingMode.DOWN));
		scaleMap.put("セーブポイント2", LazyBigDecimal.scaleOf(1, RoundingMode.HALF_UP));

		// 丸め設定を使って計算する
		System.out.println(lbd.eval(scaleMap));

		// 丸め設定を使ってデバッグする
		System.out.println(lbd.toStringDebug(scaleMap));
	}

こんな結果になったりする。

3
3+4
3+4-2
(3+4-2)*1.33
(3+4-2)*1.33/0.73
8.2
セーブポイント:セーブポイント1
(3+4-2)*1.33=6  
スケール:0, 丸めモード:DOWN
------------------------------------------------------
セーブポイント:セーブポイント2
(3+4-2)*1.33/0.73=8.2  
スケール:1, 丸めモード:HALF_UP
------------------------------------------------------

使えるのかというと知らない…。