package org.develnext.jphp.zend.ext.standard; import php.runtime.Memory; import php.runtime.env.Environment; import php.runtime.ext.support.compile.FunctionsContainer; import php.runtime.memory.LongMemory; import php.runtime.memory.StringMemory; import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; import java.math.RoundingMode; /** * bcmath extension * http://www.php.net/manual/ru/book.bc.php */ public class BCMathFunctions extends FunctionsContainer { private static final BigDecimal DECIMAL_TWO = new BigDecimal(2); private static final BigInteger INTEGER_MAX = new BigInteger(Integer.MAX_VALUE + ""); private static int getScale(Environment env) { Memory value = env.getConfigValue("bcmath.scale"); return value == null ? 0 : value.toInteger(); } private static BigDecimal toBigDecimal(Memory value) { try { switch (value.type){ case DOUBLE: return BigDecimal.valueOf(value.toDouble()); case INT: return BigDecimal.valueOf(value.toLong()); default: return new BigDecimal(value.toString()); } } catch (NumberFormatException ex) { return BigDecimal.ZERO; } catch (IllegalArgumentException ex) { return BigDecimal.ZERO; } } public static Memory bcadd(Environment env, Memory left, Memory right, int scale) { BigDecimal bd1 = toBigDecimal(left); BigDecimal bd2 = toBigDecimal(right); BigDecimal bd = bd1.add(bd2); bd = bd.setScale(scale, RoundingMode.DOWN); return new StringMemory(bd.toPlainString()); } public static Memory bcadd(Environment env, Memory left, Memory right) { return bcadd(env, left, right, getScale(env)); } public static int bccomp(Environment env, Memory left, Memory right, int scale){ BigDecimal bd1 = toBigDecimal(left); BigDecimal bd2 = toBigDecimal(right); bd1 = bd1.setScale(scale, RoundingMode.DOWN); bd2 = bd2.setScale(scale, RoundingMode.DOWN); return bd1.compareTo(bd2); } public static int bccomp(Environment env, Memory left, Memory right){ return bccomp(env, left, right, getScale(env)); } public static Memory bcdiv(Environment env, Memory left, Memory right, int scale){ BigDecimal bd1 = toBigDecimal(left); BigDecimal bd2 = toBigDecimal(right); if (bd2.compareTo(BigDecimal.ZERO) == 0) { return Memory.NULL; } BigDecimal result; if (scale > 0) result = bd1.divide(bd2, scale + 2, RoundingMode.DOWN); else result = bd1.divide(bd2, 2, RoundingMode.DOWN); result = result.setScale(scale, RoundingMode.DOWN); return new StringMemory(result.toPlainString()); } public static Memory bcdiv(Environment env, Memory left, Memory right) { return bcdiv(env, left, right, getScale(env)); } public static Memory bcmod(Memory left, Memory modus){ BigDecimal base = toBigDecimal(left).setScale(0, RoundingMode.DOWN); BigDecimal mod = toBigDecimal(modus).setScale(0, RoundingMode.DOWN); if (mod.compareTo(BigDecimal.ZERO) == 0) { return Memory.NULL; } return new StringMemory(base.remainder(mod, MathContext.UNLIMITED).toString()); } public static Memory bcmul(Environment env, Memory left, Memory right, int scale){ BigDecimal bd1 = toBigDecimal(left); BigDecimal bd2 = toBigDecimal(right); BigDecimal bd = bd1.multiply(bd2); // odd php special case for 0, scale is ignored: if (bd.compareTo(BigDecimal.ZERO) == 0) { if (scale > 0) return new StringMemory("0.0"); else return new StringMemory("0"); } bd = bd.setScale(scale, RoundingMode.DOWN); bd = bd.stripTrailingZeros(); return new StringMemory(bd.toPlainString()); } public static Memory bcmul(Environment env, Memory left, Memory right) { return bcmul(env, left, right, getScale(env)); } public static boolean bcscale(Environment env, int scale) { env.getConfigValue("bcmath.scale", LongMemory.valueOf(scale)); return true; } public static String bcpow(Environment env, Memory base, Memory exp, int scale) { BigDecimal baseD = toBigDecimal(base); BigDecimal expD = toBigDecimal(exp); BigInteger expI = expD.toBigInteger(); return bcpowImpl(baseD, expI, scale).toPlainString(); } public static String bcpow(Environment env, Memory base, Memory exp){ return bcpow(env, base, exp, getScale(env)); } private static BigDecimal bcpowImpl(BigDecimal base, BigInteger exp, int scale) { if (exp.compareTo(BigInteger.ZERO) == 0) return BigDecimal.ONE; boolean isNeg; if (exp.compareTo(BigInteger.ZERO) < 0) { isNeg = true; exp = exp.negate(); } else isNeg = false; BigDecimal result = BigDecimal.ZERO; while (exp.compareTo(BigInteger.ZERO) > 0) { BigInteger expSub = exp.min(INTEGER_MAX); exp = exp.subtract(expSub); result = result.add(base.pow(expSub.intValue())); } if (isNeg) result = BigDecimal.ONE.divide(result, scale + 2, RoundingMode.DOWN); result = result.setScale(scale, RoundingMode.DOWN); if (result.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO; result = result.stripTrailingZeros(); return result; } public static String bcpowmod(Environment env, Memory _base, Memory _exp, Memory _modulus, int scale) { BigDecimal base = toBigDecimal(_base); BigDecimal exp = toBigDecimal(_exp); BigDecimal modulus = toBigDecimal(_modulus); if (base.scale() != 0) { BigInteger expI = exp.toBigInteger(); BigDecimal pow = bcpowImpl(base, expI, scale); return pow.remainder(modulus, MathContext.UNLIMITED).toString(); } else { BigInteger baseI = base.toBigInteger(); BigInteger expI = exp.toBigInteger(); BigInteger modulusI = modulus.toBigInteger(); BigInteger result = baseI.modPow(expI, modulusI); return result.toString(); } } public static String bcpowmod(Environment env, Memory _base, Memory _exp, Memory _modulus){ return bcpowmod(env, _base, _exp, _modulus, getScale(env)); } public static Memory bcsqrt(Environment env, Memory operand, int scale) { BigDecimal value = toBigDecimal(operand); int compareToZero = value.compareTo(BigDecimal.ZERO); if (compareToZero < 0) { return Memory.NULL; } else if (compareToZero == 0) { return new StringMemory("0"); } int compareToOne = value.compareTo(BigDecimal.ONE); if (compareToOne == 0) return new StringMemory("1"); int cscale; BigDecimal initialGuess; if (compareToOne < 1) { initialGuess = BigDecimal.ONE; cscale = value.scale(); } else { BigInteger integerPart = value.toBigInteger(); int length = integerPart.toString().length(); if ((length % 2) == 0) length--; length /= 2; initialGuess = BigDecimal.ONE.movePointRight(length); cscale = Math.max(scale, value.scale()) + 2; } BigDecimal guess = initialGuess; BigDecimal lastGuess; for (int iteration = 0; iteration < 50; iteration++) { lastGuess = guess; guess = value.divide(guess, cscale, RoundingMode.DOWN); guess = guess.add(lastGuess); guess = guess.divide(DECIMAL_TWO, cscale, RoundingMode.DOWN); if (lastGuess.equals(guess)) { break; } } value = guess; value = value.setScale(scale, RoundingMode.DOWN); return new StringMemory(value.toPlainString()); } public static Memory bcsqrt(Environment env, Memory operand) { return bcsqrt(env, operand, getScale(env)); } public static String bcsub(Environment env, Memory left, Memory right, int scale){ BigDecimal bd1 = toBigDecimal(left); BigDecimal bd2 = toBigDecimal(right); return bd1.subtract(bd2).setScale(scale, RoundingMode.DOWN).toPlainString(); } public static String bcsub(Environment env, Memory left, Memory right) { return bcsub(env, left, right, getScale(env)); } }