package org.geogebra.common.util; import java.math.BigDecimal; import java.math.BigInteger; import org.apache.commons.math3.exception.MathRuntimeException; import org.apache.commons.math3.linear.AnyMatrix; /* * utilities for "exact" arithmetics * of arbitrary precision * */ public class MyMathExact { public static class MyDecimal { private final int fixedScale; private static final int roundingMode = BigDecimal.ROUND_HALF_EVEN; private final BigDecimal impl; public MyDecimal(int significance) { // super(0); impl = BigDecimal.ZERO.setScale(significance); fixedScale = significance; } public MyDecimal(int significance, double val) { // super(val); // super.setScale(significance, roundingMode); impl = (new BigDecimal(val)).setScale(significance, roundingMode); fixedScale = significance; } public MyDecimal(MyDecimal md) { impl = new BigDecimal(md.unscaledValue(), md.scale()); fixedScale = md.scale(); } @Override public String toString() { return impl.toString(); } private int scale() { return impl.scale(); } private BigInteger unscaledValue() { return impl.unscaledValue(); } public MyDecimal(BigDecimal bd) { impl = new BigDecimal(bd.unscaledValue(), bd.scale()); fixedScale = bd.scale(); } public MyDecimal(int significance, BigDecimal bd) { impl = new BigDecimal(bd.unscaledValue(), bd.scale()) .setScale(significance, roundingMode); fixedScale = significance; } public int getScale() { return fixedScale; } public MyDecimal copy() { return new MyDecimal(this.getScale(), impl); } public MyDecimal negate() { return new MyDecimal(this.getScale(), impl.negate()); } public MyDecimal add(MyDecimal md) { return new MyDecimal(this.getScale(), impl.add(md.getImpl())); } public BigDecimal getImpl() { return impl; } public MyDecimal multiply(MyDecimal md) { return new MyDecimal(this.getScale(), impl.multiply(md.getImpl())); } public MyDecimal subtract(MyDecimal md) { return new MyDecimal(this.getScale(), impl.subtract(md.getImpl())); } public MyDecimal divide(MyDecimal md) { return new MyDecimal(this.getScale(), impl.divide(md.getImpl(), this.getScale(), BigDecimal.ROUND_HALF_EVEN)); } public MyDecimal sqrt() { if (impl.compareTo(BigDecimal.ZERO) == 0) { return new MyDecimal(BigDecimal.ZERO); } MyDecimal TWO = new MyDecimal(BigDecimal.ONE.add(BigDecimal.ONE)); double lower_bound = Math.sqrt(impl.doubleValue()); int thisScale = this.getScale(); int thisScalePlusOne = thisScale + 1; MyDecimal ret = new MyDecimal(thisScalePlusOne, lower_bound); MyDecimal radicand = new MyDecimal(thisScalePlusOne, impl); int iterCount = 0; while (ret.multiply(ret).subtract(radicand).divide(radicand) .divide(new MyDecimal(thisScalePlusOne, lower_bound * 2)) .abs().doubleValue() > Math.pow(10, -thisScale) && iterCount < 5) { ret = ret.add(radicand.divide(ret)).divide(TWO); iterCount++; } return new MyDecimal(thisScale, ret.getImpl()); } public BigDecimal abs() { return impl.abs(); } public double doubleValue() { return impl.doubleValue(); } public int intValue() { return impl.intValue(); } public int signum() { return impl.signum(); } } public static class MyDecimalMatrix implements AnyMatrix { private int fixedScale; private int rowD; private int colD; private MyDecimal[][] data; public MyDecimalMatrix(int significance, int rowD, int colD) { fixedScale = significance; this.rowD = rowD; this.colD = colD; data = new MyDecimal[rowD][colD]; } @Override public boolean isSquare() { return rowD == colD; } @Override public int getRowDimension() { return rowD; } @Override public int getColumnDimension() { return colD; } public int getScale() { return this.fixedScale; } public MyDecimal getEntry(int i, int j) { return data[i][j].copy(); } public void setEntry(int i, int j, MyDecimal md) { data[i][j] = new MyDecimal(fixedScale, md.getImpl()); } public MyDecimalMatrix copy() { MyDecimalMatrix mdm = new MyDecimalMatrix(fixedScale, rowD, colD); for (int i = 0; i < rowD; i++) { for (int j = 0; j < colD; j++) { mdm.setEntry(i, j, data[i][j]); } } return mdm; } public MyDecimal[] getColumn(int j) { MyDecimal[] ret = new MyDecimal[this.getRowDimension()]; for (int i = 0; i < this.getRowDimension(); i++) { ret[i] = this.getEntry(i, j); } return ret; } public void setColumn(int j, MyDecimal[] column) { for (int i = 0; i < this.getRowDimension(); i++) { this.setEntry(i, j, column[i]); } } public MyDecimal[] getRow(int i) { MyDecimal[] ret = new MyDecimal[this.getColumnDimension()]; for (int j = 0; j < this.getColumnDimension(); j++) { ret[j] = this.getEntry(i, j); } return ret; } public void setRow(int i, MyDecimal[] row) { for (int j = 0; j < this.getColumnDimension(); j++) { this.setEntry(i, j, row[j]); } } public MyDecimal frobNormSq(MyDecimalMatrix matrix, int m, int n) { // m is number of rows; n is number of columns MyDecimal ret = new MyDecimal(BigDecimal.ZERO); if (m == 0 || n == 0) { return ret; } for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { ret = ret.add(matrix.getEntry(i, j) .multiply(matrix.getEntry(i, j))); } } return ret; } public MyDecimalMatrix multiply(MyDecimalMatrix m) { if (this.getColumnDimension() != m.getRowDimension()) { throw MathRuntimeException.createIllegalArgumentException( "Cannot multiply " + getRowDimension() + " x " + getColumnDimension() + " and " + m.getRowDimension() + " x " + m.getColumnDimension() + "matrices!"); } MyDecimalMatrix ret = new MyDecimalMatrix(this.getScale(), this.getRowDimension(), m.getColumnDimension()); for (int i = 0; i < this.getRowDimension(); i++) { for (int j = 0; j < m.getColumnDimension(); j++) { MyDecimal entry = new MyDecimal(this.fixedScale, 0); for (int k = 0; k < this.getColumnDimension(); k++) { entry = entry.add( this.getEntry(i, k).multiply(m.getEntry(k, j))); } ret.setEntry(i, j, entry); } } return ret; } } }