added efficient Factorial algorithm from Guava library

This commit is contained in:
Dmitry
2022-02-03 11:51:51 +07:00
parent 4270cc107d
commit f3b7a7aff0
8 changed files with 226 additions and 2 deletions

View File

@ -0,0 +1,140 @@
package other
import java.math.BigInteger
/**
* This algorithm is taken from Google Guava library
*
*/
class FactorialAdvanced {
private val factorials = longArrayOf(
1L,
1L,
1L * 2,
1L * 2 * 3,
1L * 2 * 3 * 4,
1L * 2 * 3 * 4 * 5,
1L * 2 * 3 * 4 * 5 * 6,
1L * 2 * 3 * 4 * 5 * 6 * 7,
1L * 2 * 3 * 4 * 5 * 6 * 7 * 8,
1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9,
1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10,
1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11,
1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12,
1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13,
1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14,
1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15,
1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16,
1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17,
1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18,
1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19,
1L * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19 * 20
)
fun compute(n: Int): BigInteger? {
if (n <= 0) {
return BigInteger.ZERO
}
if (n < factorials.size) {
return BigInteger.valueOf(factorials[n])
}
// Pre-allocate space for our list of intermediate BigIntegers.
val approxSize = divide(n * log2Celling(n), java.lang.Long.SIZE)
val bigNumbers = ArrayList<BigInteger>(approxSize)
// Start from the pre-computed maximum long factorial.
val startingNumber = factorials.size
var number = factorials[startingNumber - 1]
// Strip off 2s from this value.
var shift = java.lang.Long.numberOfTrailingZeros(number)
number = number shr shift
// Use floor(log2(num)) + 1 to prevent overflow of multiplication.
var numberBits = log2Floor(number) + 1
var bits = log2Floor(startingNumber.toLong()) + 1
// Check for the next power of two boundary, to save us a CLZ operation.
var nextPowerOfTwo = 1 shl bits - 1
// Iteratively multiply the longs as big as they can go.
for (num in startingNumber..n) {
// Check to see if the floor(log2(num)) + 1 has changed.
if ((num and nextPowerOfTwo) != 0) {
nextPowerOfTwo = nextPowerOfTwo shl 1
bits++
}
// Get rid of the 2s in num.
val tz = java.lang.Long.numberOfTrailingZeros(num.toLong())
val normalizedNum = (num shr tz).toLong()
shift += tz
// Adjust floor(log2(num)) + 1.
val normalizedBits = bits - tz
// If it won't fit in a long, then we store off the intermediate product.
if (normalizedBits + numberBits >= java.lang.Long.SIZE) {
bigNumbers.add(BigInteger.valueOf(number))
number = 1
numberBits = 0
}
number *= normalizedNum
numberBits = log2Floor(number) + 1
}
// Check for leftovers.
if (number > 1) {
bigNumbers.add(BigInteger.valueOf(number))
}
// Efficiently multiply all the intermediate products together.
return listNumbers(bigNumbers).shiftLeft(shift)
}
/**
* Returns the result of dividing p by q, rounding using the celling
*
* @throws ArithmeticException if q == 0
*/
private fun divide(number: Int, divider: Int): Int {
if (divider == 0) {
throw ArithmeticException("/ by zero") // for GWT
}
val div = number / divider
val rem = number - divider * div // equal to number % divider
if (rem == 0) {
return div
}
val signedNumber = 1 or (number xor divider shr Integer.SIZE - 1)
val increment = signedNumber > 0
return if (increment) div + signedNumber else div
}
private fun listNumbers(numbers: List<BigInteger>, start: Int = 0, end: Int = numbers.size): BigInteger {
return when (end - start) {
0 -> BigInteger.ONE
1 -> numbers[start]
2 -> numbers[start].multiply(numbers[start + 1])
3 -> numbers[start].multiply(numbers[start + 1]).multiply(numbers[start + 2])
else -> {
// Otherwise, split the list in half and recursively do this.
val m = end + start ushr 1
listNumbers(numbers, start, m).multiply(listNumbers(numbers, m, end))
}
}
}
/**
* Returns the base-2 logarithm of number, rounded according to the celling.
*
*/
private fun log2Celling(number: Int): Int {
return Integer.SIZE - Integer.numberOfLeadingZeros(number - 1)
}
/**
* Returns the base-2 logarithm of number rounded according to the floor.
*
*/
private fun log2Floor(number: Long): Int {
return java.lang.Long.SIZE - 1 - java.lang.Long.numberOfLeadingZeros(number)
}
}

View File

@ -0,0 +1,82 @@
package other
import org.junit.Test
import org.junit.jupiter.api.Assertions
internal class FactorialAdvancedTest {
private val factorial = FactorialAdvanced()
@Test
fun test_factorial_30() {
val actual = factorial.compute(30)
Assertions.assertEquals("265252859812191058636308480000000", actual.toString())
}
@Test
fun test_factorial_60() {
val actual = factorial.compute(60)
Assertions.assertEquals("8320987112741390144276341183223364380754172606361245952449277696409600000000000000", actual.toString())
}
@Test
fun test_factorial_90() {
val expected = "93326215443944152681699238856266700490715968264381621468592963" +
"895217599993229915608941463976156518286253697920827223758251185210916" +
"864000000000000000000000000"
val actual = factorial.compute(100)
Assertions.assertEquals(expected, actual.toString())
}
@Test
fun test_factorial_200() {
val expected = "788657867364790503552363213932185062295135977687173263294742533244359449963403342920304" +
"2840119846239041772121389196388302576427902426371050619266249528299311134628572707633172373969" +
"8894392244562145166424025403329186413122742829485327752424240757390324032125740557956866022603" +
"1904170324062351700858796178922222789623703897374720000000000000000000000000000000000000000000000000"
val actual = factorial.compute(200)
Assertions.assertEquals(expected, actual.toString())
}
@Test
fun test_factorial_1000() {
val expected = "40238726007709377354370243392300398571937486421071463254379991042993851" +
"2398629020592044208486969404800479988610197196058631666872994808558901323829669" +
"9445909974245040870737599188236277271887325197795059509952761208749754624970436" +
"0141827809464649629105639388743788648733711918104582578364784997701247663288983" +
"5955735432513185323958463075557409114262417474349347553428646576611667797396668" +
"8202912073791438537195882498081268678383745597317461360853795345242215865932019" +
"2809087829730843139284440328123155861103697680135730421616874760967587134831202" +
"5478589320767169132448426236131412508780208000261683151027341827977704784635868" +
"1701643650241536913982812648102130927612448963599287051149649754199093422215668" +
"3257208082133318611681155361583654698404670897560290095053761647584772842188967" +
"9646244945160765353408198901385442487984959953319101723355556602139450399736280" +
"7501378376153071277619268490343526252000158885351473316117021039681759215109077" +
"8801939317811419454525722386554146106289218796022383897147608850627686296714667" +
"4697562911234082439208160153780889893964518263243671616762179168909779911903754" +
"0312746222899880051954444142820121873617459926429565817466283029555702990243241" +
"5318161721046583203678690611726015878352075151628422554026517048330422614397428" +
"6933061690897968482590125458327168226458066526769958652682272807075781391858178" +
"8896522081643483448259932660433676601769996128318607883861502794659551311565520" +
"3609398818061213855860030143569452722420634463179746059468257310379008402443243" +
"8465657245014402821885252470935190620929023136493273497565513958720559654228749" +
"7740114133469627154228458623773875382304838656889764619273838149001407673104466" +
"4025989949022222176590433990188601856652648506179970235619389701786004081188972" +
"9918311021171229845901641921068884387121855646124960798722908519296819372388642" +
"6148396573822911231250241866493531439701374285319266498753372189406942814341185" +
"2015801412334482801505139969429015348307764456909907315243327828826986460278986" +
"4321139083506217095002597389863554277196742822248757586765752344220207573630569" +
"4988250879689281627538488633969099598262809561214509948717012445164612603790293" +
"0912088908694202851064018215439945715680594187274899809425474217358240106367740" +
"4595741785160829230135358081840096996372524230560855903700624271243416909004153" +
"6901059339838357779394109700277534720000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000000000000000000000" +
"000000000000000000000000000000000000000000000000"
val actual = factorial.compute(1000)
Assertions.assertEquals(expected, actual.toString())
}
}

View File

@ -18,11 +18,13 @@ internal class FactorialTest {
@Test
fun test_recursive() {
assertEquals(1, factorial.compute(0))
assertEquals(1, factorial.compute(1))
assertEquals(1, factorial.computeRecursive(0))
assertEquals(1, factorial.computeRecursive(1))
assertEquals(6, factorial.computeRecursive(3))
assertEquals(120, factorial.computeRecursive(5))
assertEquals(720, factorial.computeRecursive(6))
}
}