package un.api.math; import un.api.collection.primitive.ByteSequence; /* REQUIRED OPERATIONS: * Arithmetic operations (add, subtract, multiply, divide, square root, fused multiply–add, remainder...) * add(LargeDecimal) [x] * subtract(LargeDecimal) [x] * multiply(LargeDecimal) [x] * divide(LargeDecimal) [x] * sqrt() [x] * fma(LargeDecimal, LargeDecimal) [x] * mod(LargeDecimal) [x] * pow(int) [x] * * Conversions (between formats, to and from strings, etc.) * toString() [x] * * Scaling * Scale: decimal position? [x] * * Copying and manipulating the sign (abs, negate, etc.) * abs() [x] * negate() [x] * * Comparisons and total ordering * compare() [x] * equals() [x] * getSign() [x] * * Classification and testing for NaNs, etc. * NaN, Infinity, Sign [x] * * Testing and setting flags... [x] * * Miscellaneous/Optional operations. * ceil() [x] * floor() [x] * round() [x] * ... [?] TODO: Add more optional operations * * roundingMode() [x] * * SheNaNigans * Deal with operations by 0, [x] * infinities, or NaN */ /** * @author Devin W. Brite */ public class LargeDecimal { public static final boolean NEGATIVE = true; public static final boolean POSITIVE = false; /** * Rounding mode to round towards positive infinity. */ public static final int ROUND_CEILING = 0; /** * Rounding mode to round towards zero. */ public static final int ROUND_DOWN = 1; /** * Rounding mode to round towards negative infinity. */ public static final int ROUND_FLOOR = 2; /** * Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, * in which case round down. */ public static final int ROUND_HALF_DOWN = 3; /** * Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, * in which case, round towards the even neighbor. */ public static final int ROUND_HALF_EVEN = 4; /** * Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, * in which case round up. */ public static final int ROUND_HALF_UP = 5; /** * Rounding mode to assert that the requested operation has an exact result, hence no rounding is necessary. */ public static final int ROUND_UNNECESSARY = 6; /** * Rounding mode to round away from zero. */ public static final int ROUND_UP = 7; private static final byte SIGN_MASK = 0x1; //0b00000001 private static final byte INFINITY_MASK = 0x4; //0b00000010 private static final byte NAN_MASK = 0x2; //0b00000100 private static int precision = 64; private static char decimalSeparator = '.'; private static char thousandsSeparator = ','; private static boolean bUseThousandsSeparator = false; private static boolean bThrowExceptionOnNaN = true; private static boolean bThrowExceptionOnDivisionByZero = true; private static boolean bUseInfinityOnDivisionByZero = false; private static int roundingMode = ROUND_HALF_UP; //NOTE: KEEP THESE STATIC FINAL LARGEDECIMALS AFTER THE decimalSeparator VAR! (DARN YOU, CODE REARRANGER) public static final LargeDecimal NEGATIVE_ONE = new LargeDecimal("-1"); public static final LargeDecimal ZERO = new LargeDecimal("0"); public static final LargeDecimal ONE = new LargeDecimal("1"); public static final LargeDecimal TEN = new LargeDecimal("10"); public static final LargeDecimal ONE_TENTH = new LargeDecimal("0.1"); public static final LargeDecimal INFINITY = new LargeDecimal("INF"); public static final LargeDecimal NEGATIVE_INFINITY = new LargeDecimal("-INF"); public static final LargeDecimal NAN = new LargeDecimal("NaN"); private byte meta = 0x0; // unused, unused, unused, unused, unused, inf, NaN, sign private ByteSequence integer = new ByteSequence(); private int scale; /** * Creates a LargeDecimal representation of a decimal number. * * @param decimal The String representation of a decimal number. Example: "3.14159" */ public LargeDecimal(String decimal) { if (decimal.equals("NaN")) { this.meta = resolveMetaNaN(meta, true); } else if (decimal.equals("INF")) { this.meta = resolveMetaInfinite(meta, true); } else if (decimal.equals("-INF")) { this.meta = resolveMetaInfinite(meta, true); this.meta = resolveMetaSign(meta, true); } else { integer = generateByteSequence(decimal); while ((integer.getSize() - 1) - scale < 0) { integer.add(0, (byte) 0); } } } private LargeDecimal(ByteSequence integer, int scale, byte meta, boolean removeLeadingZeroes, boolean removeTrailingZeroes) { this.integer = new ByteSequence(integer.toArrayByte()); this.scale = (int) scale; this.meta = (byte) meta; if (removeLeadingZeroes) { while ((this.integer.getSize() - 1) - this.scale > 0 && this.integer.read(0) == 0) this.integer.remove(0); //Ensures the correct amount of 0s after scale while ((this.integer.getSize() - 1) - this.scale < 0) this.integer.add(0, (byte) 0); } if (removeTrailingZeroes) { while (this.scale >= 1 && this.integer.read(this.integer.getSize() - 1) == 0) { this.integer.remove(this.integer.getSize() - 1); this.scale--; } } } /** * Sets the decimal separator character to be used for String conversions. * * @param decimalSeparator The new character to be used as a decimal separator. (Default: '.') * @throws IllegalAccessException if decimalSeparator == thousandsSeparator */ public static void setDecimalSeparator(char decimalSeparator) { if (decimalSeparator == thousandsSeparator) throw new IllegalArgumentException("Decimal thousandsSeparator should not be the same as thousands thousandsSeparator.\n" + "Use LargeDecimal.flipSeparators() to flip the decimal and thousands separators."); else LargeDecimal.decimalSeparator = decimalSeparator; } /** * Sets the thousands separator character to be used for String conversions. * * @param thousandsSeparator The new character to be used as a thousands separator. (Default: ',') * @throws IllegalAccessException if thousandsSeparator == decimalSeparator */ public static void setThousandsSeparator(char thousandsSeparator) { if (thousandsSeparator == decimalSeparator) throw new IllegalArgumentException("Decimal thousandsSeparator should not be the same as thousands thousandsSeparator.\n" + "Use LargeDecimal.flipSeparators() to flip the decimal and thousands separators."); LargeDecimal.thousandsSeparator = thousandsSeparator; } /** * Determines whether or not the thousands separator is used in the String output. * * @see LargeDecimal#toString() */ public static void setUseThousandsSeparator(boolean b) { bUseThousandsSeparator = b; } /** * Switches the use of the thousands and decimal separators. * * Switches the use of the thousands and decimal separators so that * the thousands separator becomes the decimal separator * and the decimal separator becomes the thousands separator. */ public static void flipSeparators() { char temp = LargeDecimal.thousandsSeparator; LargeDecimal.thousandsSeparator = LargeDecimal.decimalSeparator; LargeDecimal.decimalSeparator = temp; } /** * Sets the precision of LargeDecimals. (Default: 128) * * @param precision The precision of LargeDecimals beyond the decimal point */ public static void setPrecision(int precision) { LargeDecimal.precision = precision; } /** * Determines whether or not to throw an exception when NaN is used in calculations. */ public static void setThrowExceptionOnNaN(boolean b) { bThrowExceptionOnNaN = b; } /** * Determines whether or not to throw an exception when dividing by zero. */ public static void setThrowExceptionOnDivisionByZero(boolean b) { bThrowExceptionOnDivisionByZero = b; } /** * Determines whether division by zero yields Infinity or NaN if division by zero does not throw an exception. * * @see LargeDecimal#setThrowExceptionOnDivisionByZero(boolean b) */ public static void setUseInfinityOnDivisionByZero(boolean b) { bUseInfinityOnDivisionByZero = b; } /** * Checks for the use of NaN in calculations. * * @throws ArithmeticException if NaN is used in a calculation and bThrowExceptionOnNaN is true. * @see LargeDecimal#setThrowExceptionOnNaN(boolean b) */ private void checkNaN() { if (bThrowExceptionOnNaN && this.isNaN()) { throw new ArithmeticException("Operation used or resulted in NaN. If you would like NaNs to be silenced," + " please use: LargeDecimal.setThrowExceptionOnNaN(false)."); } } private byte resolveMetaSign(byte meta, boolean sign) { if (sign) return (byte) (meta | SIGN_MASK); else return (byte) (meta & (~SIGN_MASK)); } private byte resolveMetaNaN(byte meta, boolean nan) { if (nan) return (byte) (meta | NAN_MASK); else return (byte) (meta & (~NAN_MASK)); } private byte resolveMetaInfinite(byte meta, boolean infinite) { if (infinite) return (byte) (meta | INFINITY_MASK); else return (byte) (meta & (~INFINITY_MASK)); } private boolean getSign() { return (this.meta & SIGN_MASK) > 0; } private boolean isNaN() { return (this.meta & NAN_MASK) > 0; } private boolean isInfinity() { return (this.meta & INFINITY_MASK) > 0; } /** * Returns the absolute value of this LargeDecimal * * @return a new LargeDecimal with the value of |this| */ public LargeDecimal abs() { checkNaN(); byte meta = resolveMetaSign(this.meta, POSITIVE); return new LargeDecimal(integer, scale, meta, true, true); } /** * Negates this LargeDecimal * * If this LargeDecimal is positive, it becomes negative. * If this LargeDecimal is negative, it becomes positive. * * * @return a new LargeDecimal with the value of -(this). */ public LargeDecimal negate() { checkNaN(); byte meta = resolveMetaSign(this.meta, !this.getSign()); return new LargeDecimal(integer, scale, meta, true, true); } /** * Multiplies this LargeDecimal by the multiplicand, then adds the addend. * * @param multiplicand The value to multiply by. * @param addend The value to add. * @return a new LargeDecimal with the value of (this * multiplicand) + addend */ public LargeDecimal fma(LargeDecimal multiplicand, LargeDecimal addend) { checkNaN(); return this.multiply(multiplicand).add(addend); } private ByteSequence generateByteSequence(String decimal) { ByteSequence fraction = new ByteSequence(); // Negative meta = resolveMetaSign((byte) 0, decimal.startsWith("-", 0)); decimal = decimal.replaceAll("\\-", ""); // Decimal point scale = decimal.indexOf(LargeDecimal.decimalSeparator); if (scale == -1) scale = 0; else scale = (decimal.length() - 1) - decimal.indexOf(LargeDecimal.decimalSeparator); decimal = decimal.replaceAll("\\" + LargeDecimal.decimalSeparator, ""); decimal = decimal.replaceAll("" + LargeDecimal.thousandsSeparator, ""); // Literally turns a char array into a byte array. for (int i = decimal.length() - 1; i >= 0; i--) { fraction.add(0, Byte.valueOf(decimal.charAt(i) + "")); } //Remove trailing 0s after decimal point while (scale != 0 && fraction.read(fraction.getSize() - 1) == 0) { fraction.remove(fraction.getSize() - 1); scale--; } // Remove leading 0s. while (fraction.read(0) == 0 && fraction.getSize() - scale > 1) fraction.remove(0); return fraction; } @Override public String toString() { if(this.isInfinity()) { if(this.getSign() == POSITIVE) { return "INF"; } else { return "-INF"; } } else if(this.isNaN()) { return "NaN"; } else { StringBuilder sb = new StringBuilder(); sb.append(((meta & SIGN_MASK) > 0) ? "-" : ""); for (int i = 0; i < integer.getSize() && i < integer.getSize() - scale - 1 + precision; i++) { sb.append(integer.read(i)); if (i == integer.getSize() - 1 - scale && scale != 0) { sb.append(LargeDecimal.decimalSeparator); } } //Add thousands separator afterwards. if (bUseThousandsSeparator) { for (int i = integer.getSize() - scale - 3; i > 0; i -= 3) { sb.insert(i, thousandsSeparator); } } return sb.toString(); } } /** * Adds this by the addend. * * @param addend The value to add. * @return a new LargeDecimal with the value of this + addend. */ public LargeDecimal add(LargeDecimal addend) { //TEMP VALS ByteSequence tbs1 = new ByteSequence(this.integer.toArrayByte()); ByteSequence tbs2 = new ByteSequence(addend.integer.toArrayByte()); //CHECKS checkNaN(); if (this.getSign() == POSITIVE && addend.getSign() == NEGATIVE) { byte tempMeta = resolveMetaSign(addend.meta, false); LargeDecimal subtrahend = new LargeDecimal(tbs2, addend.scale, tempMeta, false, false); return this.subtract(subtrahend); } else if (this.getSign() == NEGATIVE && addend.getSign() == POSITIVE) { byte tempMeta = resolveMetaSign(this.meta, false); LargeDecimal subtrahend = new LargeDecimal(tbs1, this.scale, tempMeta, false, false); return addend.subtract(subtrahend); } //SheNaNigans if (this.isNaN() || addend.isNaN()) return returnNaN(); if (this.isInfinity() || addend.isInfinity()) return (this.getSign() == POSITIVE) ? INFINITY : NEGATIVE_INFINITY; //PADDING LargeDecimal padResults[] = padByteData(this, addend); //SET MATH VALS ByteSequence bs1; ByteSequence bs2; if (padResults[1].integer.getSize() >= padResults[0].integer.getSize()) { bs1 = new ByteSequence(padResults[0].integer.toArrayByte()); bs2 = new ByteSequence(padResults[1].integer.toArrayByte()); } else { bs1 = new ByteSequence(padResults[1].integer.toArrayByte()); bs2 = new ByteSequence(padResults[0].integer.toArrayByte()); } //PREPARE VARIABLES byte carry = 0; ByteSequence resultInteger = new ByteSequence(); int indexLength = bs1.getSize() - 1; //MATH for (int i = 0; i <= indexLength; i++) { byte tempResult; byte firstVal = bs1.read(indexLength - i); byte secondVal = bs2.read(indexLength - i); tempResult = (byte) (firstVal + secondVal + carry); if (tempResult >= 10) { tempResult -= 10; carry = 1; } else { carry = 0; } resultInteger.add(0, tempResult); if (carry == 1 && i == indexLength) resultInteger.add(0, carry); } //END MATH //META INFO byte meta = padResults[0].meta; int resultScale = Math.max(this.scale, addend.scale); //RETURN return new LargeDecimal(resultInteger, resultScale, meta, true, true); } private LargeDecimal[] padByteData(LargeDecimal primary, LargeDecimal secondary) { ByteSequence integerA = new ByteSequence(primary.integer.toArrayByte()); int scaleA = primary.scale; byte metaA = primary.meta; ByteSequence integerB = new ByteSequence(secondary.integer.toArrayByte()); int scaleB = secondary.scale; byte metaB = secondary.meta; // Pad right boolean padRight = scale > secondary.scale; if (padRight) { for (int i = 0; i < scale - secondary.scale; i++) { integerB.add((byte) 0); scaleB++; } } else { for (int i = 0; i < secondary.scale - scale; i++) { integerA.add((byte) 0); scaleA++; } } // Pad left boolean padLeft = integerA.getSize() > integerB.getSize(); if (padLeft) { while (integerA.getSize() > integerB.getSize()) { integerB.add(0, (byte) 0); } } else { while (integerB.getSize() > integerA.getSize()) { integerA.add(0, (byte) 0); } } integerA.add(0, (byte) 0); integerB.add(0, (byte) 0); primary = new LargeDecimal(integerA, scaleA, metaA, false, false); secondary = new LargeDecimal(integerB, scaleB, metaB, false, false); return new LargeDecimal[]{primary, secondary}; } /** * Multiplies this LargeDecimal by the multiplicand. * * @param multiplicand The value to multiply by. * @return a new LargeDecimal with the value of this * multiplicand. */ public LargeDecimal multiply(LargeDecimal multiplicand) { //TEMP VALS //CHECKS checkNaN(); //SheNaNigans if((this.isNaN() || multiplicand.isNaN()) || (this.isInfinity() && multiplicand.equals(ZERO)) || (this.equals(ZERO) && multiplicand.isInfinity())) return returnNaN(); else if(this.isInfinity() || multiplicand.isInfinity()) return (this.getSign() == multiplicand.getSign())?NEGATIVE_INFINITY:INFINITY; //PADDING LargeDecimal padResults[] = padByteData(this, multiplicand); //SET MATH VALS ByteSequence bs1; ByteSequence bs2; if (padResults[1].integer.getSize() >= padResults[0].integer.getSize()) { bs1 = new ByteSequence(padResults[0].integer.toArrayByte()); bs2 = new ByteSequence(padResults[1].integer.toArrayByte()); } else { bs1 = new ByteSequence(padResults[1].integer.toArrayByte()); bs2 = new ByteSequence(padResults[0].integer.toArrayByte()); } //PREPARE VARIABLES ByteSequence bsVal1 = new ByteSequence(); ByteSequence bsVal2; LargeDecimal ldVal1; LargeDecimal ldVal2; LargeDecimal ldVal3; byte carry = 0; int indexLength = bs1.getSize() - 1; ByteSequence tempFraction; //MATH //FOR EFFICIENCY'S SAKE: if (multiplicand.equals(ONE_TENTH)) { bs1.add(0, (byte) 0); int tempScale = padResults[0].scale + 1; return new LargeDecimal(bs1, tempScale, this.meta, true, true); } for (int i = 0; i <= indexLength; i++) { // #2 = 0 bsVal2 = new ByteSequence(); //adds 0s to the end of #2 for (int k = 0; k < i; k++) { bsVal2.add(0, (byte) 0); } //firstResult = the last value of bs1, - i byte firstVal = bs1.read(indexLength - i); byte tempResult; for (int j = 0; j <= indexLength; j++) { //secondResult = the last value of bs1, -j byte secondVal = bs2.read(indexLength - j); //multiply those to get the multResult byte multResult = (byte) (firstVal * secondVal); //tempResult = multResult%10 + carry. //So if carry = 3 and multresult = 18, tempResult = 11 (8 + 3) //If that's >=10, subtract 10 and set the carry to 1. //But what if the tempResult = (byte) ((multResult % 10) + carry); if (tempResult >= 10) { tempResult -= 10; carry = 1; } else { carry = 0; } byte difference = (byte) (multResult - (multResult % 10)); carry += (byte) (difference / 10); bsVal2.add(0, tempResult); } if (i != 0) { ldVal1 = new LargeDecimal(bsVal1, 0, (byte) 0, false, false); ldVal2 = new LargeDecimal(bsVal2, 0, (byte) 0, false, false); ldVal3 = ldVal1.add(ldVal2); bsVal1 = ldVal3.integer; } else { bsVal1 = bsVal2; } } tempFraction = bsVal1; //END MATH //META INFO boolean sign = multiplicand.getSign() ^ this.getSign(); int scale = padResults[0].scale + padResults[1].scale; byte meta = padResults[0].meta; meta = resolveMetaSign(meta, sign); //RETURN // RATIONAL PRECISION SHENANIGANS // We have to check if a number is an integer after rounding. // If it is, then we can return the integer, // otherwise we return a decimal with 2 extra accurate digits just in case. LargeDecimal result1 = new LargeDecimal(tempFraction, scale, meta, true, true).round(precision, ROUND_HALF_UP); LargeDecimal result2 = new LargeDecimal(tempFraction, scale, meta, true, true).round(precision + 2, ROUND_HALF_UP); if (result1.scale > 0) return result2; else return result1; } /** * Subtracts this by the subtrahend. * * @param subtrahend The value to subtract by. * @return a new LargeDecimal with the value of this - subtrahend. */ public LargeDecimal subtract(LargeDecimal subtrahend) { //TEMP VALS int comparison = this.compare(subtrahend); boolean sign = (this.getSign() == NEGATIVE && subtrahend.getSign() == NEGATIVE) ? (comparison >= 0) : (comparison == -1); ByteSequence tbs2 = new ByteSequence(subtrahend.integer.toArrayByte()); //CHECKS checkNaN(); if (this.getSign() == NEGATIVE && subtrahend.getSign() == POSITIVE) { byte tempMeta = resolveMetaSign(subtrahend.meta, true); LargeDecimal addend = new LargeDecimal(tbs2, subtrahend.scale, tempMeta, false, false); return this.add(addend); } else if (this.getSign() == POSITIVE && subtrahend.getSign() == NEGATIVE) { byte tempMeta = resolveMetaSign(subtrahend.meta, false); LargeDecimal addend = new LargeDecimal(tbs2, subtrahend.scale, tempMeta, false, false); return this.add(addend); } if (comparison == 0) return ZERO; //SheNaNigans if (this.isNaN() || subtrahend.isNaN()) return returnNaN(); if (this.isInfinity() && subtrahend.isInfinity()) return returnNaN(); else if (this.isInfinity() && !subtrahend.isInfinity()) return (this.getSign() == POSITIVE) ? INFINITY : NEGATIVE_INFINITY; else if (subtrahend.isInfinity() && !this.isInfinity()) return (subtrahend.getSign() == POSITIVE) ? NEGATIVE_INFINITY : INFINITY; //PADDING LargeDecimal padResults[] = padByteData(this, subtrahend); //SET MATH VALS ByteSequence bs1; ByteSequence bs2; if (comparison == 1) { bs1 = new ByteSequence(padResults[0].integer.toArrayByte()); bs2 = new ByteSequence(padResults[1].integer.toArrayByte()); } else { bs1 = new ByteSequence(padResults[1].integer.toArrayByte()); bs2 = new ByteSequence(padResults[0].integer.toArrayByte()); } //PREPARE VARIABLES byte carry = 0; int indexLength = bs1.getSize() - 1; ByteSequence tempFraction = new ByteSequence(); //MATH for (int i = 0; i <= indexLength; i++) { byte tempResult; byte firstVal = bs1.read(indexLength - i); byte secondVal = bs2.read(indexLength - i); tempResult = (byte) (firstVal - secondVal - carry); if (tempResult < 0) { tempResult += 10; carry = 1; } else { carry = 0; } tempFraction.add(0, tempResult); } //ENDMATH //META INFO int resultScale = Math.max(this.scale, subtrahend.scale); byte meta = this.meta;//(byte)(comparison == 1 ? this.meta: this.meta | sign); meta = resolveMetaSign(meta, sign); //RETURN return new LargeDecimal(tempFraction, resultScale, meta, true, true); } /** * Divides this by the divisor. * * @param divisor The value to divide by. * @return a new LargeDecimal with the value of this/divisor. */ public LargeDecimal divide(LargeDecimal divisor) { //TEMP VALS boolean sign = divisor.getSign() ^ getSign(); ByteSequence tbs1 = new ByteSequence(integer.toArrayByte()); ByteSequence tbs2 = new ByteSequence(divisor.integer.toArrayByte()); //CHECKS checkNaN(); //SheNaNigans //NaN operations: if(this.isNaN() || divisor.isNaN()) return returnNaN(); // a/0 = ∞ // or a/0 = NaN (depending on the flag) //±∞/0 = NaN //0/0 = NaN if (divisor.equals(ZERO)) if(!this.equals(ZERO) && !this.isInfinity() && bUseInfinityOnDivisionByZero == true) if(bThrowExceptionOnDivisionByZero) throw new ArithmeticException("Cannot divide by 0."); else return INFINITY; else return returnNaN(); // ∞/a = ∞ // -∞/a = -∞ // ∞/-a = -∞ // -∞/-a = ∞ // ±∞/±∞ = NaN if (this.isInfinity() && !(divisor.isInfinity() || divisor.equals(ZERO) || divisor.isNaN())) if(this.getSign() == POSITIVE) return INFINITY; else return NEGATIVE_INFINITY; else if(this.isInfinity() && divisor.isInfinity()) return returnNaN(); // a/±∞ = 0 // 0/a = 0 if(!this.isInfinity() && !this.isNaN() && !divisor.isNaN()) if((!this.equals(ZERO) && divisor.isInfinity()) || (this.equals(ZERO) && !divisor.isInfinity())) return ZERO; //PADDING //SET MATH VALS //PREPARE VARIABLES LargeDecimal ldVal1 = new LargeDecimal(tbs1, this.scale, this.meta, true, true).abs(); LargeDecimal ldVal2 = new LargeDecimal(tbs2, divisor.scale, divisor.meta, true, true).abs(); LargeDecimal ldVal3; LargeDecimal endVal = new LargeDecimal("0"); int scale = 0; int compareVal = 1; LargeDecimal increment = ONE; if (ldVal1.integer.getSize() - ldVal1.scale > ldVal2.integer.getSize() - ldVal2.scale) { int scaleUp = 1; int numZeroes = (ldVal1.integer.getSize() - ldVal1.scale) - (ldVal2.integer.getSize() - ldVal2.scale); for (int i = 0; i < numZeroes; i++) { scaleUp *= 10; } ldVal2 = ldVal2.multiply(new LargeDecimal("" + scaleUp)); increment = increment.multiply(new LargeDecimal("" + scaleUp)); } //MATH // RATIONAL PRECISION SHENANIGANS // This uses "precision+3" to account for 2 extra accurate digits (the last will be inaccurate). // These digits are used for scenarios where a result isn't properly represented in base 10. // For example: 1/3 = 0.3333... while (compareVal != 0 && scale < precision + 3) { ldVal3 = ldVal1.subtract(ldVal2); compareVal = ldVal3.compare(ZERO); if (compareVal == 1) { ldVal1 = ldVal3; endVal = endVal.add(increment); } else if (compareVal == -1) { ldVal2 = ldVal2.multiply(ONE_TENTH); increment = increment.multiply(ONE_TENTH); scale++; } } endVal = endVal.add(increment); //ENDMATH //META INFO byte meta = resolveMetaSign(this.meta, sign); //RETURN return new LargeDecimal(endVal.integer, endVal.scale, meta, true, true); } /** * Rounds a LargeDecimal towards +infinity at a certain decimal digit. * * @param precision The precision beyond the decimal point * @return a new LargeDecimal with the value of this.round(precision, ROUND_CEILING) */ public LargeDecimal ceil(int precision) { return this.round(precision, ROUND_CEILING); } /** * Rounds a LargeDecimal using the default/preset roundingMode. * * @param precision The precision beyond the decimal point * @return a new LargeDecimal with the value of this.round(precision, roundingMode) */ public LargeDecimal round(int precision) { return round(precision, roundingMode); } /** * Rounds a LargeDecimal using the specified roundingMode. * * @param precision The precision beyond the decimal point * @param roundingMode The rounding mode to be used for this calculation. * @return a new LargeDecimal with the value of this.round(precision, roundingMode) * @see LargeDecimal#ROUND_CEILING * @see LargeDecimal#ROUND_FLOOR * @see LargeDecimal#ROUND_DOWN * @see LargeDecimal#ROUND_UP * @see LargeDecimal#ROUND_HALF_UP * @see LargeDecimal#ROUND_HALF_EVEN * @see LargeDecimal#ROUND_HALF_DOWN */ public LargeDecimal round(int precision, int roundingMode) { //TEMP ByteSequence tbs1 = new ByteSequence(integer.toArrayByte()); //CHECKS checkNaN(); if(this.isNaN() || this.isInfinity()) return this; //SET REAL MATH VALS //PREPARE VARIABLES int scale = this.scale; LargeDecimal increment = ONE; for (; scale > precision; scale--) { tbs1.remove(tbs1.getSize() - 1); } for (int i = 0; i < precision; i++) { increment = increment.multiply(ONE_TENTH); } LargeDecimal result = new LargeDecimal(tbs1, scale, this.meta, true, true); int comparison = this.compare(result); int roundHalfVal; int roundHalfEvenVal; try { roundHalfVal = this.integer.read(integer.getSize() - this.scale + precision); roundHalfEvenVal = this.integer.read(integer.getSize() - this.scale + precision - 1); } catch (ArrayIndexOutOfBoundsException ex) { roundHalfVal = 0; roundHalfEvenVal = 0; } //MATH... switch (roundingMode) { case ROUND_CEILING: { if (comparison == 0) { return result; } else { return result.add(increment); } } case ROUND_DOWN: { if (comparison == -1 && getSign() == POSITIVE) { return result.add(increment); } else { return result; } } case ROUND_FLOOR: { if (comparison == 0) { return result; } else { return result.subtract(increment); } } case ROUND_HALF_DOWN: { if (roundHalfVal > 5) return this.round(precision, ROUND_UP); else return this.round(precision, ROUND_DOWN); } case ROUND_HALF_EVEN: { if (roundHalfVal == 5) { if (roundHalfEvenVal % 2 == 0) return this.round(precision, ROUND_DOWN); else return this.round(precision, ROUND_UP); } else if (roundHalfVal > 5) return this.round(precision, ROUND_UP); else return this.round(precision, ROUND_DOWN); } case ROUND_HALF_UP: { if (roundHalfVal >= 5) return this.round(precision, ROUND_UP); else return this.round(precision, ROUND_DOWN); } case ROUND_UNNECESSARY: { return this; } case ROUND_UP: { if (this.getSign() == NEGATIVE) { increment = increment.negate(); } if (comparison == 0) { return result; } else { return result.add(increment); } } } return ZERO; } /** * Rounds a LargeDecimal towards -infinity at a certain decimal digit. * * @param precision The precision beyond the decimal point * @return a new LargeDecimal with the value of this.round(precision, ROUND_FLOOR) */ public LargeDecimal floor(int precision) { return this.round(precision, ROUND_FLOOR); } /** * Returns the LargeDecimal integer value of this % divisor using the specified rounding mode. * * @param divisor The LargeDecimal to divide by. * @param roundingMode The rounding mode to be used for this calculation. * @return a new LargeDecimal with the value of this % divisor */ public LargeDecimal mod(LargeDecimal divisor, int roundingMode) { //TEMP VALS //CHECKS checkNaN(); //SheNaNigans //NaN operations: if(this.isNaN() || divisor.isNaN()) return returnNaN(); // a/0 = ∞ // or a/0 = NaN (depending on the flag) //±∞/0 = NaN //0/0 = NaN if (divisor.equals(ZERO)) if(!this.equals(ZERO) && !this.isInfinity() && bUseInfinityOnDivisionByZero == true) if(bThrowExceptionOnDivisionByZero) throw new ArithmeticException("Cannot divide by zero."); else return INFINITY; else return returnNaN(); // ∞/a = ∞ // -∞/a = -∞ // ∞/-a = -∞ // -∞/-a = ∞ // ±∞/±∞ = NaN if (this.isInfinity() && !(divisor.isInfinity() || divisor.equals(ZERO) || divisor.isNaN())) if(this.getSign() == POSITIVE) return INFINITY; else return NEGATIVE_INFINITY; else if(this.isInfinity() && divisor.isInfinity()) return returnNaN(); // a/±∞ = 0 // 0/a = 0 if(!this.isInfinity() && !this.isNaN() && !divisor.isNaN()) if((!this.equals(ZERO) && divisor.isInfinity()) || (this.equals(ZERO) && !divisor.isInfinity())) return ZERO; int comparison = this.abs().compare(divisor.abs()); if (comparison == 0) { return ZERO; } else if (comparison == -1) //Noice. { if (this.getSign() == POSITIVE && divisor.getSign() == POSITIVE) return this; else if (this.getSign() == NEGATIVE && divisor.getSign() == POSITIVE) return this.add(divisor); else if (this.getSign() == NEGATIVE && divisor.getSign() == NEGATIVE) return this; else return this.add(divisor); } else { //PADDING LargeDecimal padResults[] = padByteData(this, divisor); //SET MATH VALS //PREPARE VARIABLES LargeDecimal ldVal1 = padResults[0].abs().round(0, roundingMode); LargeDecimal ldVal2 = padResults[1].abs().round(0, roundingMode); LargeDecimal ldVal3; int compareVal; LargeDecimal increment = ONE; if (ldVal1.integer.getSize() - ldVal1.scale > ldVal2.integer.getSize() - ldVal2.scale) { int scaleUp = 1; int numZeroes = (ldVal1.integer.getSize() - ldVal1.scale) - (ldVal2.integer.getSize() - ldVal2.scale); for (int i = 0; i < numZeroes; i++) { scaleUp *= 10; } ldVal2 = ldVal2.multiply(new LargeDecimal("" + scaleUp)); increment = increment.multiply(new LargeDecimal("" + scaleUp)); } //MATH while (increment.compare(ONE_TENTH) != 0) { ldVal3 = ldVal1.subtract(ldVal2); compareVal = ldVal3.compare(ZERO); if (compareVal == 1) { ldVal1 = ldVal3; } else if (compareVal == -1) { ldVal2 = ldVal2.multiply(ONE_TENTH); increment = increment.multiply(ONE_TENTH); //scale++; } else { return ZERO; } } LargeDecimal remainder = ldVal1; //ENDMATH //META INFO if (this.getSign() == POSITIVE && divisor.getSign() == POSITIVE) return remainder; else if (this.getSign() == NEGATIVE && divisor.getSign() == POSITIVE) return divisor.subtract(remainder); else if (this.getSign() == NEGATIVE && divisor.getSign() == NEGATIVE) return remainder.negate(); else return remainder.add(divisor); } } /** * Returns the LargeDecimal integer value of this % divisor using ROUND_DOWN as the rounding mode. * * @param divisor The LargeDecimal to divide by. * @return a new LargeDecimal with the value of this % divisor */ public LargeDecimal mod(LargeDecimal divisor) { return this.mod(divisor, ROUND_DOWN); } /** * Calculates this to the power of n, where n is an integer value. * @param n * @return a new LargeDecimal with the value of this^(n). * Returns a LargeDecimal with the value of 0 when performing ±∞^0 or 0^0 */ //TODO: Implement decimal exponents public LargeDecimal pow(int n) { if((this.isInfinity() || this.equals(ZERO)) && n == 0) return ONE; LargeDecimal multiplicand = this; LargeDecimal result = multiplicand; if (n > 0) { for (int i = 1; i < n; i++) { result = result.multiply(multiplicand); } } else if (n <= 0) { for (int i = 0; i >= n; i--) { result = result.divide(multiplicand); } } return result; } /** * Calculates this to the power of n, where n is an integer value. * * @param n * @return a new LargeDecimal with the value of this^(n) * Returns NAN when performing ±∞^0 or 0^0 */ public LargeDecimal powNaN(int n) { if((this.isInfinity() || this.equals(ZERO)) && n == 0) return returnNaN(); LargeDecimal multiplicand = this; LargeDecimal result = multiplicand; if (n > 0) { for (int i = 1; i < n; i++) { result = result.multiply(multiplicand); } } else if (n <= 0) { for (int i = 0; i >= n; i--) { result = result.divide(multiplicand); } } return result; } private LargeDecimal returnNaN() { if (bThrowExceptionOnNaN) { throw new ArithmeticException("Operation resulted in NaN. If you would like NaNs to be silenced," + " please use: LargeDecimal.setThrowExceptionOnNaN(false)."); } else return NAN; } /** * Calculates the square root of a LargeDecimal * * @return a new LargeDecimal with the value of the square root of the original. */ public LargeDecimal sqrt() { return this.root(2); } /** * Calculates the nth root of a LargeDecimal where n is a natural number * * * @return a new LargeDecimal with the value of the nth root of the original. */ public LargeDecimal root(int n) { if (this.isInfinity() || this.isNaN()) return returnNaN(); if(n < 0) { //TODO: Decimal powers throw new ArithmeticException("Sorry, I haven't implemented negatives yet." + "There's this big issue with decimal powers that I just *really* don't want to deal with."); } LargeDecimal result; LargeDecimal decrement; ByteSequence bs = new ByteSequence(new byte[]{(byte) 1}); for (int i = 0; i < this.integer.getSize() - this.scale; i++) { bs.add((byte) 0); } result = new LargeDecimal(bs, 0, (byte) 0, true, true); decrement = new LargeDecimal(bs, 0, (byte) 0, true, true).multiply(ONE_TENTH); int comparison = 1; int figures = result.integer.getSize() - 1 - scale; while (comparison != 0 && figures < precision) { result = result.subtract(decrement); comparison = result.pow(n).compare(this); if (comparison == -1) { result = result.add(decrement); decrement = decrement.multiply(ONE_TENTH); } figures = result.integer.getSize() - 1 - scale; } return result; } /** * Determines whether this LargeDecimal is equal to the secondary LargeDecimal * * @param secondary The LargeDecimal to compare against. * @return a boolean if this is equal to secondary. */ public boolean equals(LargeDecimal secondary) { return (this.compare(secondary) == 0); } /** * Returns an integer value between -1 and 1 to specify the comparison between this LargeDecimal and the comparator LargeDecimal * * @param comparator The LargeDecimal to compare against. * @return an integer with the value of... * 1 if: this > comparator * 0 if: this == comparator * -1 if: this < comparator */ public int compare(LargeDecimal comparator) { if (integer.equals(comparator.integer) && this.scale == comparator.scale && this.meta == comparator.meta) { return 0; } else if (this.getSign() == POSITIVE && comparator.getSign() == NEGATIVE) return 1; else if (this.getSign() == NEGATIVE && comparator.getSign() == POSITIVE) return -1; else if (integer.getSize() - scale > comparator.integer.getSize() - comparator.scale) return (this.getSign() == POSITIVE) ? 1 : -1; else if (integer.getSize() - scale < comparator.integer.getSize() - comparator.scale) return (this.getSign() == POSITIVE) ? -1 : 1; //More complicated shit else { LargeDecimal padResults[] = padByteData(this, comparator); for (int i = 0; i < padResults[0].integer.getSize(); i++) { if (padResults[0].integer.read(i) > padResults[1].integer.read(i)) return (this.getSign() == POSITIVE) ? 1 : -1; else if (padResults[0].integer.read(i) < padResults[1].integer.read(i)) return (this.getSign() == POSITIVE) ? -1 : 1; } return 0; } } }