zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

blob 5b3ee8a8 (5624B) - Raw


      1 const std = @import("../std.zig");
      2 const builtin = @import("builtin");
      3 const testing = std.testing;
      4 
      5 /// Returns the base-10 logarithm of x.
      6 ///
      7 /// Special Cases:
      8 ///  - log10(+inf)  = +inf
      9 ///  - log10(0)     = -inf
     10 ///  - log10(x)     = nan if x < 0
     11 ///  - log10(nan)   = nan
     12 pub fn log10(x: anytype) @TypeOf(x) {
     13     const T = @TypeOf(x);
     14     switch (@typeInfo(T)) {
     15         .ComptimeFloat => {
     16             return @as(comptime_float, @log10(x));
     17         },
     18         .Float => return @log10(x),
     19         .ComptimeInt => {
     20             return @as(comptime_int, @floor(@log10(@as(f64, x))));
     21         },
     22         .Int => |IntType| switch (IntType.signedness) {
     23             .signed => @compileError("log10 not implemented for signed integers"),
     24             .unsigned => return log10_int(x),
     25         },
     26         else => @compileError("log10 not implemented for " ++ @typeName(T)),
     27     }
     28 }
     29 
     30 // Based on Rust, which is licensed under the MIT license.
     31 // https://github.com/rust-lang/rust/blob/f63ccaf25f74151a5d8ce057904cd944074b01d2/LICENSE-MIT
     32 //
     33 // https://github.com/rust-lang/rust/blob/f63ccaf25f74151a5d8ce057904cd944074b01d2/library/core/src/num/int_log10.rs
     34 
     35 /// Return the log base 10 of integer value x, rounding down to the
     36 /// nearest integer.
     37 pub fn log10_int(x: anytype) std.math.Log2Int(@TypeOf(x)) {
     38     const T = @TypeOf(x);
     39     const OutT = std.math.Log2Int(T);
     40     if (@typeInfo(T) != .Int or @typeInfo(T).Int.signedness != .unsigned)
     41         @compileError("log10_int requires an unsigned integer, found " ++ @typeName(T));
     42 
     43     std.debug.assert(x != 0);
     44 
     45     const bit_size = @typeInfo(T).Int.bits;
     46 
     47     if (bit_size <= 8) {
     48         return @as(OutT, @intCast(log10_int_u8(x)));
     49     } else if (bit_size <= 16) {
     50         return @as(OutT, @intCast(less_than_5(x)));
     51     }
     52 
     53     var val = x;
     54     var log: u32 = 0;
     55 
     56     inline for (0..11) |i| {
     57         // Unnecessary branches should be removed by the compiler
     58         if (bit_size > (1 << (11 - i)) * 5 * @log2(10.0) and val >= pow10((1 << (11 - i)) * 5)) {
     59             const num_digits = (1 << (11 - i)) * 5;
     60             val /= pow10(num_digits);
     61             log += num_digits;
     62         }
     63     }
     64 
     65     if (val >= pow10(5)) {
     66         val /= pow10(5);
     67         log += 5;
     68     }
     69 
     70     return @as(OutT, @intCast(log + less_than_5(@as(u32, @intCast(val)))));
     71 }
     72 
     73 fn pow10(comptime y: comptime_int) comptime_int {
     74     if (y == 0) return 1;
     75 
     76     var squaring = 0;
     77     var s = 1;
     78 
     79     while (s <= y) : (s <<= 1) {
     80         squaring += 1;
     81     }
     82 
     83     squaring -= 1;
     84 
     85     var result = 10;
     86 
     87     for (0..squaring) |_| {
     88         result *= result;
     89     }
     90 
     91     const rest_exp = y - (1 << squaring);
     92 
     93     return result * pow10(rest_exp);
     94 }
     95 
     96 inline fn log10_int_u8(x: u8) u32 {
     97     // For better performance, avoid branches by assembling the solution
     98     // in the bits above the low 8 bits.
     99 
    100     // Adding c1 to val gives 10 in the top bits for val < 10, 11 for val >= 10
    101     const C1: u32 = 0b11_00000000 - 10; // 758
    102     // Adding c2 to val gives 01 in the top bits for val < 100, 10 for val >= 100
    103     const C2: u32 = 0b10_00000000 - 100; // 412
    104 
    105     // Value of top bits:
    106     //            +c1  +c2  1&2
    107     //     0..=9   10   01   00 = 0
    108     //   10..=99   11   01   01 = 1
    109     // 100..=255   11   10   10 = 2
    110     return ((x + C1) & (x + C2)) >> 8;
    111 }
    112 
    113 inline fn less_than_5(x: u32) u32 {
    114     // Similar to log10u8, when adding one of these constants to val,
    115     // we get two possible bit patterns above the low 17 bits,
    116     // depending on whether val is below or above the threshold.
    117     const C1: u32 = 0b011_00000000000000000 - 10; // 393206
    118     const C2: u32 = 0b100_00000000000000000 - 100; // 524188
    119     const C3: u32 = 0b111_00000000000000000 - 1000; // 916504
    120     const C4: u32 = 0b100_00000000000000000 - 10000; // 514288
    121 
    122     // Value of top bits:
    123     //                +c1  +c2  1&2  +c3  +c4  3&4   ^
    124     //         0..=9  010  011  010  110  011  010  000 = 0
    125     //       10..=99  011  011  011  110  011  010  001 = 1
    126     //     100..=999  011  100  000  110  011  010  010 = 2
    127     //   1000..=9999  011  100  000  111  011  011  011 = 3
    128     // 10000..=99999  011  100  000  111  100  100  100 = 4
    129     return (((x + C1) & (x + C2)) ^ ((x + C3) & (x + C4))) >> 17;
    130 }
    131 
    132 test log10_int {
    133     if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
    134     if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
    135     if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
    136     if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
    137     if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
    138     if (builtin.zig_backend == .stage2_llvm and comptime builtin.target.isWasm()) return error.SkipZigTest; // TODO
    139 
    140     inline for (
    141         .{ u8, u16, u32, u64, u128, u256, u512 },
    142         .{ 2, 4, 9, 19, 38, 77, 154 },
    143     ) |T, max_exponent| {
    144         for (0..max_exponent + 1) |exponent_usize| {
    145             const exponent: std.math.Log2Int(T) = @intCast(exponent_usize);
    146             const power_of_ten = try std.math.powi(T, 10, exponent);
    147 
    148             if (exponent > 0) {
    149                 try testing.expectEqual(exponent - 1, log10_int(power_of_ten - 9));
    150                 try testing.expectEqual(exponent - 1, log10_int(power_of_ten - 1));
    151             }
    152             try testing.expectEqual(exponent, log10_int(power_of_ten));
    153             try testing.expectEqual(exponent, log10_int(power_of_ten + 1));
    154             try testing.expectEqual(exponent, log10_int(power_of_ten + 8));
    155         }
    156         try testing.expectEqual(max_exponent, log10_int(@as(T, std.math.maxInt(T))));
    157     }
    158 }