I am working on a project to implement a rational number type which overloads as many intrinsic operators and procedures as I can stand to write tests for. I want to solicit opinions on how best to implement operator(**)
for raising a rational number to a real power. I can think of two natural ways to do it
- Convert the rational number to a real (divide numerator by denominator) and then exponentiate the result.
- Exponentiate the numerator and denominator of the rational number separately, and then divide the two results. In the case of negative powers, swap the numerator and denominator first and raise them to a positive power.
Due to the finite precision of floating point, these two are not equivalent. I can come up with test cases which make either one look superior.
For example, consider the expression rational(2, 3)**(-3.0)
representing raising 2/3 to the -3 power, which “ought to” result in 27/8. The true-mathematics result is exactly representable in floating point, but method #1 will not give it due to the inexact intermediate step in computing a floating-point approximation to 2/3. Method #2 will give the exact result, since the intermediate steps will all be exactly representable.
As a second example, consider the expression rational(1, 2)**3.14
. Here, the rational number has an exact floating-point representation, but the exponent does not. The answer to 17 decimal places, compared with the above two methods is
- exact: 0.11343989441464511
- #1 (divide first): 0.11343989441464510
- #2 (powers first): 0.11343989441464508
which points toward method #1 as being the more appropriate method. It would seem that when the exponent is not exactly representable, one is better off doing one inexact exponentiation rather than two.
I checked what Julia does, since it has native rational numbers, and determined that it performs the division first (in the implementation, the rational is “promoted” to the type of the exponent). I’m curious what other languages with native rational numbers (or widely used libraries) elect to do. I’m also curious if any readers here have an intuitive sense for which behavior seems better.