All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.phasmidsoftware.number.core.BigNumber Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2023. Phasmid Software
 */

package com.phasmidsoftware.number.core;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * BigNumber class which represents an exact number of unlimited decimal extent.
 * The representation of the whole part is a BigInteger.
 * The representation of the decimal part is as a true decimal (not binary) number.
 * 

* Addition of Comparable and fixes to add method, as well as long division of BigNumber * and Karatsuba's algorithm (together with main program): * all due to Amrita Dubey. *

* If you're wondering why this isn't written in Scala, it's simply because * I created it for an assignment for a class for which Java is the required language. *

*/ public class BigNumber extends java.lang.Number implements Comparable { /** * Some BigNumber constants. */ public static final BigNumber zero = new BigNumber(0); public static final BigNumber one = new BigNumber(1); public static final BigNumber ten = new BigNumber(10); public static final BigNumber two = new BigNumber(2); // NOTE that the following is an exact number. It is defined as the closest approximation to pi with 100 digits. // But, we know that it is not pi itself. public static final BigNumber pi = BigNumber.parse("3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067"); public static final BigNumber e = BigNumber.parse("2.71828182845904523536028747135266249775724709369995"); /** * Factory method to throw an exception. * * @param ignoredX a double. * @throws BigNumberException because BigNumber is an EXACT representation whereas double is not. */ @SuppressWarnings("UnusedReturnValue") public static BigNumber value(final double ignoredX) { throw new BigNumberException("value(double): This is an inappropriate operation. Please use parse or another value call."); } /** * Factory method to convert a long into a BigNumber. * * @param x a long. * @return a new BigNumber. */ public static BigNumber value(final long x) { return value(BigInteger.valueOf(x)); } /** * Factory method to create a BigNumber from an (exact) BigDecimal. * * @param x a BigDecimal number. * @return a new BigNumber. */ public static BigNumber value(final BigDecimal x) { final BigNumber result = BigNumber.value(x.unscaledValue().longValue()); return result.divide(BigInteger.TEN.pow(x.scale())); } /** * Factory method to create a BigNumber from a Rational (a Scala class). * * @param x a Rational number. * @return a new BigNumber which may or may not be exact. */ public static BigNumber value(final Rational x) { final BigNumber n = BigNumber.value(x.n().bigInteger()); final BigNumber d = BigNumber.value(x.d().bigInteger()); return n.divide(d); } /** * Factory method to create a BigNumber from a long whole number and a long representing the decimals. * * @param whole a non-negative long. * @param decimals a non-negative long, for example 14 for a 3.14 valued result. * @param sign true if the result should be positive. * @return a new BigNumber. */ public static BigNumber value(final long whole, final long decimals, final boolean sign) { if (whole < 0) throw new BigNumberException("value: whole must be non-negative"); if (decimals < 0) throw new BigNumberException("value: decimals must be non-negative"); final int[] dec = new int[MAXLONGDIGITS]; int i = dec.length; long x = decimals; while (x > 0) { int q = (int) (x % 10); dec[--i] = q; x = x / 10; } return new BigNumber(BigInteger.valueOf(Math.abs(whole)), Arrays.copyOfRange(dec, i, MAXLONGDIGITS), sign); } /** * Factory method to create a BigNumber from a whole number and a decimal number. * * @param whole a non-negative long. * @param decimals a non-negative long, for example 14 for a 3.14 valued result. * @return a new BigNumber. */ public static BigNumber value(final long whole, final long decimals) { return value(whole, decimals, true); } /** * Factory method to create a BigNumber from x. * * @param x a BigInteger. * @return a new BigNumber. */ public static BigNumber value(final BigInteger x) { return new BigNumber(x); } /** * Factory method to create a BigNumber from a whole number and a decimal number. * * @param s a String representing a BigNumber. It is optionally preceded by a "-" and, otherwise, is of form "x.y" * @return a new BigNumber. */ public static BigNumber parse(String s) { final Pattern p = Pattern.compile("(-?)(\\d+)(\\.?(\\d*)?)"); final Matcher matcher = p.matcher(s); if (matcher.find()) { final String group4 = matcher.group(4); final int decimals = group4.length(); final int[] dec = new int[decimals]; for (int i = 0; i < decimals; i++) dec[i] = group4.charAt(i) - '0'; return new BigNumber(BigInteger.valueOf(Long.parseLong(matcher.group(2))), dec, !matcher.group(1).equals("-")); } else throw new BigNumberException("cannot parse input string: " + s); } /** * Method to determine if this BigNumber is a whole number. * * @return true if this BigNumber is a whole number. */ public boolean isWhole() { return decimals.length == 0; } /** * Method to determine if this BigNumber is, as expected, exact. * The reason it might not be exact is that we allow division to yield a BigNumber. * For now at least, we simply look at the number of decimals. * CONSIDER in future we might have a field which explicitly determines exactness. * * @return true if this BigNumber is exact. */ public boolean isExact() { return decimals.length < MAXDECIMALDIGITS; } /** * Return a BigDecimal which has the exact value of this BigNumber. * * @return a BigDecimal. */ public BigDecimal toBigDecimal() { final int scale = decimals.length; BigInteger value = whole; for (final int decimal : decimals) { value = value.multiply(BigInteger.TEN).add(BigInteger.valueOf(decimal)); } final BigDecimal result = new BigDecimal(value, scale); return sign ? result : result.negate(); } /** * Returns the value of the specified number as an {@code int}. * * @return the numeric value represented by this object after conversion * to type {@code int}. */ @Override public int intValue() { if (isWhole()) { final int value = whole.intValueExact(); // NOTE: can throw an ArithmeticException return sign ? value : -value; } else throw new BigNumberException("intValue: cannot represent this BigNumber as an int: " + this); } /** * Returns the value of the specified number as a {@code long}. * * @return the numeric value represented by this object after conversion * to type {@code long}. */ @Override public long longValue() { if (isWhole()) { final long value = whole.longValueExact(); // NOTE: can throw an ArithmeticException return sign ? value : -value; } else throw new BigNumberException("longValue: cannot represent this BigNumber as a long: " + this); } /** * Returns the value of the specified number as a {@code float}. * * @return the numeric value represented by this object after conversion * to type {@code float}. */ @Override public float floatValue() { return (float) doubleValue(); } /** * Method to yield a double representation of this BigNumber. * In general, the result will NOT be exact. * But you can check on that by using the isExact() method. * CONSIDER It is possible that isExact() will be false but the resulting double will be exact. * * @return a double representation of this. */ public double doubleValue() { double result = whole.doubleValue(); double factor = 1.0 / 10; for (int decimal : decimals) { result += factor * decimal; factor /= 10; } return sign ? result : -result; } /** * Add a BigNumber that to this. *

* TODO fix the high cyclomatic complexity of this method. * * @param that a BigNumber. * @return the sum of this and that. */ public BigNumber add(final BigNumber that) { if (!sign && !that.sign) return this.negate().add(that.negate()).negate(); if (!sign) return that.add(this); if (this.compareTo(that) < 0) { return that.negate().add(this.negate()).negate(); } // NOTE at this point, sign is always true. final int thisLength = decimals.length; final int thatLength = that.decimals.length; final int resultLength = Math.max(thisLength, thatLength); final int[] dec = new int[resultLength]; int carry = 0; boolean borrow = false; final boolean subtract = !that.sign; for (int i = resultLength - 1; i >= 0; i--) { int sum = carry; borrow = false; if (i < thisLength) sum += decimals[i]; if (i < thatLength) sum += subtract ? -that.decimals[i] : that.decimals[i]; if (sum < 0) { sum += 10; if (i != 0) dec[i - 1] -= 1; else borrow = true; } carry = sum / 10; dec[i] += sum % 10; } BigInteger wholeSum = whole.add((subtract ? that.whole.negate() : that.whole).add(BigInteger.valueOf(carry))); if (borrow) wholeSum = wholeSum.add(BigInteger.valueOf(-1)); if (wholeSum.signum() < 0) { for (int i = 0; i < resultLength; i++) dec[i] = -dec[i]; return new BigNumber(wholeSum.negate(), dec, false); } return new BigNumber(wholeSum, dec, true); } /** * Method to compare this with that. * * @param that the object to be compared. * @return a negative, zero, or positive int. */ public int compareTo(final BigNumber that) { if (this.equals(that)) { return 0; } else { int wholeComparison = this.whole.compareTo(that.whole); if (wholeComparison != 0) { return this.sign ? wholeComparison : -wholeComparison; } else { final int[] thisDecimals = this.decimals; final int[] thatDecimals = that.decimals; final int maxLength = Math.max(thisDecimals.length, thatDecimals.length); for (int i = 0; i < maxLength; i++) { int thisNumber = (i < thisDecimals.length) ? thisDecimals[i] : 0; int thatNumber = (i < thatDecimals.length) ? thatDecimals[i] : 0; if (thisNumber != thatNumber) { return this.sign ? Integer.compare(thisNumber, thatNumber) : Integer.compare(thatNumber, thisNumber); } } return 0; } } } public BigNumber negate() { return new BigNumber(whole, decimals, !sign); } /** * Method to multiply Big Numbers using Karatsuba Algorithm. * * @param other a BigNumber value. * @return a BigNumber. */ public BigNumber multiplyWithKaratsuba(final BigNumber other) { final BigNumber thisW = BigNumber.value(this.whole); final BigNumber thatW = BigNumber.value(other.whole); if (this.isWhole() && other.isWhole()) return thisW.multiply(thatW); int[] first = this.decimals; int[] second = other.decimals; if (first.length < second.length) { first = Arrays.copyOf(first, second.length); } else { second = Arrays.copyOf(second, first.length); } final BigNumber thisF = new BigNumber(BigInteger.ZERO, first, true); final BigNumber thatF = new BigNumber(BigInteger.ZERO, second, true); final BigNumber result1 = thisW.multiply(thatW); final BigNumber result2 = thisW.multiply(thatF); final BigNumber result3 = thatW.multiply(thisF); final int[] result = recursiveKarat(first, second, 0, first.length - 1, 2 * first.length); final BigNumber result4 = new BigNumber(BigInteger.ZERO, result, true); return result1.add(result2).add(result3).add(result4); } private int[] multiplyArrays(final int[] arr1, final int[] arr2, final int start, final int end, final int resultSize) { StringBuilder b = new StringBuilder(); for (int i = start; i <= end; i++) { b.append(arr1[i]); } BigInteger b1 = new BigInteger(b.toString()); b.setLength(0); for (int i = start; i <= end; i++) { b.append(arr2[i]); } final BigInteger b2 = new BigInteger(b.toString()); final String res = b2.multiply(b1).toString(); int[] result = new int[resultSize]; int resIdx = resultSize - 1; for (int i = res.length() - 1; i >= 0; i--) { result[resIdx--] = res.charAt(i) - 48; } return result; } private int[] recursiveKarat(final int[] first, final int[] second, final int start, final int end, final int resSize) { int j = start; // adaptively ignore leading zeros for (int i = j; i <= end; i++) { if (first[i] != 0 || second[i] != 0) { j = i; break; } } final int numDigits = end - j + 1; if (numDigits == 0) { return new int[resSize]; } if (numDigits <= 160) { // return an array of size `resSize` return multiplyArrays(first, second, j, end, resSize); } final int middle = (end + j) / 2; final int m = end - middle; final int[] z0 = recursiveKarat(first, second, j, middle, resSize); final int[] z2 = recursiveKarat(first, second, middle + 1, end, resSize); final int[] sum1 = add(first, j, middle, end, numDigits + 1); final int[] sum2 = add(second, j, middle, end, numDigits + 1); final int[] prod = recursiveKarat(sum1, sum2, 0, sum1.length - 1, 2 * numDigits); difference(prod, z0); difference(prod, z2); leftShift(prod, m); leftShift(z0, 2 * m); addInPlace(z0, prod); addInPlace(z0, z2); return z0; } private void leftShift(final int[] arr, final int offset) { int i = 0; while (i < arr.length && arr[i] == 0) { i++; } for (; i < arr.length; i++) { arr[i - offset] = arr[i]; arr[i] = 0; } } private int[] add(final int[] first, final int start, final int middle, final int end, final int resSize) { final int[] result = new int[resSize]; int carry = 0; int val, toAdd; int k = resSize - 1; for (int i = end, j = middle; j >= start; i--, j--, k--) { if (i <= middle) { toAdd = 0; } else { toAdd = first[i]; } val = toAdd + first[j] + carry; if (val >= 10) { carry = 1; result[k] = val - 10; } else { carry = 0; result[k] = val; } } result[k] = carry; return result; } private void addInPlace(final int[] first, final int[] second) { int j = first.length - 1; int carry = 0; int val; for (int i = first.length - 1, k = second.length - 1; k >= 0; i--, k--, j--) { val = first[i] + second[k] + carry; first[j] = val % 10; carry = val / 10; } if (carry != 0) { first[j] += carry; } } private void difference(final int[] first, final int[] second) { for (int i = first.length - 1, j = second.length - 1; i >= 0; i--, j--) { if (first[i] >= second[j]) { first[i] -= second[j]; } else { first[i] = first[i] + 10 - second[j]; first[i - 1]--; // carry } } } public BigNumber multiply(final BigNumber that) { final BigInteger bigTen = BigInteger.valueOf(10); final int thisLength = decimals.length + 1, thatLength = that.decimals.length + 1; final long[] results = new long[thisLength + thatLength]; final int[] dec = new int[thisLength + thatLength - 1]; for (int i = 0; i < thisLength; i++) for (int j = 0; j < thatLength; j++) results[i + j] += element(i) * that.element(j); BigInteger carry = BigInteger.ZERO; for (int k = results.length; k > 1; k--) { final BigInteger value = carry.add(BigInteger.valueOf(results[k - 1])); final BigInteger[] qr = value.divideAndRemainder(bigTen); carry = qr[0]; dec[k - 2] = qr[1].intValue(); } return new BigNumber(carry.add(BigInteger.valueOf(results[0])), dec, sign == that.sign); } /** * Method to divide this BigNumber by a BigNumber. * * @param x a BigNumber value. * @return a BigNumber y such that x * y = this. */ public BigNumber divide(final BigNumber x) { if (x.decimals.length > 0) { final int n = x.decimals.length; final BigNumber target = this.multiply(BigNumber.value(BigInteger.TEN.pow(n))); final BigNumber divisor = x.multiply(BigNumber.value(BigInteger.TEN.pow(n))); final BigNumber result = target.divide(divisor); return x.sign ? result : result.negate(); } else { final BigNumber quotient = divide(x.whole); return x.sign ? quotient : quotient.negate(); } } /** * Method to divide this BigNumber by a BigInteger. * * @param x a BigInteger value. * @return a BigNumber y such that x * y = this. */ public BigNumber divide(final BigInteger x) { if (x.signum() < 0) return divide(x.negate()).negate(); final BigInteger[] quotientRemainder = whole.divideAndRemainder(x); BigInteger remainder = quotientRemainder[1]; final List dividends = new ArrayList<>(); final BigInteger ten = BigInteger.valueOf(10); for (int i = 0; i < MAXDECIMALDIGITS; i++) { final BigInteger p = remainder.multiply(ten).add(BigInteger.valueOf(i < decimals.length ? decimals[i] : 0)); final BigInteger[] qr = p.divideAndRemainder(x); remainder = qr[1]; dividends.add(qr[0]); if (remainder.signum() == 0 && i > decimals.length) break; } final int[] dec = new int[dividends.size()]; for (int i = 0; i < dec.length; i++) dec[i] = (int) dividends.get(i).longValue(); return new BigNumber(quotientRemainder[0], dec, true); } /** * Method to divide this BigNumber by a long. * The implementation is based on division by BigInteger. * * @param x a long value. * @return a BigNumber y such that x * y = this. */ public BigNumber divide(final long x) { return divide(BigInteger.valueOf(x)); } @Override public boolean equals(final Object o) { if (this == o) return true; // NOTE That if you replace this as recommended, you must ensure that Java is compiling on CircleCI with the correct compiler (Java 16). if ( !(o instanceof BigNumber ) ) return false; final BigNumber bigNumber = (BigNumber) o; return whole.equals(bigNumber.whole) && sign == bigNumber.sign && Arrays.equals(decimals, bigNumber.decimals); } /** * TESTME * * @return the hashCode for this BigNumber. */ @Override public int hashCode() { int result = Objects.hash(whole, sign); result = 31 * result + Arrays.hashCode(decimals); return result; } public String toString() { final StringBuilder result = new StringBuilder(sign ? "" : "-"); result.append(whole); final String dec = decimalsToString(); if (dec.length() > 0) result.append('.'); result.append(dec); return result.toString(); } /** * Primary constructor of BigNumber. * * @param whole the whole number part (must be non-negative). * @param decimals the array of decimal digits. * @param sign true if the result is to a positive number. */ public BigNumber(final BigInteger whole, final int[] decimals, final boolean sign) { if (whole.signum() < 0) throw new BigNumberException("BigNumber constructor: whole must be non-negative"); this.whole = whole; this.decimals = trim(decimals); this.sign = sign; } /** * Secondary constructor to creat a BigNumber which has no decimal part. * * @param whole any BigInteger value (positive or negative). */ public BigNumber(final BigInteger whole) { this(whole.abs(), new int[]{}, whole.signum() >= 0); } /** * Secondary constructor to creat a BigNumber which has no decimal part. * * @param whole any BigInteger value (positive or negative). */ public BigNumber(final long whole) { this(BigInteger.valueOf(whole)); } private String decimalsToString() { final StringBuilder stringBuilder = new StringBuilder(); for (int x : decimals) stringBuilder.append(x); return stringBuilder.toString(); } private static int[] trim(final int[] array) { int i = array.length; for (; i > 0; i--) if (array[i - 1] != 0) break; return Arrays.copyOf(array, i); } private long element(final int i) { if (i == 0) return whole.longValueExact(); else if (i > 0) return decimals[i - 1]; else return 0; // TESTME CONSIDER throwing an exception. } /** * This number is the maximum number of decimal digits that will be generated by division. * If the actual number of decimal digits is less than this number, we assume that the * number is exact. * CONSIDER looking for repeated sequences to shorten this. */ public static final int MAXDECIMALDIGITS = 1000; /** * This is the greatest number of digits that a long number can have. */ public static final int MAXLONGDIGITS = 19; private final BigInteger whole; private final int[] decimals; private final boolean sign; static class BigNumberException extends RuntimeException { public BigNumberException(final String s) { super(s); } } public static void main(final String[] args) { doMain(); } public static void doMain() { final String seed = "3.80264732771409300520864834438671209698720957589958651119907375222849430002967817359000866255197344412894598244154180957589958651119907375222849430002967817359000866255197344412894598244154184095758995865111990737522284943000296781735900086625519734441289459824415418409575899586511199073752228494300029678173590008662551973444128945982441541840957589958651119907375222849430002967817359000866255197344412894598244154184095758995865111990737522284943000296781735900086625519734441289459824415418409575899586511199073752228494300029678173590008662551973444128945982441541840957589958651119907375222849430002967817359000866255197344412894598244154184095758995865111990737522284943000296781735900086625519734441289459824415418409575899586511199073752228494300029678173590008662551973444128945982441541840957589958651119907375222849430002967817359000866255197344412894598244154184095758995865111990737522284943000296781735900086625519734441289459824415418409575899586511199073752228494300029678173590008662551973444128945982441541840957589958651119907375222849430002967817359000866255197344412894598244154184095758995865111990737522284943000296781735900086625519734441289459824415418409575899586511199073752228494300029678173590008662551973444128945982441541840957589958651119907375222849430002967817359000866255197344412894598244154184095758995865111990737522284943000296781735900086625519734441289459824415418409575899586511199073752228494300029678173590008662551973444128945982441541840957589958651119907375222849430002967817359000866255197344412894598244154184095758995865111990737522284943000296781735900086625519734441289459824415418409575899586511199073752228494300029678173590008662551973444128945982441541840957589958651119907375222849430002967817359000866255197344412894598244154184095758995865111990737522284943000296781735900086625519734441289459824415418409575899586511199073752228494300029678173590008662551973444128945982441541840957589958651119907375222849430002967817359000866255197344412894598244154184095758995865111990737522284943000296781735900086625519734441289459824415418409575899586511199073752228494300029678173590008662551973444128945982441541840957589958651119907375222849430002967817359000866255197344412894598244154184095758995865111990737522284943000296781735900086625519734441289459824415418409575899586511199073752228494300029678173590008662551973444128945982441541840957589958651119907375222849430002967817359000866255197344412894598244154184095758995865111990737522284943000296781735900086625519734441289459824415418448117483823635540800639306781065132028198662321859262596212005648226134579249871801970850380282492694242171297757609737902183514884179014706720272008148141514118681922046978558050713877551342513339470002439445599760202735750907375222849430002967817359000866255197344412894598244154184811748382363554080063930678106513202819866232185926259621200564822613457924987180197085038028249269424217129775760973790218351488417901470672027200814814151411868192204697855805071387755134251333947000243944559976020273575090737522284943000296781735900086625519734441289459824415418481174838236355408006393067810651320281986623218592625962120056482261345792498718019708503802824926942421712977576097379021835148841790147067202720081481415141186819220469785580507138775513425133394700024394455997602027357506449179433722141754241393658320727321911046813620076340538390386873407571606319347045589812102646318413325303681054856"; System.out.println("Seed length: " + seed.length()); final String seed1 = seed.substring(0, 800); final BigNumber b = parse(seed1); System.out.println(seed1.length()); final long[] arr = new long[10]; // CONSIDER using StopWatch or Timer. for (int i = 0; i < 10; i++) { final long st = System.currentTimeMillis(); final BigNumber res = b.multiply(b); // NOTE: we need this!!! final long et = System.currentTimeMillis(); final long time = et - st; arr[i] = time; } long averageTime = 0; for (long time : arr) { averageTime += time; } final double totalTime = (double) averageTime / 10; System.out.println("Standard: " + totalTime); final long[] arr1 = new long[10]; for (int i = 0; i < 10; i++) { final long st = System.currentTimeMillis(); final BigNumber res = b.multiplyWithKaratsuba(b); final long et = System.currentTimeMillis(); final long time = et - st; arr1[i] = time; } long averageTime2 = 0; for (final long time : arr1) { averageTime2 += time; } final double totalTime2 = (double) averageTime2 / 10; System.out.println("Karatsuba: " + totalTime2); System.out.println(((totalTime - totalTime2) / totalTime) * 100); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy