master
  1// Based on Rust, which is licensed under the MIT license.
  2// https://github.com/rust-lang/rust/blob/360432f1e8794de58cd94f34c9c17ad65871e5b5/LICENSE-MIT
  3//
  4// https://github.com/rust-lang/rust/blob/360432f1e8794de58cd94f34c9c17ad65871e5b5/src/libcore/num/mod.rs#L3423
  5
  6const std = @import("../std.zig");
  7const math = std.math;
  8const assert = std.debug.assert;
  9const testing = std.testing;
 10
 11/// Returns the power of x raised by the integer y (x^y).
 12///
 13/// Errors:
 14///  - Overflow: Integer overflow or Infinity
 15///  - Underflow: Absolute value of result smaller than 1
 16///
 17/// Edge case rules ordered by precedence:
 18///  - powi(T, x, 0)   = 1 unless T is i1, i0, u0
 19///  - powi(T, 0, x)   = 0 when x > 0
 20///  - powi(T, 0, x)   = Overflow
 21///  - powi(T, 1, y)   = 1
 22///  - powi(T, -1, y)  = -1 for y an odd integer
 23///  - powi(T, -1, y)  = 1 unless T is i1, i0, u0
 24///  - powi(T, -1, y)  = Overflow
 25///  - powi(T, x, y)   = Overflow when y >= @bitSizeOf(x)
 26///  - powi(T, x, y)   = Underflow when y < 0
 27pub fn powi(comptime T: type, x: T, y: T) (error{
 28    Overflow,
 29    Underflow,
 30}!T) {
 31    const bit_size = @typeInfo(T).int.bits;
 32
 33    // `y & 1 == 0` won't compile when `does_one_overflow`.
 34    const does_one_overflow = math.maxInt(T) < 1;
 35    const is_y_even = !does_one_overflow and y & 1 == 0;
 36
 37    if (x == 1 or y == 0 or (x == -1 and is_y_even)) {
 38        if (does_one_overflow) {
 39            return error.Overflow;
 40        } else {
 41            return 1;
 42        }
 43    }
 44
 45    if (x == -1) {
 46        return -1;
 47    }
 48
 49    if (x == 0) {
 50        if (y > 0) {
 51            return 0;
 52        } else {
 53            // Infinity/NaN, not overflow in strict sense
 54            return error.Overflow;
 55        }
 56    }
 57    // x >= 2 or x <= -2 from this point
 58    if (y >= bit_size) {
 59        return error.Overflow;
 60    }
 61    if (y < 0) {
 62        return error.Underflow;
 63    }
 64
 65    // invariant :
 66    // return value = powi(T, base, exp) * acc;
 67
 68    var base = x;
 69    var exp = y;
 70    var acc: T = if (does_one_overflow) unreachable else 1;
 71
 72    while (exp > 1) {
 73        if (exp & 1 == 1) {
 74            const ov = @mulWithOverflow(acc, base);
 75            if (ov[1] != 0) return error.Overflow;
 76            acc = ov[0];
 77        }
 78
 79        exp >>= 1;
 80
 81        const ov = @mulWithOverflow(base, base);
 82        if (ov[1] != 0) return error.Overflow;
 83        base = ov[0];
 84    }
 85
 86    if (exp == 1) {
 87        const ov = @mulWithOverflow(acc, base);
 88        if (ov[1] != 0) return error.Overflow;
 89        acc = ov[0];
 90    }
 91
 92    return acc;
 93}
 94
 95test powi {
 96    try testing.expectError(error.Overflow, powi(i8, -66, 6));
 97    try testing.expectError(error.Overflow, powi(i16, -13, 13));
 98    try testing.expectError(error.Overflow, powi(i32, -32, 21));
 99    try testing.expectError(error.Overflow, powi(i64, -24, 61));
100    try testing.expectError(error.Overflow, powi(i17, -15, 15));
101    try testing.expectError(error.Overflow, powi(i42, -6, 40));
102
103    try testing.expect((try powi(i8, -5, 3)) == -125);
104    try testing.expect((try powi(i16, -16, 3)) == -4096);
105    try testing.expect((try powi(i32, -91, 3)) == -753571);
106    try testing.expect((try powi(i64, -36, 6)) == 2176782336);
107    try testing.expect((try powi(i17, -2, 15)) == -32768);
108    try testing.expect((try powi(i42, -5, 7)) == -78125);
109
110    try testing.expect((try powi(u8, 6, 2)) == 36);
111    try testing.expect((try powi(u16, 5, 4)) == 625);
112    try testing.expect((try powi(u32, 12, 6)) == 2985984);
113    try testing.expect((try powi(u64, 34, 2)) == 1156);
114    try testing.expect((try powi(u17, 16, 3)) == 4096);
115    try testing.expect((try powi(u42, 34, 6)) == 1544804416);
116
117    try testing.expectError(error.Overflow, powi(i8, 120, 7));
118    try testing.expectError(error.Overflow, powi(i16, 73, 15));
119    try testing.expectError(error.Overflow, powi(i32, 23, 31));
120    try testing.expectError(error.Overflow, powi(i64, 68, 61));
121    try testing.expectError(error.Overflow, powi(i17, 15, 15));
122    try testing.expectError(error.Overflow, powi(i42, 121312, 41));
123
124    try testing.expectError(error.Overflow, powi(u8, 123, 7));
125    try testing.expectError(error.Overflow, powi(u16, 2313, 15));
126    try testing.expectError(error.Overflow, powi(u32, 8968, 31));
127    try testing.expectError(error.Overflow, powi(u64, 2342, 63));
128    try testing.expectError(error.Overflow, powi(u17, 2723, 16));
129    try testing.expectError(error.Overflow, powi(u42, 8234, 41));
130
131    const minInt = std.math.minInt;
132    try testing.expect((try powi(i8, -2, 7)) == minInt(i8));
133    try testing.expect((try powi(i16, -2, 15)) == minInt(i16));
134    try testing.expect((try powi(i32, -2, 31)) == minInt(i32));
135    try testing.expect((try powi(i64, -2, 63)) == minInt(i64));
136
137    try testing.expectError(error.Underflow, powi(i8, 6, -2));
138    try testing.expectError(error.Underflow, powi(i16, 5, -4));
139    try testing.expectError(error.Underflow, powi(i32, 12, -6));
140    try testing.expectError(error.Underflow, powi(i64, 34, -2));
141    try testing.expectError(error.Underflow, powi(i17, 16, -3));
142    try testing.expectError(error.Underflow, powi(i42, 34, -6));
143}
144
145test "powi.special" {
146    try testing.expectError(error.Overflow, powi(i8, -2, 8));
147    try testing.expectError(error.Overflow, powi(i16, -2, 16));
148    try testing.expectError(error.Overflow, powi(i32, -2, 32));
149    try testing.expectError(error.Overflow, powi(i64, -2, 64));
150    try testing.expectError(error.Overflow, powi(i17, -2, 17));
151    try testing.expectError(error.Overflow, powi(i17, -2, 16));
152    try testing.expectError(error.Overflow, powi(i42, -2, 42));
153
154    try testing.expect((try powi(i8, -1, 3)) == -1);
155    try testing.expect((try powi(i16, -1, 2)) == 1);
156    try testing.expect((try powi(i32, -1, 16)) == 1);
157    try testing.expect((try powi(i64, -1, 6)) == 1);
158    try testing.expect((try powi(i17, -1, 15)) == -1);
159    try testing.expect((try powi(i42, -1, 7)) == -1);
160
161    try testing.expect((try powi(u8, 1, 2)) == 1);
162    try testing.expect((try powi(u16, 1, 4)) == 1);
163    try testing.expect((try powi(u32, 1, 6)) == 1);
164    try testing.expect((try powi(u64, 1, 2)) == 1);
165    try testing.expect((try powi(u17, 1, 3)) == 1);
166    try testing.expect((try powi(u42, 1, 6)) == 1);
167
168    try testing.expectError(error.Overflow, powi(i8, 2, 7));
169    try testing.expectError(error.Overflow, powi(i16, 2, 15));
170    try testing.expectError(error.Overflow, powi(i32, 2, 31));
171    try testing.expectError(error.Overflow, powi(i64, 2, 63));
172    try testing.expectError(error.Overflow, powi(i17, 2, 16));
173    try testing.expectError(error.Overflow, powi(i42, 2, 41));
174
175    try testing.expectError(error.Overflow, powi(u8, 2, 8));
176    try testing.expectError(error.Overflow, powi(u16, 2, 16));
177    try testing.expectError(error.Overflow, powi(u32, 2, 32));
178    try testing.expectError(error.Overflow, powi(u64, 2, 64));
179    try testing.expectError(error.Overflow, powi(u17, 2, 17));
180    try testing.expectError(error.Overflow, powi(u42, 2, 42));
181
182    try testing.expect((try powi(u8, 6, 0)) == 1);
183    try testing.expect((try powi(u16, 5, 0)) == 1);
184    try testing.expect((try powi(u32, 12, 0)) == 1);
185    try testing.expect((try powi(u64, 34, 0)) == 1);
186    try testing.expect((try powi(u17, 16, 0)) == 1);
187    try testing.expect((try powi(u42, 34, 0)) == 1);
188}
189
190test "powi.narrow" {
191    try testing.expectError(error.Overflow, powi(u0, 0, 0));
192    try testing.expectError(error.Overflow, powi(i0, 0, 0));
193    try testing.expectError(error.Overflow, powi(i1, 0, 0));
194    try testing.expectError(error.Overflow, powi(i1, -1, 0));
195    try testing.expectError(error.Overflow, powi(i1, 0, -1));
196    try testing.expect((try powi(i1, -1, -1)) == -1);
197}