const std = @import("../std.zig");
const builtin = @import("builtin");
const math = std.math;
const testing = std.testing;
const maxInt = std.math.maxInt;
const assert = std.debug.assert;
const Log2Int = std.math.Log2Int;
pub fn log10(x: anytype) @TypeOf(x) {
const T = @TypeOf(x);
switch (@typeInfo(T)) {
.ComptimeFloat => {
return @as(comptime_float, @log10(x));
},
.Float => return @log10(x),
.ComptimeInt => {
return @as(comptime_int, @floor(@log10(@as(f64, x))));
},
.Int => |IntType| switch (IntType.signedness) {
.signed => @compileError("log10 not implemented for signed integers"),
.unsigned => return log10_int(x),
},
else => @compileError("log10 not implemented for " ++ @typeName(T)),
}
}
pub fn log10_int(x: anytype) Log2Int(@TypeOf(x)) {
const T = @TypeOf(x);
const OutT = Log2Int(T);
if (@typeInfo(T) != .Int or @typeInfo(T).Int.signedness != .unsigned)
@compileError("log10_int requires an unsigned integer, found " ++ @typeName(T));
assert(x != 0);
const bit_size = @typeInfo(T).Int.bits;
if (bit_size <= 8) {
return @intCast(OutT, log10_int_u8(x));
} else if (bit_size <= 16) {
return @intCast(OutT, less_than_5(x));
}
var val = x;
var log: u32 = 0;
inline for (0..11) |i| {
if (bit_size > (1 << (11 - i)) * 5 * @log2(10.0) and val >= pow10((1 << (11 - i)) * 5)) {
const num_digits = (1 << (11 - i)) * 5;
val /= pow10(num_digits);
log += num_digits;
}
}
if (val >= pow10(5)) {
val /= pow10(5);
log += 5;
}
return @intCast(OutT, log + less_than_5(@intCast(u32, val)));
}
fn pow10(comptime y: comptime_int) comptime_int {
if (y == 0) return 1;
var squaring = 0;
var s = 1;
while (s <= y) : (s <<= 1) {
squaring += 1;
}
squaring -= 1;
var result = 10;
for (0..squaring) |_| {
result *= result;
}
const rest_exp = y - (1 << squaring);
return result * pow10(rest_exp);
}
inline fn log10_int_u8(x: u8) u32 {
const C1: u32 = 0b11_00000000 - 10;
const C2: u32 = 0b10_00000000 - 100;
return ((x + C1) & (x + C2)) >> 8;
}
inline fn less_than_5(x: u32) u32 {
const C1: u32 = 0b011_00000000000000000 - 10;
const C2: u32 = 0b100_00000000000000000 - 100;
const C3: u32 = 0b111_00000000000000000 - 1000;
const C4: u32 = 0b100_00000000000000000 - 10000;
return (((x + C1) & (x + C2)) ^ ((x + C3) & (x + C4))) >> 17;
}
fn oldlog10(x: anytype) u8 {
return @floatToInt(u8, @log10(@intToFloat(f64, x)));
}
test "oldlog10 doesn't work" {
try testing.expect(14 != oldlog10(pow10(15) - 1));
try testing.expect(14 == log10_int(@as(u64, pow10(15) - 1)));
}
test "log10_int vs old implementation" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_llvm and comptime builtin.target.isWasm()) return error.SkipZigTest;
const int_types = .{ u8, u16, u32, u64, u128 };
inline for (int_types) |T| {
const last = @min(maxInt(T), 100_000);
for (1..last) |i| {
const x = @intCast(T, i);
try testing.expectEqual(oldlog10(x), log10_int(x));
}
const max_int: T = maxInt(T);
try testing.expectEqual(oldlog10(max_int), log10_int(max_int));
}
}
test "log10_int close to powers of 10" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_llvm and comptime builtin.target.isWasm()) return error.SkipZigTest;
const int_types = .{ u8, u16, u32, u64, u128, u256, u512 };
const max_log_values: [7]usize = .{ 2, 4, 9, 19, 38, 77, 154 };
inline for (int_types, max_log_values) |T, expected_max_ilog| {
const max_val: T = maxInt(T);
try testing.expectEqual(expected_max_ilog, log10_int(max_val));
for (0..(expected_max_ilog + 1)) |idx| {
const i = @intCast(T, idx);
const p: T = try math.powi(T, 10, i);
const b = @intCast(Log2Int(T), i);
if (p >= 10) {
try testing.expectEqual(b - 1, log10_int(p - 9));
try testing.expectEqual(b - 1, log10_int(p - 1));
}
try testing.expectEqual(b, log10_int(p));
try testing.expectEqual(b, log10_int(p + 1));
if (p >= 10) {
try testing.expectEqual(b, log10_int(p + 9));
}
}
}
}