/* XLogo4Schools - A Logo Interpreter specialized for use in schools, based on XLogo by Loic Le Coq * Copyright (C) 2013 Marko Zivkovic * * Contact Information: marko88zivkovic at gmail dot com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. This program is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. You should have received a copy of the * GNU General Public License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * * This Java source code belongs to XLogo4Schools, written by Marko Zivkovic * during his Bachelor thesis at the computer science department of ETH Zurich, * in the year 2013 and/or during future work. * * It is a reengineered version of XLogo written by Loic Le Coq, published * under the GPL License at http://xlogo.tuxfamily.org/ * * Contents of this file were initially written by Loic Le Coq, * modifications, extensions, refactorings might have been applied by Marko Zivkovic */ package xlogo.kernel; import java.math.MathContext; import java.math.BigDecimal; import java.math.BigInteger; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.Stack; import xlogo.Logo; import xlogo.utils.Utils; public class MyCalculator { private final BigDecimal tenth = new BigDecimal(0.1); private MathContext mc = null; // If precision is lesser than 16 (operation for double) /** * @uml.property name="lowPrecision" */ private boolean lowPrecision = true; /** * Indicates if the log table have been created * * @uml.property name="initLogTable" */ private boolean initLogTable; /** * This is a table containing all BigDecimal ln(1+10^ (-k) ), k in * {0,1....,digits-1} This are constants for the Cordic method to calculate * ln, exp * * @uml.property name="logTable" multiplicity="(0 -1)" dimension="1" */ private BigDecimal[] logTable; /** * Indicates if the trigonometric table have been created * * @uml.property name="initCosTable" */ private boolean initCosTable; /** * This is a table containing all BigDecimal arctan 10^ (-k) , k in * {0,1....,digits-1} This are constants for the Cordic method to calculate * trigonometric functions * * @uml.property name="cosTable" multiplicity="(0 -1)" dimension="1" */ private BigDecimal[] cosTable; private static int digits; protected MyCalculator(int digits) { MyCalculator.digits = digits; initLogTable = false; initCosTable = false; if (digits < 16) { mc = new MathContext(16); lowPrecision = true; logTable = new BigDecimal[16]; cosTable = new BigDecimal[16]; } else { mc = new MathContext(digits); lowPrecision = false; logTable = new BigDecimal[digits]; cosTable = new BigDecimal[digits]; } } /** * Return The exponential of s according to matContext Precision * * @param s * The number * @return Exp(s) * @throws LogoError * if s isn't a number */ protected String exp(String s) throws LogoError { if (lowPrecision) { double nombre = numberDouble(s); return teste_fin_double(Math.exp(nombre)); } else { BigDecimal bd = numberDecimal(s); return expBD(bd).toPlainString(); } } /** * Return The logarithm of s according to matContext Precision * * @param s * The number * @return log(s) * @throws LogoError * if s isn't a number or negative */ protected String log(String s) throws LogoError { if (lowPrecision) { double nombre = numberDouble(s); if (nombre < 0 || nombre == 0) { String log = Utils.primitiveName("arithmetic.log"); throw new LogoError(log + " " + Logo.messages.getString("attend_positif")); } return teste_fin_double(Math.log(nombre)); } else { BigDecimal bd = numberDecimal(s); if (bd.signum() != 1) { String log = Utils.primitiveName("arithmetic.log"); throw new LogoError(log + " " + Logo.messages.getString("attend_positif")); } return logBD(bd).toPlainString(); } } /** * Return The square root of s according to matContext Precision * * @param s * The number * @return sqrt(s) * @throws LogoError * if s isn't a number or negative */ protected String sqrt(String s) throws LogoError { if (lowPrecision) { double number = numberDouble(s); if (number < 0) { String sqrt = Utils.primitiveName("arithmetic.racine"); throw new LogoError(sqrt + " " + Logo.messages.getString("attend_positif")); } return teste_fin_double(Math.sqrt(number)); } else { BigDecimal bd = numberDecimal(s); if (bd.signum() == -1) { String sqrt = Utils.primitiveName("arithmetic.racine"); throw new LogoError(sqrt + " " + Logo.messages.getString("attend_positif")); } return sqrtBD(bd).toPlainString(); } } /** * Return the product of all elements in stack param * * @param param * The stack of operands * @return The product * @throws LogoError */ protected String multiply(Stack param) throws LogoError { int size = param.size(); BigDecimal product = BigDecimal.ONE; BigDecimal a; for (int i = 0; i < size; i++) { a = numberDecimal(param.get(i)); product = product.multiply(a, mc); } return product.stripTrailingZeros().toPlainString(); } protected String divide(Stack param) throws LogoError { if (lowPrecision) { double a = numberDouble(param.get(0)); double b = numberDouble(param.get(1)); if (b == 0) throw new LogoError(Logo.messages.getString("division_par_zero")); return (teste_fin_double(a / b)); } else { BigDecimal a = new BigDecimal(param.get(0), mc); BigDecimal b = new BigDecimal(param.get(1), mc); if (b.signum() == 0) throw new LogoError(Logo.messages.getString("division_par_zero")); return (a.divide(b, mc).stripTrailingZeros().toPlainString()); } } /** * Return the sum of all elements in stack param * * @param param * The stack of operands * @return The sum * @throws LogoError */ protected String add(Stack param) throws LogoError { int size = param.size(); BigDecimal sum = BigDecimal.ZERO; BigDecimal a; for (int i = 0; i < size; i++) { a = numberDecimal(param.get(i)); sum = sum.add(a, mc); } return sum.stripTrailingZeros().toPlainString(); } protected String inf(Stack param) throws LogoError { BigDecimal a = numberDecimal(param.get(0)); BigDecimal b = numberDecimal(param.get(1)); if (a.compareTo(b) < 0) return Logo.messages.getString("vrai"); return Logo.messages.getString("faux"); } protected String sup(Stack param) throws LogoError { BigDecimal a = numberDecimal(param.get(0)); BigDecimal b = numberDecimal(param.get(1)); if (a.compareTo(b) > 0) return Logo.messages.getString("vrai"); return Logo.messages.getString("faux"); } protected String infequal(Stack param) throws LogoError { BigDecimal a = numberDecimal(param.get(0)); BigDecimal b = numberDecimal(param.get(1)); if (a.compareTo(b) <= 0) return Logo.messages.getString("vrai"); return Logo.messages.getString("faux"); } protected String supequal(Stack param) throws LogoError { BigDecimal a = numberDecimal(param.get(0)); BigDecimal b = numberDecimal(param.get(1)); if (a.compareTo(b) >= 0) return Logo.messages.getString("vrai"); return Logo.messages.getString("faux"); } protected String equal(Stack param) throws LogoError { BigDecimal a = numberDecimal(param.get(0)); BigDecimal b = numberDecimal(param.get(1)); if (a.compareTo(b) == 0) return Logo.messages.getString("vrai"); return Logo.messages.getString("faux"); } protected String substract(Stack param) throws LogoError { BigDecimal a = numberDecimal(param.get(0)); BigDecimal b = numberDecimal(param.get(1)); return a.subtract(b, mc).stripTrailingZeros().toPlainString(); } /** * Returns the opposite of s * * @param s * @return */ protected String minus(String s) throws LogoError { BigDecimal a = numberDecimal(s); return a.negate(mc).stripTrailingZeros().toPlainString(); } protected String remainder(String a, String b) throws LogoError { if (lowPrecision) { int aa = getInteger(a); int bb = getInteger(b); if (bb == 0) throw new LogoError(Logo.messages.getString("division_par_zero")); return teste_fin_double(aa % bb); } else { BigDecimal aa = getBigInteger(a); BigDecimal bb = getBigInteger(b); if (bb.signum() == 0) throw new LogoError(Logo.messages.getString("division_par_zero")); return aa.remainder(bb, mc).stripTrailingZeros().toPlainString(); } } protected String modulo(String a, String b) throws LogoError { if (lowPrecision) { int aa = getInteger(a); int bb = getInteger(b); if (bb == 0) throw new LogoError(Logo.messages.getString("division_par_zero")); double rem = aa % bb; if (aa * bb < 0 && rem != 0) rem = rem + bb; return teste_fin_double(rem); } else { BigDecimal aa = getBigInteger(a); BigDecimal bb = getBigInteger(b); if (bb.signum() == 0) throw new LogoError(Logo.messages.getString("division_par_zero")); BigDecimal rem = aa.remainder(bb, mc); if (aa.multiply(bb).compareTo(BigDecimal.ZERO) == -1 && (!rem.equals(BigDecimal.ZERO))) rem = rem.add(bb); return rem.stripTrailingZeros().toPlainString(); } } protected String quotient(String a, String b) throws LogoError { if (lowPrecision) { double aa = numberDouble(a); double bb = numberDouble(b); if (bb == 0) throw new LogoError(Logo.messages.getString("division_par_zero")); return String.valueOf((int) (aa / bb)); } else { BigDecimal aa = numberDecimal(a); BigDecimal bb = numberDecimal(b); if (bb.signum() == 0) throw new LogoError(Logo.messages.getString("division_par_zero")); return aa.divideToIntegralValue(bb, mc).stripTrailingZeros().toPlainString(); } } protected String truncate(String a) throws LogoError { BigDecimal ent = numberDecimal(a); return ent.toBigInteger().toString(); } protected String abs(String a) throws LogoError { BigDecimal e = numberDecimal(a); return e.abs().stripTrailingZeros().toPlainString(); } protected String power(String a, String b) throws LogoError { if (lowPrecision) { double p = Math.pow(numberDouble(a), numberDouble(b)); // Bug pr power -1 0.5 Double p1 = new Double(p); if (p1.equals(Double.NaN)) throw new LogoError(Utils.primitiveName("arithmetic.puissance") + " " + Logo.messages.getString("attend_positif")); // End Bug return teste_fin_double(p); } else { // if the exposant is an integer try { int n = Integer.parseInt(b); BigDecimal aa = numberDecimal(a); return aa.pow(n, mc).toPlainString(); } catch (NumberFormatException e) { BigDecimal aa = numberDecimal(a); BigDecimal bb = numberDecimal(b); if (aa.signum() == 1) { return expBD(bb.multiply(logBD(aa), mc)).toPlainString(); } else if (aa.signum() == 0) return "0"; else return String.valueOf(getInteger(b)); } } } protected String log10(String s) throws LogoError { Stack tmp = new Stack(); tmp.push(log(s)); tmp.push(log("10")); return divide(tmp); } protected String pi() { if (lowPrecision) { return String.valueOf(Math.PI); } else { return piBD().toPlainString(); } } protected String sin(String s) throws LogoError { if (lowPrecision) { return teste_fin_double(Math.sin(Math.toRadians(numberDouble(s)))); } else { BigDecimal bd = numberDecimal(s); return sinBD(bd).toPlainString(); } } protected String cos(String s) throws LogoError { if (lowPrecision) { return teste_fin_double(Math.cos(Math.toRadians(numberDouble(s)))); } else { BigDecimal bd = numberDecimal(s); return cosBD(bd).toPlainString(); } } protected String tan(String s) throws LogoError { if (lowPrecision) { return teste_fin_double(Math.tan(Math.toRadians(numberDouble(s)))); } else { BigDecimal bd = numberDecimal(s); return tanBD(bd).toPlainString(); } } protected String atan(String s) throws LogoError { if (lowPrecision) { return teste_fin_double(Math.toDegrees(Math.atan(numberDouble(s)))); } else { BigDecimal bd = numberDecimal(s); return toDegree(atanBD(bd)).toPlainString(); } } protected String acos(String s) throws LogoError { if (lowPrecision) { return teste_fin_double(Math.toDegrees(Math.acos(numberDouble(s)))); } else { BigDecimal bd = numberDecimal(s); return toDegree(acosBD(bd)).toPlainString(); } } protected String asin(String s) throws LogoError { if (lowPrecision) { return teste_fin_double(Math.toDegrees(Math.asin(numberDouble(s)))); } else { BigDecimal bd = numberDecimal(s); return toDegree(asinBD(bd)).toPlainString(); } } /** * This method returns the exp of bd * based on the Cordic algorithm * * @param bd * The first BigDecimal * @return The result */ private BigDecimal expBD(BigDecimal bd) { if (!initLogTable) { initLogTable(); } int signum = bd.signum(); if (signum == -1) { BigDecimal exp = expCordic(bd.negate(mc)); exp = BigDecimal.ONE.divide(exp, mc); return exp; } else if (signum == 0) return BigDecimal.ONE; else { return expCordic(bd); } } private BigDecimal expCordic(BigDecimal bd) { int i = 0; BigDecimal y = BigDecimal.ONE; while (i < mc.getPrecision()) { while (logTable[i].subtract(bd).signum() == -1) { bd = bd.subtract(logTable[i], mc); y = y.add(y.multiply(tenth.pow(i, mc), mc), mc); } i++; } y = y.multiply(bd.add(BigDecimal.ONE, mc), mc); return y; } /** * This method returns the log of bd * based on the Cordic algorithm * * @param bd * The first BigDecimal * @return The result */ private BigDecimal logBD(BigDecimal bd) { if (!initLogTable) { initLogTable(); } // If bd > 1 int signum = bd.subtract(BigDecimal.ONE, mc).signum(); if (signum == 1) { bd = bd.subtract(BigDecimal.ONE, mc); return logCordic(bd); } else if (signum == 0) return BigDecimal.ZERO; else { bd = BigDecimal.ONE.divide(bd, mc).subtract(BigDecimal.ONE, mc); return logCordic(bd).negate(mc); } } private BigDecimal logCordic(BigDecimal bd) { int i = 0; BigDecimal y = BigDecimal.ZERO; while (i < mc.getPrecision()) { BigDecimal tenthi = tenth.pow(i, mc); while (bd.subtract(tenthi, mc).signum() > 0) { bd = bd.subtract(tenthi, mc).divide(BigDecimal.ONE.add(tenthi, mc), mc); y = y.add(logTable[i], mc); } i++; } y = y.add(bd, mc).subtract(bd.pow(2, mc).multiply(new BigDecimal(0.5), mc), mc); return y; } /** * This method returns the sqrt of bd * based on the Cordic algorithm * * @param bd * The first BigDecimal * @return The result */ private BigDecimal sqrtBD(BigDecimal bd) { if (bd.signum() == 0) return BigDecimal.ZERO; BigDecimal three = new BigDecimal(3); BigDecimal half = new BigDecimal(0.5); BigDecimal x = BigDecimal.ZERO; BigDecimal y = BigDecimal.ONE.min(BigDecimal.ONE.divide(bd, mc)); while (x.compareTo(y) == -1) { x = y; // y=(3x-bd*x^3)/2 y = x.multiply(three, mc).subtract(bd.multiply(x.pow(3, mc), mc), mc).multiply(half, mc); } return BigDecimal.ONE.divide(y, mc); } /** * This method returns the cos of bd * based on the Cordic algorithm * * @param bd * The first BigDecimal * @return The result */ private BigDecimal cosBD(BigDecimal bd) { // bd is in degree BigDecimal period = new BigDecimal(360); BigDecimal a90 = new BigDecimal(90); BigDecimal a135 = new BigDecimal(135); BigDecimal a180 = new BigDecimal(180); BigDecimal a225 = new BigDecimal(225); BigDecimal a270 = new BigDecimal(270); BigDecimal a315 = new BigDecimal(315); bd = bd.remainder(period, mc); if (bd.signum() == -1) bd = bd.add(period, mc); BigDecimal quarterpi = new BigDecimal(45); // Now bd between 0 and 360. if (bd.compareTo(quarterpi) == -1) { // Between 0 and 45 return cosCordic(toRadian(bd)); } else if (bd.compareTo(a90) == -1) { // Between 45 and 90 return sinCordic(toRadian(a90.subtract(bd, mc))); } else if (bd.compareTo(a135) == -1) { // Between 90 and 135 return sinCordic(toRadian(bd.subtract(a90, mc))).negate(mc); } else if (bd.compareTo(a180) == -1) { // Between 135 and 180 return cosCordic(toRadian(a180.subtract(bd, mc))).negate(mc); } else if (bd.compareTo(a225) == -1) { // Between 180 and 225 return cosCordic(toRadian(bd.subtract(a180, mc))).negate(mc); } else if (bd.compareTo(a270) == -1) { // Between 225 and 270 return sinCordic(toRadian(a270.subtract(bd, mc))).negate(mc); } else if (bd.compareTo(a315) == -1) { // Between 270 and 315 return sinCordic(toRadian(bd.subtract(a270, mc))); } else { return cosCordic(toRadian(new BigDecimal(360).subtract(bd, mc))); } } /** * This method returns the cos of bd with 0= 0 && digits < 16) { BigDecimal bd = new BigDecimal(s); s = bd.toPlainString(); // is it a decimal number? int index = s.indexOf("."); if (index != -1) { if (digits == 0) return s.substring(0, index); else if (s.length() > index + digits) { s = s.substring(0, index + digits + 1); int a = Integer.parseInt(String.valueOf(s.charAt(s.length() - 1))); if (a > 4) { a++; s = s.substring(0, s.length() - 1) + a; } return s; } } else return s; } } catch (NumberFormatException e) {} return s; } }