    const std = @import("../std.zig");
    const math = std.math;
    const testing = std.testing;
    const assert = std.debug.assert;
    const Log2Int = math.Log2Int;

    /// Returns the logarithm of `x` for the provided `base`, rounding down to the nearest integer.
    /// Asserts that `base > 1` and `x > 0`.
    pub fn log_int(comptime T: type, base: T, x: T) Log2Int(T) {
        const valid = switch (@typeInfo(T)) {
            .ComptimeInt => true,
            .Int => |IntType| IntType.signedness == .unsigned,
            else => false,
        };
        if (!valid) @compileError("log_int requires an unsigned integer, found " ++ @typeName(T));

        assert(base > 1 and x > 0);
        if (base == 2) return math.log2_int(T, x);

        // Let's denote by [y] the integer part of y.

        // Throughout the iteration the following invariant is preserved:

        //     power = base ^ exponent

        // Safety and termination.

        //

        // We never overflow inside the loop because when we enter the loop we have

        //     power <= [maxInt(T) / base]

        // therefore

        //     power * base <= maxInt(T)

        // is a valid multiplication for type `T` and

        //     exponent + 1 <= log(base, maxInt(T)) <= log2(maxInt(T)) <= maxInt(Log2Int(T))

        // is a valid addition for type `Log2Int(T)`.

        //

        // This implies also termination because power is strictly increasing,

        // hence it must eventually surpass [x / base] < maxInt(T) and we then exit the loop.

        var exponent: Log2Int(T) = 0;
        var power: T = 1;
        while (power <= x / base) {
            power *= base;
            exponent += 1;
        }

        // If we never entered the loop we must have

        //     [x / base] < 1

        // hence

        //     x <= [x / base] * base < base

        // thus the result is 0. We can then return exponent, which is still 0.

        //

        // Otherwise, if we entered the loop at least once,

        // when we exit the loop we have that power is exactly divisible by base and

        //     power / base <= [x / base] < power

        // hence

        //     power <= [x / base] * base <= x < power * base

        // This means that

        //     base^exponent <= x < base^(exponent+1)

        // hence the result is exponent.

        return exponent;
    }

    test "log_int" {
        // Test all unsigned integers with 2, 3, ..., 64 bits.

        // We cannot test 0 or 1 bits since base must be > 1.

        inline for (2..64 + 1) |bits| {
            const T = @Type(std.builtin.Type{
                .Int = std.builtin.Type.Int{ .signedness = .unsigned, .bits = @intCast(bits) },
            });

            // for base = 2, 3, ..., min(maxInt(T),1024)

            var base: T = 1;
            while (base < math.maxInt(T) and base <= 1024) {
                base += 1;

                // test that `log_int(T, base, 1) == 0`

                try testing.expectEqual(@as(Log2Int(T), 0), log_int(T, base, 1));

                // For powers `pow = base^exp > 1` that fit inside T,

                // test that `log_int` correctly detects the jump in the logarithm

                // from `log(pow-1) == exp-1` to `log(pow) == exp`.

                var exp: Log2Int(T) = 0;
                var pow: T = 1;
                while (pow <= math.maxInt(T) / base) {
                    exp += 1;
                    pow *= base;

                    try testing.expectEqual(exp - 1, log_int(T, base, pow - 1));
                    try testing.expectEqual(exp, log_int(T, base, pow));
                }
            }
        }
    }

    test "log_int vs math.log2" {
        const types = [_]type{ u2, u3, u4, u8, u16 };
        inline for (types) |T| {
            var n: T = 0;
            while (n < math.maxInt(T)) {
                n += 1;
                const special = math.log2_int(T, n);
                const general = log_int(T, 2, n);
                try testing.expectEqual(special, general);
            }
        }
    }

    test "log_int vs math.log10" {
        const types = [_]type{ u4, u5, u6, u8, u16 };
        inline for (types) |T| {
            var n: T = 0;
            while (n < math.maxInt(T)) {
                n += 1;
                const special = math.log10_int(n);
                const general = log_int(T, 10, n);
                try testing.expectEqual(special, general);
            }
        }
    }

    test "log_int at comptime" {
        const x = 59049; // 9 ** 5;

        comptime {
            if (math.log_int(comptime_int, 9, x) != 5) {
                @compileError("log(9, 59049) should be 5");
            }
        }
    }