const std = @import("../std.zig");
const math = std.math;
const expect = std.testing.expect;
pub fn pow(comptime T: type, x: T, y: T) T {
    if (@typeInfo(T) == .Int) {
        return math.powi(T, x, y) catch unreachable;
    }
    if (T != f32 and T != f64) {
        @compileError("pow not implemented for " ++ @typeName(T));
    }
    
    
    if (y == 0 or x == 1) {
        return 1;
    }
    
    
    if (math.isNan(x) or math.isNan(y)) {
        return math.nan(T);
    }
    
    if (y == 1) {
        return x;
    }
    if (x == 0) {
        if (y < 0) {
            
            if (isOddInteger(y)) {
                return math.copysign(math.inf(T), x);
            }
            
            else {
                return math.inf(T);
            }
        } else {
            if (isOddInteger(y)) {
                return x;
            } else {
                return 0;
            }
        }
    }
    if (math.isInf(y)) {
        
        if (x == -1) {
            return 1.0;
        }
        
        
        else if ((@fabs(x) < 1) == math.isPositiveInf(y)) {
            return 0;
        }
        
        
        else {
            return math.inf(T);
        }
    }
    if (math.isInf(x)) {
        if (math.isNegativeInf(x)) {
            return pow(T, 1 / x, -y);
        }
        
        else if (y < 0) {
            return 0;
        }
        
        else if (y > 0) {
            return math.inf(T);
        }
    }
    
    if (y == 0.5) {
        return @sqrt(x);
    }
    if (y == -0.5) {
        return 1 / @sqrt(x);
    }
    const r1 = math.modf(@fabs(y));
    var yi = r1.ipart;
    var yf = r1.fpart;
    if (yf != 0 and x < 0) {
        return math.nan(T);
    }
    if (yi >= 1 << (@typeInfo(T).Float.bits - 1)) {
        return @exp(y * @log(x));
    }
    
    var a1: T = 1.0;
    var ae: i32 = 0;
    
    if (yf != 0) {
        if (yf > 0.5) {
            yf -= 1;
            yi += 1;
        }
        a1 = @exp(yf * @log(x));
    }
    
    const r2 = math.frexp(x);
    var xe = r2.exponent;
    var x1 = r2.significand;
    var i = @floatToInt(std.meta.Int(.signed, @typeInfo(T).Float.bits), yi);
    while (i != 0) : (i >>= 1) {
        const overflow_shift = math.floatExponentBits(T) + 1;
        if (xe < -(1 << overflow_shift) or (1 << overflow_shift) < xe) {
            
            
            
            
            
            ae += xe;
            break;
        }
        if (i & 1 == 1) {
            a1 *= x1;
            ae += xe;
        }
        x1 *= x1;
        xe <<= 1;
        if (x1 < 0.5) {
            x1 += x1;
            xe -= 1;
        }
    }
    
    if (y < 0) {
        a1 = 1 / a1;
        ae = -ae;
    }
    return math.scalbn(a1, ae);
}
fn isOddInteger(x: f64) bool {
    const r = math.modf(x);
    return r.fpart == 0.0 and @floatToInt(i64, r.ipart) & 1 == 1;
}
test "math.pow" {
    const epsilon = 0.000001;
    try expect(math.approxEqAbs(f32, pow(f32, 0.0, 3.3), 0.0, epsilon));
    try expect(math.approxEqAbs(f32, pow(f32, 0.8923, 3.3), 0.686572, epsilon));
    try expect(math.approxEqAbs(f32, pow(f32, 0.2, 3.3), 0.004936, epsilon));
    try expect(math.approxEqAbs(f32, pow(f32, 1.5, 3.3), 3.811546, epsilon));
    try expect(math.approxEqAbs(f32, pow(f32, 37.45, 3.3), 155736.703125, epsilon));
    try expect(math.approxEqAbs(f32, pow(f32, 89.123, 3.3), 2722489.5, epsilon));
    try expect(math.approxEqAbs(f64, pow(f64, 0.0, 3.3), 0.0, epsilon));
    try expect(math.approxEqAbs(f64, pow(f64, 0.8923, 3.3), 0.686572, epsilon));
    try expect(math.approxEqAbs(f64, pow(f64, 0.2, 3.3), 0.004936, epsilon));
    try expect(math.approxEqAbs(f64, pow(f64, 1.5, 3.3), 3.811546, epsilon));
    try expect(math.approxEqAbs(f64, pow(f64, 37.45, 3.3), 155736.7160616, epsilon));
    try expect(math.approxEqAbs(f64, pow(f64, 89.123, 3.3), 2722490.231436, epsilon));
}
test "math.pow.special" {
    const epsilon = 0.000001;
    try expect(pow(f32, 4, 0.0) == 1.0);
    try expect(pow(f32, 7, -0.0) == 1.0);
    try expect(pow(f32, 45, 1.0) == 45);
    try expect(pow(f32, -45, 1.0) == -45);
    try expect(math.isNan(pow(f32, math.nan(f32), 5.0)));
    try expect(math.isPositiveInf(pow(f32, -math.inf(f32), 0.5)));
    try expect(math.isPositiveInf(pow(f32, -0, -0.5)));
    try expect(pow(f32, -0, 0.5) == 0);
    try expect(math.isNan(pow(f32, 5.0, math.nan(f32))));
    try expect(math.isPositiveInf(pow(f32, 0.0, -1.0)));
    
    try expect(math.isPositiveInf(pow(f32, 0.0, -math.inf(f32))));
    try expect(math.isPositiveInf(pow(f32, -0.0, -math.inf(f32))));
    try expect(pow(f32, 0.0, math.inf(f32)) == 0.0);
    try expect(pow(f32, -0.0, math.inf(f32)) == 0.0);
    try expect(math.isPositiveInf(pow(f32, 0.0, -2.0)));
    try expect(math.isPositiveInf(pow(f32, -0.0, -2.0)));
    try expect(pow(f32, 0.0, 1.0) == 0.0);
    try expect(pow(f32, -0.0, 1.0) == -0.0);
    try expect(pow(f32, 0.0, 2.0) == 0.0);
    try expect(pow(f32, -0.0, 2.0) == 0.0);
    try expect(math.approxEqAbs(f32, pow(f32, -1.0, math.inf(f32)), 1.0, epsilon));
    try expect(math.approxEqAbs(f32, pow(f32, -1.0, -math.inf(f32)), 1.0, epsilon));
    try expect(math.isPositiveInf(pow(f32, 1.2, math.inf(f32))));
    try expect(math.isPositiveInf(pow(f32, -1.2, math.inf(f32))));
    try expect(pow(f32, 1.2, -math.inf(f32)) == 0.0);
    try expect(pow(f32, -1.2, -math.inf(f32)) == 0.0);
    try expect(pow(f32, 0.2, math.inf(f32)) == 0.0);
    try expect(pow(f32, -0.2, math.inf(f32)) == 0.0);
    try expect(math.isPositiveInf(pow(f32, 0.2, -math.inf(f32))));
    try expect(math.isPositiveInf(pow(f32, -0.2, -math.inf(f32))));
    try expect(math.isPositiveInf(pow(f32, math.inf(f32), 1.0)));
    try expect(pow(f32, math.inf(f32), -1.0) == 0.0);
    
    try expect(pow(f32, -math.inf(f32), -5.2) == pow(f32, -0.0, 5.2));
    try expect(math.isNan(pow(f32, -1.0, 1.2)));
    try expect(math.isNan(pow(f32, -12.4, 78.5)));
}
test "math.pow.overflow" {
    try expect(math.isPositiveInf(pow(f64, 2, 1 << 32)));
    try expect(pow(f64, 2, -(1 << 32)) == 0);
    try expect(math.isNegativeInf(pow(f64, -2, (1 << 32) + 1)));
    try expect(pow(f64, 0.5, 1 << 45) == 0);
    try expect(math.isPositiveInf(pow(f64, 0.5, -(1 << 45))));
}