Skip to content

Added some series-based functions (exp, cos, sin, ...) #235

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ package com.ionspin.kotlin.bignum.decimal
import com.ionspin.kotlin.bignum.BigNumber
import com.ionspin.kotlin.bignum.CommonBigNumberOperations
import com.ionspin.kotlin.bignum.NarrowingOperations
import com.ionspin.kotlin.bignum.decimal.util.CosineCalculator
import com.ionspin.kotlin.bignum.decimal.util.ExponentialCalculator
import com.ionspin.kotlin.bignum.decimal.util.HyperbolicCosineCalculator
import com.ionspin.kotlin.bignum.decimal.util.HyperbolicSineCalculator
import com.ionspin.kotlin.bignum.decimal.util.SineCalculator
import com.ionspin.kotlin.bignum.integer.BigInteger
import com.ionspin.kotlin.bignum.integer.Platform
import com.ionspin.kotlin.bignum.integer.RuntimePlatform
Expand Down Expand Up @@ -1696,6 +1701,73 @@ class BigDecimal private constructor(
*/
override fun signum(): Int = significand.signum()

/**
* Exponential function (e^x)
* @param decimalMode the decimal mode to use. Precision and rounding mode must be specified.
* @return Result of exponential function for this BigDecimal.
*/
fun exp(decimalMode: DecimalMode): BigDecimal {
return ExponentialCalculator.calculate(this, decimalMode)
}

/**
* Sine function
* @param decimalMode the decimal mode to use. Precision and rounding mode must be specified.
* @return Result of sine function for this BigDecimal.
*/
fun sin(decimalMode: DecimalMode): BigDecimal {
return SineCalculator.calculate(this, decimalMode)
}

/**
* Cosine function
* @param decimalMode the decimal mode to use. Precision and rounding mode must be specified.
* @return Result of cosine function for this BigDecimal.
*/
fun cos(decimalMode: DecimalMode): BigDecimal {
return CosineCalculator.calculate(this, decimalMode)
}

/**
* Tangent function
* @param decimalMode the decimal mode to use. Precision and rounding mode must be specified.
* @return Result of tangent function for this BigDecimal.
*/
fun tan(decimalMode: DecimalMode): BigDecimal {
val higherPrecisionMode = decimalMode.copy(decimalPrecision = decimalMode.decimalPrecision + 3)
return sin(higherPrecisionMode)
.divide(cos(higherPrecisionMode), decimalMode)
}

/**
* Hyperbolic sine function
* @param decimalMode the decimal mode to use. Precision and rounding mode must be specified.
* @return Result of hyperbolic sine function for this BigDecimal.
*/
fun sinh(decimalMode: DecimalMode): BigDecimal {
return HyperbolicSineCalculator.calculate(this, decimalMode)
}

/**
* Hyperbolic cosine function
* @param decimalMode the decimal mode to use. Precision and rounding mode must be specified.
* @return Result of hyperbolic cosine function for this BigDecimal.
*/
fun cosh(decimalMode: DecimalMode): BigDecimal {
return HyperbolicCosineCalculator.calculate(this, decimalMode)
}

/**
* Hyperbolic tangent function
* @param decimalMode the decimal mode to use. Precision and rounding mode must be specified.
* @return Result of hyperbolic tangent function for this BigDecimal.
*/
fun tanh(decimalMode: DecimalMode): BigDecimal {
val higherPrecisionMode = decimalMode.copy(decimalPrecision = decimalMode.decimalPrecision + 3)
return sinh(higherPrecisionMode)
.divide(cosh(higherPrecisionMode), decimalMode)
}

/**
* The next group of functions are implementations of the NarrowingOperations interface
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.ionspin.kotlin.bignum.decimal.util

import com.ionspin.kotlin.bignum.decimal.BigDecimal
import com.ionspin.kotlin.bignum.decimal.DecimalMode

object CosineCalculator : SeriesCalculator() {
private val MINUS_ONE = BigDecimal.parseString("-1")

override fun createTermSequence(x: BigDecimal, decimalMode: DecimalMode): Sequence<BigDecimal> {
// 1, x^2, x^4, x^6, ...
val powerSequence = createAllPowersSequence(x, decimalMode)
.filterIndexed { i, _ -> i % 2 == 0 }

// 1/0!, -1/2!, 1/4!, -1/6!, ...
val factorSequence = createAllFactorialsSequence()
.filterIndexed { i, _ -> i % 2 == 0 }
.mapIndexed { i, n ->
val sign = if (i % 2 == 0) BigDecimal.ONE else MINUS_ONE
sign.divide(n, decimalMode)
}

return powerSequence.zip(factorSequence) { a, b -> a.multiply(b, decimalMode) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.ionspin.kotlin.bignum.decimal.util

import com.ionspin.kotlin.bignum.decimal.BigDecimal
import com.ionspin.kotlin.bignum.decimal.DecimalMode

object ExponentialCalculator : SeriesCalculator() {
override fun createTermSequence(x: BigDecimal, decimalMode: DecimalMode): Sequence<BigDecimal> {
// 1, x, x^2, x^3, ...
val powerSequence = createAllPowersSequence(x, decimalMode)

// 1/0!, 1/1!, 1/2!, 1/3!, ...
val factorSequence = createAllFactorialsSequence()
.map { n -> BigDecimal.ONE.divide(n, decimalMode) }

return powerSequence.zip(factorSequence) { a, b -> a.multiply(b, decimalMode) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ionspin.kotlin.bignum.decimal.util

import com.ionspin.kotlin.bignum.decimal.BigDecimal
import com.ionspin.kotlin.bignum.decimal.DecimalMode

object HyperbolicCosineCalculator : SeriesCalculator() {
override fun createTermSequence(x: BigDecimal, decimalMode: DecimalMode): Sequence<BigDecimal> {
// 1, x^2, x^4, x^6, ...
val powerSequence = createAllPowersSequence(x, decimalMode)
.filterIndexed { i, _ -> i % 2 == 0 }

// 1/0!, 1/2!, 1/4!, 1/6!, ...
val factorSequence = createAllFactorialsSequence()
.filterIndexed { i, _ -> i % 2 == 0 }
.map { n -> BigDecimal.ONE.divide(n, decimalMode) }

return powerSequence.zip(factorSequence) { a, b -> a.multiply(b, decimalMode) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ionspin.kotlin.bignum.decimal.util

import com.ionspin.kotlin.bignum.decimal.BigDecimal
import com.ionspin.kotlin.bignum.decimal.DecimalMode

object HyperbolicSineCalculator : SeriesCalculator() {
override fun createTermSequence(x: BigDecimal, decimalMode: DecimalMode): Sequence<BigDecimal> {
// x, x^3, x^5, x^7, ...
val powerSequence = createAllPowersSequence(x, decimalMode)
.filterIndexed { i, _ -> i % 2 != 0 }

// 1/1!, 1/3!, 1/5!, 1/7!, ...
val factorSequence = createAllFactorialsSequence()
.filterIndexed { i, _ -> i % 2 != 0 }
.map { n -> BigDecimal.ONE.divide(n, decimalMode) }

return powerSequence.zip(factorSequence) { a, b -> a.multiply(b, decimalMode) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.ionspin.kotlin.bignum.decimal.util

import com.ionspin.kotlin.bignum.decimal.BigDecimal
import com.ionspin.kotlin.bignum.decimal.DecimalMode

/**
* Utility abstract class for implementing infinite series summations.
*
* Subclasses must ensure that the series actually converges (at least for
* all values you plan to calculate.)
*/
abstract class SeriesCalculator {
fun calculate(x: BigDecimal, decimalMode: DecimalMode): BigDecimal {
val higherPrecisionMode = decimalMode.copy(decimalPrecision = (decimalMode.decimalPrecision * 1.1).toLong() + 2)
val epsilon = BigDecimal.ONE.moveDecimalPoint(-higherPrecisionMode.decimalPrecision)

return createTermSequence(x, higherPrecisionMode)
.takeWhile { step -> step.abs() > epsilon }
.fold(BigDecimal.ZERO) { acc, step -> acc.add(step) }
.roundSignificand(decimalMode)
}

/**
* Implemented by subclasses to create an appropriate sequence of terms for the series.
*/
abstract fun createTermSequence(x: BigDecimal, decimalMode: DecimalMode): Sequence<BigDecimal>

/**
* Utility function for subclasses to use which produces a sequence of all powers of `x`,
* starting at 0.
*/
protected fun createAllPowersSequence(x: BigDecimal, decimalMode: DecimalMode): Sequence<BigDecimal> {
return generateSequence(BigDecimal.ONE) { n -> n.multiply(x, decimalMode) }
}

/**
* Utility function for subclasses to use which produces a sequence of all factorials,
* starting at 0! = 1, then 1! = 1, 2! = 2, 3! = 6, etc.
*/
protected fun createAllFactorialsSequence(): Sequence<BigDecimal> {
return generateSequence(1) { n -> n + 1 }
.runningFold(BigDecimal.ONE) { acc, n -> acc * n }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.ionspin.kotlin.bignum.decimal.util

import com.ionspin.kotlin.bignum.decimal.BigDecimal
import com.ionspin.kotlin.bignum.decimal.DecimalMode

object SineCalculator : SeriesCalculator() {
private val MINUS_ONE = BigDecimal.parseString("-1")

override fun createTermSequence(x: BigDecimal, decimalMode: DecimalMode): Sequence<BigDecimal> {
// x, x^3, x^5, x^7, ...
val powerSequence = createAllPowersSequence(x, decimalMode)
.filterIndexed { i, _ -> i % 2 != 0 }

// 1/1!, -1/3!, 1/5!, -1/7!, ...
val factorSequence = createAllFactorialsSequence()
.filterIndexed { i, _ -> i % 2 != 0 }
.mapIndexed { i, n ->
val sign = if (i % 2 == 0) BigDecimal.ONE else MINUS_ONE
sign.divide(n, decimalMode)
}

return powerSequence.zip(factorSequence) { a, b -> a.multiply(b, decimalMode) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.ionspin.kotlin.bignum.decimal

import kotlin.test.Test
import kotlin.test.assertEquals

class BigDecimalExpTest {

@Test
fun expTest() {
val decimalMode = DecimalMode(decimalPrecision = 20, roundingMode = RoundingMode.TOWARDS_ZERO)
val examples = listOf(
"0" to "1",
"0.2" to "1.2214027581601698339",
"0.4" to "1.4918246976412703178",
"0.6" to "1.8221188003905089748",
"0.8" to "2.2255409284924676045",
"1" to "2.7182818284590452353",
"2" to "7.3890560989306502272",
"-0.2" to "0.81873075307798185866",
"-0.4" to "0.67032004603563930074",
"-0.6" to "0.54881163609402643262",
)

for ((input, expected) in examples) {
assertEquals(BigDecimal.parseString(expected), BigDecimal.parseString(input).exp(decimalMode))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.ionspin.kotlin.bignum.decimal

import kotlin.test.Test
import kotlin.test.assertEquals

class BigDecimalTrigTest {

@Test
fun sinTest() {
val decimalMode = DecimalMode(decimalPrecision = 20, roundingMode = RoundingMode.TOWARDS_ZERO)
val examples = listOf(
"0" to "0",
"0.2" to "0.19866933079506121545",
"0.4" to "0.38941834230865049166",
"0.6" to "0.56464247339503535720",
)

for ((input, expected) in examples) {
assertEquals(BigDecimal.parseString(expected), BigDecimal.parseString(input).sin(decimalMode))
}
}

@Test
fun cosTest() {
val decimalMode = DecimalMode(decimalPrecision = 20, roundingMode = RoundingMode.TOWARDS_ZERO)
val examples = listOf(
"0" to "1",
"0.2" to "0.98006657784124163112",
"0.4" to "0.92106099400288508279",
"0.6" to "0.82533561490967829724",
)

for ((input, expected) in examples) {
assertEquals(BigDecimal.parseString(expected), BigDecimal.parseString(input).cos(decimalMode))
}
}

@Test
fun tanTest() {
val decimalMode = DecimalMode(decimalPrecision = 20, roundingMode = RoundingMode.TOWARDS_ZERO)
val examples = listOf(
"0" to "0",
"0.2" to "0.20271003550867248332",
"0.4" to "0.42279321873816176198",
"0.6" to "0.68413680834169231707",
)

for ((input, expected) in examples) {
assertEquals(BigDecimal.parseString(expected), BigDecimal.parseString(input).tan(decimalMode))
}
}

@Test
fun sinhTest() {
val decimalMode = DecimalMode(decimalPrecision = 20, roundingMode = RoundingMode.TOWARDS_ZERO)
val examples = listOf(
"0" to "0",
"0.2" to "0.20133600254109398762",
"0.4" to "0.41075232580281550854",
"0.6" to "0.63665358214824127112",
)

for ((input, expected) in examples) {
assertEquals(BigDecimal.parseString(expected), BigDecimal.parseString(input).sinh(decimalMode))
}
}

@Test
fun coshTest() {
val decimalMode = DecimalMode(decimalPrecision = 20, roundingMode = RoundingMode.TOWARDS_ZERO)
val examples = listOf(
"0" to "1",
"0.2" to "1.0200667556190758462",
"0.4" to "1.0810723718384548092",
"0.6" to "1.1854652182422677037",
)

for ((input, expected) in examples) {
assertEquals(BigDecimal.parseString(expected), BigDecimal.parseString(input).cosh(decimalMode))
}
}

@Test
fun tanhTest() {
val decimalMode = DecimalMode(decimalPrecision = 20, roundingMode = RoundingMode.TOWARDS_ZERO)
val examples = listOf(
"0" to "0",
"0.2" to "0.19737532022490400073",
"0.4" to "0.37994896225522488526",
"0.6" to "0.53704956699803528586",
)

for ((input, expected) in examples) {
assertEquals(BigDecimal.parseString(expected), BigDecimal.parseString(input).tanh(decimalMode))
}
}
}