master
  1// Ported from go, which is licensed under a BSD-3 license.
  2// https://golang.org/LICENSE
  3//
  4// https://golang.org/src/math/pow.go
  5
  6const std = @import("../std.zig");
  7const math = std.math;
  8const expect = std.testing.expect;
  9
 10/// Returns x raised to the power of y (x^y).
 11///
 12/// Special Cases:
 13///  - pow(x, +-0)    = 1 for any x
 14///  - pow(1, y)      = 1 for any y
 15///  - pow(x, 1)      = x for any x
 16///  - pow(nan, y)    = nan
 17///  - pow(x, nan)    = nan
 18///  - pow(+-0, y)    = +-inf for y an odd integer < 0
 19///  - pow(+-0, -inf) = +inf
 20///  - pow(+-0, +inf) = +0
 21///  - pow(+-0, y)    = +inf for finite y < 0 and not an odd integer
 22///  - pow(+-0, y)    = +-0 for y an odd integer > 0
 23///  - pow(+-0, y)    = +0 for finite y > 0 and not an odd integer
 24///  - pow(-1, +-inf) = 1
 25///  - pow(x, +inf)   = +inf for |x| > 1
 26///  - pow(x, -inf)   = +0 for |x| > 1
 27///  - pow(x, +inf)   = +0 for |x| < 1
 28///  - pow(x, -inf)   = +inf for |x| < 1
 29///  - pow(+inf, y)   = +inf for y > 0
 30///  - pow(+inf, y)   = +0 for y < 0
 31///  - pow(-inf, y)   = pow(-0, -y)
 32///  - pow(x, y)      = nan for finite x < 0 and finite non-integer y
 33pub fn pow(comptime T: type, x: T, y: T) T {
 34    if (@typeInfo(T) == .int) {
 35        return math.powi(T, x, y) catch unreachable;
 36    }
 37
 38    if (T != f32 and T != f64) {
 39        @compileError("pow not implemented for " ++ @typeName(T));
 40    }
 41
 42    // pow(x, +-0) = 1      for all x
 43    // pow(1, y) = 1        for all y
 44    if (y == 0 or x == 1) {
 45        return 1;
 46    }
 47
 48    // pow(nan, y) = nan    for all y
 49    // pow(x, nan) = nan    for all x
 50    if (math.isNan(x) or math.isNan(y)) {
 51        @branchHint(.unlikely);
 52        return math.nan(T);
 53    }
 54
 55    // pow(x, 1) = x        for all x
 56    if (y == 1) {
 57        return x;
 58    }
 59
 60    if (x == 0) {
 61        if (y < 0) {
 62            // pow(+-0, y) = +-inf  for y an odd integer
 63            if (isOddInteger(y)) {
 64                return math.copysign(math.inf(T), x);
 65            }
 66            // pow(+-0, y) = +inf   for y an even integer
 67            else {
 68                return math.inf(T);
 69            }
 70        } else {
 71            if (isOddInteger(y)) {
 72                return x;
 73            } else {
 74                return 0;
 75            }
 76        }
 77    }
 78
 79    if (math.isInf(y)) {
 80        // pow(-1, inf) = 1     for all x
 81        if (x == -1) {
 82            return 1.0;
 83        }
 84        // pow(x, +inf) = +0    for |x| < 1
 85        // pow(x, -inf) = +0    for |x| > 1
 86        else if ((@abs(x) < 1) == math.isPositiveInf(y)) {
 87            return 0;
 88        }
 89        // pow(x, -inf) = +inf  for |x| < 1
 90        // pow(x, +inf) = +inf  for |x| > 1
 91        else {
 92            return math.inf(T);
 93        }
 94    }
 95
 96    if (math.isInf(x)) {
 97        if (math.isNegativeInf(x)) {
 98            return pow(T, 1 / x, -y);
 99        }
100        // pow(+inf, y) = +0    for y < 0
101        else if (y < 0) {
102            return 0;
103        }
104        // pow(+inf, y) = +0    for y > 0
105        else if (y > 0) {
106            return math.inf(T);
107        }
108    }
109
110    // special case sqrt
111    if (y == 0.5) {
112        return @sqrt(x);
113    }
114
115    if (y == -0.5) {
116        return 1 / @sqrt(x);
117    }
118
119    const r1 = math.modf(@abs(y));
120    var yi = r1.ipart;
121    var yf = r1.fpart;
122
123    if (yf != 0 and x < 0) {
124        return math.nan(T);
125    }
126    if (yi >= 1 << (@typeInfo(T).float.bits - 1)) {
127        return @exp(y * @log(x));
128    }
129
130    // a = a1 * 2^ae
131    var a1: T = 1.0;
132    var ae: i32 = 0;
133
134    // a *= x^yf
135    if (yf != 0) {
136        if (yf > 0.5) {
137            yf -= 1;
138            yi += 1;
139        }
140        a1 = @exp(yf * @log(x));
141    }
142
143    // a *= x^yi
144    const r2 = math.frexp(x);
145    var xe = r2.exponent;
146    var x1 = r2.significand;
147
148    var i = @as(std.meta.Int(.signed, @typeInfo(T).float.bits), @intFromFloat(yi));
149    while (i != 0) : (i >>= 1) {
150        const overflow_shift = math.floatExponentBits(T) + 1;
151        if (xe < -(1 << overflow_shift) or (1 << overflow_shift) < xe) {
152            // catch xe before it overflows the left shift below
153            // Since i != 0 it has at least one bit still set, so ae will accumulate xe
154            // on at least one more iteration, ae += xe is a lower bound on ae
155            // the lower bound on ae exceeds the size of a float exp
156            // so the final call to Ldexp will produce under/overflow (0/Inf)
157            ae += xe;
158            break;
159        }
160        if (i & 1 == 1) {
161            a1 *= x1;
162            ae += xe;
163        }
164        x1 *= x1;
165        xe <<= 1;
166        if (x1 < 0.5) {
167            x1 += x1;
168            xe -= 1;
169        }
170    }
171
172    // a *= a1 * 2^ae
173    if (y < 0) {
174        a1 = 1 / a1;
175        ae = -ae;
176    }
177
178    return math.scalbn(a1, ae);
179}
180
181fn isOddInteger(x: f64) bool {
182    if (@abs(x) >= 1 << 53) {
183        // From https://golang.org/src/math/pow.go
184        // 1 << 53 is the largest exact integer in the float64 format.
185        // Any number outside this range will be truncated before the decimal point and therefore will always be
186        // an even integer.
187        // Without this check and if x overflows i64 the @intFromFloat(r.ipart) conversion below will panic
188        return false;
189    }
190    const r = math.modf(x);
191    return r.fpart == 0.0 and @as(i64, @intFromFloat(r.ipart)) & 1 == 1;
192}
193
194test isOddInteger {
195    try expect(isOddInteger(@floatFromInt(math.maxInt(i64) * 2)) == false);
196    try expect(isOddInteger(@floatFromInt(math.maxInt(i64) * 2 + 1)) == false);
197    try expect(isOddInteger(1 << 53) == false);
198    try expect(isOddInteger(12.0) == false);
199    try expect(isOddInteger(15.0) == true);
200}
201
202test pow {
203    const epsilon = 0.000001;
204
205    try expect(math.approxEqAbs(f32, pow(f32, 0.0, 3.3), 0.0, epsilon));
206    try expect(math.approxEqAbs(f32, pow(f32, 0.8923, 3.3), 0.686572, epsilon));
207    try expect(math.approxEqAbs(f32, pow(f32, 0.2, 3.3), 0.004936, epsilon));
208    try expect(math.approxEqAbs(f32, pow(f32, 1.5, 3.3), 3.811546, epsilon));
209    try expect(math.approxEqAbs(f32, pow(f32, 37.45, 3.3), 155736.703125, epsilon));
210    try expect(math.approxEqAbs(f32, pow(f32, 89.123, 3.3), 2722489.5, epsilon));
211
212    try expect(math.approxEqAbs(f64, pow(f64, 0.0, 3.3), 0.0, epsilon));
213    try expect(math.approxEqAbs(f64, pow(f64, 0.8923, 3.3), 0.686572, epsilon));
214    try expect(math.approxEqAbs(f64, pow(f64, 0.2, 3.3), 0.004936, epsilon));
215    try expect(math.approxEqAbs(f64, pow(f64, 1.5, 3.3), 3.811546, epsilon));
216    try expect(math.approxEqAbs(f64, pow(f64, 37.45, 3.3), 155736.7160616, epsilon));
217    try expect(math.approxEqAbs(f64, pow(f64, 89.123, 3.3), 2722490.231436, epsilon));
218}
219
220test "special" {
221    const epsilon = 0.000001;
222
223    try expect(pow(f32, 4, 0.0) == 1.0);
224    try expect(pow(f32, 7, -0.0) == 1.0);
225    try expect(pow(f32, 45, 1.0) == 45);
226    try expect(pow(f32, -45, 1.0) == -45);
227    try expect(math.isNan(pow(f32, math.nan(f32), 5.0)));
228    try expect(math.isPositiveInf(pow(f32, -math.inf(f32), 0.5)));
229    try expect(math.isPositiveInf(pow(f32, -0.0, -0.5)));
230    try expect(pow(f32, -0.0, 0.5) == 0);
231    try expect(math.isNan(pow(f32, 5.0, math.nan(f32))));
232    try expect(math.isPositiveInf(pow(f32, 0.0, -1.0)));
233    //expect(math.isNegativeInf(pow(f32, -0.0, -3.0))); TODO is this required?
234    try expect(math.isPositiveInf(pow(f32, 0.0, -math.inf(f32))));
235    try expect(math.isPositiveInf(pow(f32, -0.0, -math.inf(f32))));
236    try expect(pow(f32, 0.0, math.inf(f32)) == 0.0);
237    try expect(pow(f32, -0.0, math.inf(f32)) == 0.0);
238    try expect(math.isPositiveInf(pow(f32, 0.0, -2.0)));
239    try expect(math.isPositiveInf(pow(f32, -0.0, -2.0)));
240    try expect(pow(f32, 0.0, 1.0) == 0.0);
241    try expect(pow(f32, -0.0, 1.0) == -0.0);
242    try expect(pow(f32, 0.0, 2.0) == 0.0);
243    try expect(pow(f32, -0.0, 2.0) == 0.0);
244    try expect(math.approxEqAbs(f32, pow(f32, -1.0, math.inf(f32)), 1.0, epsilon));
245    try expect(math.approxEqAbs(f32, pow(f32, -1.0, -math.inf(f32)), 1.0, epsilon));
246    try expect(math.isPositiveInf(pow(f32, 1.2, math.inf(f32))));
247    try expect(math.isPositiveInf(pow(f32, -1.2, math.inf(f32))));
248    try expect(pow(f32, 1.2, -math.inf(f32)) == 0.0);
249    try expect(pow(f32, -1.2, -math.inf(f32)) == 0.0);
250    try expect(pow(f32, 0.2, math.inf(f32)) == 0.0);
251    try expect(pow(f32, -0.2, math.inf(f32)) == 0.0);
252    try expect(math.isPositiveInf(pow(f32, 0.2, -math.inf(f32))));
253    try expect(math.isPositiveInf(pow(f32, -0.2, -math.inf(f32))));
254    try expect(math.isPositiveInf(pow(f32, math.inf(f32), 1.0)));
255    try expect(pow(f32, math.inf(f32), -1.0) == 0.0);
256    //expect(pow(f32, -math.inf(f32), 5.0) == pow(f32, -0.0, -5.0)); TODO support negative 0?
257    try expect(pow(f32, -math.inf(f32), -5.2) == pow(f32, -0.0, 5.2));
258    try expect(math.isNan(pow(f32, -1.0, 1.2)));
259    try expect(math.isNan(pow(f32, -12.4, 78.5)));
260}
261
262test "overflow" {
263    try expect(math.isPositiveInf(pow(f64, 2, 1 << 32)));
264    try expect(pow(f64, 2, -(1 << 32)) == 0);
265    try expect(math.isNegativeInf(pow(f64, -2, (1 << 32) + 1)));
266    try expect(pow(f64, 0.5, 1 << 45) == 0);
267    try expect(math.isPositiveInf(pow(f64, 0.5, -(1 << 45))));
268}