In Squeak Smalltalk, there is a nthRoot:
message that answers the exact Integer
result if ever the Integer receiver is exact nth power of some whole number. However, if the solution is an algebraic root, then the implementation does not fallback to a naive n**(1/exp)
; the method rounds to nearest float by appropriate care of residual.
Relevant code (MIT license) is reproduced here. The base algorithm is searching for truncated nth root of an Integer with some Newton-Raphson:
Integer>>nthRootTruncated: aPositiveInteger
"Answer the integer part of the nth root of the receiver."
| guess guessToTheNthMinusOne nextGuess |
self = 0 ifTrue: [^0].
self negative
ifTrue:
[aPositiveInteger even ifTrue: [ ArithmeticError signal: 'Negative numbers don''t have even roots.' ].
^(self negated nthRootTruncated: aPositiveInteger) negated].
guess := 1 bitShift: self highBitOfMagnitude + aPositiveInteger - 1 // aPositiveInteger.
[
guessToTheNthMinusOne := guess raisedTo: aPositiveInteger - 1.
nextGuess := (aPositiveInteger - 1 * guess * guessToTheNthMinusOne + self) // (guessToTheNthMinusOne * aPositiveInteger).
nextGuess >= guess ] whileFalse:
[ guess := nextGuess ].
( guess raisedTo: aPositiveInteger) > self ifTrue:
[ guess := guess - 1 ].
^guess
It's not particularly clever, because convergence can be very slow in case of huge exponent, but well, it works.
Then, the same root rounded away from zero:
Integer>>nthRootRounded: aPositiveInteger
"Answer the integer nearest the nth root of the receiver."
| guess |
self = 0 ifTrue: [^0].
self negative
ifTrue:
[aPositiveInteger even ifTrue: [ ArithmeticError signal: 'Negative numbers don''t have even roots.' ].
^(self negated nthRootRounded: aPositiveInteger) negated].
guess := self nthRootTruncated: aPositiveInteger.
^self * 2 > ((guess + 1 raisedTo: aPositiveInteger) + (guess raisedTo: aPositiveInteger))
ifTrue: [guess + 1]
ifFalse: [guess]
Then exactness is tested in nthRoot:
Integer>>nthRoot: aPositiveInteger
"Answer the nth root of the receiver.
Answer an Integer if root is exactly this Integer, else answer the Float nearest the exact root."
| guess excess scaled nBits |
guess := self nthRootRounded: aPositiveInteger.
excess := (guess raisedTo: aPositiveInteger) - self.
excess = 0 ifTrue: [ ^ guess ].
nBits := Float precision - guess highBitOfMagnitude.
nBits <= 0 ifTrue: [ ^(Fraction numerator: guess * 4 - excess sign denominator: 4) asFloat].
scaled := self << (nBits * aPositiveInteger).
guess := scaled nthRootRounded: aPositiveInteger.
excess := (guess raisedTo: aPositiveInteger) - scaled.
^(Fraction numerator: guess * 4 - excess sign denominator: 1 << (nBits + 2)) asFloat
This could be applied to Fraction too, but the nearest float is a bit more complex, and Squeak implementation is currently naive.
It works for large integers like:
(10 raisedTo: 600) nthRoot: 300
-> 100
"exact"
(10 raisedTo: 600) + 1 nthRoot: 300
-> 100.0
"inexact"
If you don't have such expectations, the initial guess could use inexact naive n**(1/exp)
.
The code should be easy to port in Python and leaves a lot of place for optimization.
I didn't check what was available in Python, but maybe you'll need correctly rounded LargeInteger -> Float, and Fraction -> Float, like explained here (Smalltalk too, sorry about that, but the language does not really matter).