master
  1const std = @import("../std.zig");
  2const math = std.math;
  3const testing = std.testing;
  4const assert = std.debug.assert;
  5const Log2Int = math.Log2Int;
  6
  7/// Returns the logarithm of `x` for the provided `base`, rounding down to the nearest integer.
  8/// Asserts that `base > 1` and `x > 0`.
  9pub fn log_int(comptime T: type, base: T, x: T) Log2Int(T) {
 10    const valid = switch (@typeInfo(T)) {
 11        .comptime_int => true,
 12        .int => |IntType| IntType.signedness == .unsigned,
 13        else => false,
 14    };
 15    if (!valid) @compileError("log_int requires an unsigned integer, found " ++ @typeName(T));
 16
 17    assert(base > 1 and x > 0);
 18    if (base == 2) return math.log2_int(T, x);
 19
 20    // Let's denote by [y] the integer part of y.
 21
 22    // Throughout the iteration the following invariant is preserved:
 23    //     power = base ^ exponent
 24
 25    // Safety and termination.
 26    //
 27    // We never overflow inside the loop because when we enter the loop we have
 28    //     power <= [maxInt(T) / base]
 29    // therefore
 30    //     power * base <= maxInt(T)
 31    // is a valid multiplication for type `T` and
 32    //     exponent + 1 <= log(base, maxInt(T)) <= log2(maxInt(T)) <= maxInt(Log2Int(T))
 33    // is a valid addition for type `Log2Int(T)`.
 34    //
 35    // This implies also termination because power is strictly increasing,
 36    // hence it must eventually surpass [x / base] < maxInt(T) and we then exit the loop.
 37
 38    var exponent: Log2Int(T) = 0;
 39    var power: T = 1;
 40    while (power <= x / base) {
 41        power *= base;
 42        exponent += 1;
 43    }
 44
 45    // If we never entered the loop we must have
 46    //     [x / base] < 1
 47    // hence
 48    //     x <= [x / base] * base < base
 49    // thus the result is 0. We can then return exponent, which is still 0.
 50    //
 51    // Otherwise, if we entered the loop at least once,
 52    // when we exit the loop we have that power is exactly divisible by base and
 53    //     power / base <= [x / base] < power
 54    // hence
 55    //     power <= [x / base] * base <= x < power * base
 56    // This means that
 57    //     base^exponent <= x < base^(exponent+1)
 58    // hence the result is exponent.
 59
 60    return exponent;
 61}
 62
 63test "log_int" {
 64    @setEvalBranchQuota(2000);
 65    // Test all unsigned integers with 2, 3, ..., 64 bits.
 66    // We cannot test 0 or 1 bits since base must be > 1.
 67    inline for (2..64 + 1) |bits| {
 68        const T = @Int(.unsigned, @intCast(bits));
 69
 70        // for base = 2, 3, ..., min(maxInt(T),1024)
 71        var base: T = 1;
 72        while (base < math.maxInt(T) and base <= 1024) {
 73            base += 1;
 74
 75            // test that `log_int(T, base, 1) == 0`
 76            try testing.expectEqual(@as(Log2Int(T), 0), log_int(T, base, 1));
 77
 78            // For powers `pow = base^exp > 1` that fit inside T,
 79            // test that `log_int` correctly detects the jump in the logarithm
 80            // from `log(pow-1) == exp-1` to `log(pow) == exp`.
 81            var exp: Log2Int(T) = 0;
 82            var pow: T = 1;
 83            while (pow <= math.maxInt(T) / base) {
 84                exp += 1;
 85                pow *= base;
 86
 87                try testing.expectEqual(exp - 1, log_int(T, base, pow - 1));
 88                try testing.expectEqual(exp, log_int(T, base, pow));
 89            }
 90        }
 91    }
 92}
 93
 94test "log_int vs math.log2" {
 95    const types = [_]type{ u2, u3, u4, u8, u16 };
 96    inline for (types) |T| {
 97        var n: T = 0;
 98        while (n < math.maxInt(T)) {
 99            n += 1;
100            const special = math.log2_int(T, n);
101            const general = log_int(T, 2, n);
102            try testing.expectEqual(special, general);
103        }
104    }
105}
106
107test "log_int vs math.log10" {
108    const types = [_]type{ u4, u5, u6, u8, u16 };
109    inline for (types) |T| {
110        var n: T = 0;
111        while (n < math.maxInt(T)) {
112            n += 1;
113            const special = math.log10_int(n);
114            const general = log_int(T, 10, n);
115            try testing.expectEqual(special, general);
116        }
117    }
118}
119
120test "log_int at comptime" {
121    const x = 59049; // 9 ** 5;
122    comptime {
123        if (math.log_int(comptime_int, 9, x) != 5) {
124            @compileError("log(9, 59049) should be 5");
125        }
126    }
127}