Skip to content
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

Karatsuba added, 2 bugs are fixed #328

Merged
merged 15 commits into from
May 14, 2021
Merged

Karatsuba added, 2 bugs are fixed #328

merged 15 commits into from
May 14, 2021

Conversation

zhelenskiy
Copy link
Contributor

  • Karatsuba was added and increased benchmarks results.
  • The bug that was caused by abs(Int.MIN_VALUE) == Int.MIN_VALUE was fixed.
  • The equals function now behaves with instances of other types as all other objects do.

@zhelenskiy
Copy link
Contributor Author

(Slack discussion)

@breandan
Copy link
Contributor

I recently learned that E.A. Karatsuba has a fast method for evaluating transcendentals, which can be useful for working with certain probability distributions. It might be worth looking into at some point: https://en.wikipedia.org/wiki/FEE_method

@zhelenskiy
Copy link
Contributor Author

@breandan However, the Karatsuba's algorithm that is subject of the PR is fast multiplication.

Copy link
Member

@altavir altavir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes in the core are not appropriate at the moment, they should be discussed in a separate issue first. Otherwise looks fine. It would be nice to have your benchmarking results in docs and a separate example in examples.

else -> powWithoutOptimization(exponent)
}

private fun T.powWithoutOptimization(exponent: ULong): T = when (exponent) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation details should not be present In generic algebra. Probably better to use extensions instead. Like Ring<T>.pow(T, exponent). This change needs additional discussion.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely agree with @altavir.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it should be placed there (as an extension). Thank you! I'll fix it.
Furthermore, it may be better to create a function in BigInt that would make it possible to use power for bigints without BigIntField { }. What do you think, @altavir @CommanderTvis? After inventing multiple receivers that should be replaced though.

@@ -250,6 +250,22 @@ public interface Ring<T> : Group<T>, RingOperations<T> {
* neutral operation for multiplication
*/
public val one: T

public fun T.pow(exponent: ULong): T = when {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any real use-cases for Long power?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean negative exponent usage? If so, no. It is impossible to implement.

Or do you mean big range? If so, yes for some non-bigint rings. As we don't know what the rings are, we cannot say anything about the usage of exponents of UInt.MAX_VALUE.toULong().inc()..ULog.MAX_VALUE

else -> powWithoutOptimization(exponent)
}

private fun T.powWithoutOptimization(exponent: ULong): T = when (exponent) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I completely agree with @altavir.


val z0 = x0 * y0
val z2 = x1 * y1
val z1 = (x0 + x1) * (y1 + y0) - z0 - z2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it be more optimal to use the other formula for z1, namely $z1 = (x0-x1)*(y1-y0)+z2+z0$ ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it.

@zhelenskiy
Copy link
Contributor Author

I have submitted what you suggested. Also, I fixed bugs, listed in Slack. I decreased the overhead of parsing of BigInt.

@zhelenskiy
Copy link
Contributor Author

The changes in the core are not appropriate at the moment, they should be discussed in a separate issue first. Otherwise looks fine. It would be nice to have your benchmarking results in docs and a separate example in examples.

  • I think they should be discussed in Slack because it is already in the PR
  • Which results? Furthermore, there is still a Fourier -based multiplication method to be implemented first to publish results then.

@zhelenskiy
Copy link
Contributor Author

I found that the addition of large integers is slower with KMath BigInt than with JVM BigInteger: I added benchmarks for that.

*/
public val one: T
}

@UnstableKMathAPI
public fun <T> Ring<T>.pow(base: T, exponent: ULong): T = when {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add KDoc

}

@UnstableKMathAPI
public fun <T> Ring<T>.pow(base: T, exponent: UInt): T = pow(base, exponent.toULong())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add KDoc

@UnstableKMathAPI
public fun <T> Ring<T>.pow(base: T, exponent: UInt): T = pow(base, exponent.toULong())

private fun <T> Ring<T>.powWithoutOptimization(base: T, exponent: ULong): T = when (exponent) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move pow operation to algebraExtensions.kt. It actually already have those functions: https://github.com/mipt-npm/kmath/blob/ca02f5406d06d18d07909f3d48508e1a99701fa5/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/algebraExtensions.kt#L107, so you shoud probably just replace the implementation and use UInt instead of Int.

Copy link
Contributor Author

@zhelenskiy zhelenskiy May 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not ULong? Because of being consistent with Field<T>.power which has a stable API?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I see a bug with Field.power:

public fun <T> Field<T>.power(arg: T, power: Int): T {
    require(power != 0 || arg != zero) { "The $zero raised to $power is not defined." }
    if (power == 0) return one
    if (power < 0) return one / (this as Ring<T>).power(arg, -power)
    return (this as Ring<T>).power(arg, power)
}

If [power] is Int.MIN_VALUE, this will fail

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation could be wrong in corner cases. It is not properly tested. As for ULong, the major bulk of operations on JVM and in kotlin use Int. I can't see a case where people would use more than Int for a power (please show them if you know them) and using Ulong would force users to do an additional unnecessary conversion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am absolutely sure that there is no need to power numbers by ULong ranged numbers, however, this may be ok for some other Rings (albeit I don't know such ones).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, 0^0 is not defined here while it is 1 in java's BigInt. Which convention to choose?

public fun <T> Ring<T>.power(arg: T, power: Int): T {
    require(power >= 0) { "The power can't be negative." }
    require(power != 0 || arg != zero) { "The $zero raised to $power is not defined." }
    if (power == 0) return one
    var res = arg
    repeat(power - 1) { res *= arg }
    return res
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's stick to UInt for now because I still can't imagine a case for longs. Since we are using extensions, it is easy to add another one if somebody needs it. As for 0^0, please fix it. I don't remember where this method comes from, but it was quite obviously forgotten.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean that 0^0 is expected to be 1?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I published the changes.

else
-this * kotlin.math.abs(other).toUInt()
@UnstableKMathAPI
public fun pow(other: ULong): BigInt = BigIntField { pow(this@BigInt, other) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BigIntField.pow(this, other) should be more concise.

@zhelenskiy zhelenskiy requested a review from altavir May 13, 2021 19:45
@altavir altavir merged commit c1b94ff into SciProgCentre:dev May 14, 2021
Copy link
Contributor Author

@zhelenskiy zhelenskiy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The was a mistake in the added code It was discussed in Slack. It was fixed in the further commits to the repo.

public fun <T> Field<T>.power(arg: T, power: Int): T = when {
power < 0 -> one / (this as Ring<T>).power(arg, if (power == Int.MIN_VALUE) Int.MAX_VALUE.toUInt().inc() else (-power).toUInt())
else -> (this as Ring<T>).power(arg, power.toUInt())
public fun <T> Field<T>.power(arg: T, power: UInt): T = when {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@altavir You are wrong here: Field can have negative power as Field has division. That was even in the previous implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is reverted.

@altavir altavir mentioned this pull request Apr 11, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants