//! Representation of a float as the signficant digits and exponent.
//! The fast path algorithm using machine-sized integers and floats.
//!
//! This only works if both the mantissa and the exponent can be exactly
//! represented as a machine float, since IEE-754 guarantees no rounding
//! will occur.
//!
//! There is an exception: disguised fast-path cases, where we can shift
//! powers-of-10 from the exponent to the significant digits.

const std = @import("std");
const math = std.math;
const common = @import("common.zig");
const FloatInfo = @import("FloatInfo.zig");
const Number = common.Number;
const floatFromU64 = common.floatFromU64;

fn isFastPath(comptime T: type, n: Number(T)) bool {
    const info = FloatInfo.from(T);

    return info.min_exponent_fast_path <= n.exponent and
        n.exponent <= info.max_exponent_fast_path_disguised and
        n.mantissa <= info.max_mantissa_fast_path and
        !n.many_digits;
}

// upper bound for tables is floor(mantissaDigits(T) / log2(5))

// for f64 this is floor(53 / log2(5)) = 22.

//

// Must have max_disguised_fast_path - max_exponent_fast_path entries. (82 - 48 = 34 for f128)

fn fastPow10(comptime T: type, i: usize) T {
    return switch (T) {
        f16 => ([8]f16{
            1e0, 1e1, 1e2, 1e3, 1e4, 0, 0, 0,
        })[i & 7],

        f32 => ([16]f32{
            1e0, 1e1, 1e2,  1e3, 1e4, 1e5, 1e6, 1e7,
            1e8, 1e9, 1e10, 0,   0,   0,   0,   0,
        })[i & 15],

        f64 => ([32]f64{
            1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,
            1e8,  1e9,  1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
            1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 0,
            0,    0,    0,    0,    0,    0,    0,    0,
        })[i & 31],

        f128 => ([64]f128{
            1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,
            1e8,  1e9,  1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
            1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 1e23,
            1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30, 1e31,
            1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, 1e39,
            1e40, 1e41, 1e42, 1e43, 1e44, 1e45, 1e46, 1e47,
            1e48, 0,    0,    0,    0,    0,    0,    0,
            0,    0,    0,    0,    0,    0,    0,    0,
        })[i & 63],

        else => unreachable,
    };
}

fn fastIntPow10(comptime T: type, i: usize) T {
    return switch (T) {
        u64 => ([16]u64{
            1,             10,             100,             1000,
            10000,         100000,         1000000,         10000000,
            100000000,     1000000000,     10000000000,     100000000000,
            1000000000000, 10000000000000, 100000000000000, 1000000000000000,
        })[i],

        u128 => ([35]u128{
            1,                                   10,
            100,                                 1000,
            10000,                               100000,
            1000000,                             10000000,
            100000000,                           1000000000,
            10000000000,                         100000000000,
            1000000000000,                       10000000000000,
            100000000000000,                     1000000000000000,
            10000000000000000,                   100000000000000000,
            1000000000000000000,                 10000000000000000000,
            100000000000000000000,               1000000000000000000000,
            10000000000000000000000,             100000000000000000000000,
            1000000000000000000000000,           10000000000000000000000000,
            100000000000000000000000000,         1000000000000000000000000000,
            10000000000000000000000000000,       100000000000000000000000000000,
            1000000000000000000000000000000,     10000000000000000000000000000000,
            100000000000000000000000000000000,   1000000000000000000000000000000000,
            10000000000000000000000000000000000,
        })[i],

        else => unreachable,
    };
}

pub fn convertFast(comptime T: type, n: Number(T)) ?T {
    const MantissaT = common.mantissaType(T);

    if (!isFastPath(T, n)) {
        return null;
    }

    // TODO: x86 (no SSE/SSE2) requires x87 FPU to be setup correctly with fldcw

    const info = FloatInfo.from(T);

    var value: T = 0;
    if (n.exponent <= info.max_exponent_fast_path) {
        // normal fast path

        value = @intToFloat(T, n.mantissa);
        value = if (n.exponent < 0)
            value / fastPow10(T, @intCast(usize, -n.exponent))
        else
            value * fastPow10(T, @intCast(usize, n.exponent));
    } else {
        // disguised fast path

        const shift = n.exponent - info.max_exponent_fast_path;
        const mantissa = math.mul(MantissaT, n.mantissa, fastIntPow10(MantissaT, @intCast(usize, shift))) catch return null;
        if (mantissa > info.max_mantissa_fast_path) {
            return null;
        }
        value = @intToFloat(T, mantissa) * fastPow10(T, info.max_exponent_fast_path);
    }

    if (n.negative) {
        value = -value;
    }
    return value;
}