-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
std.math.log_int: implement integer logarithm without using float math #17143
Conversation
98a2476
to
daafc14
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A quick review without looking too much into the algorithm. Test cases for signed integers are missing.
@@ -23,14 +23,17 @@ pub fn log(comptime T: type, base: T, x: T) T { | |||
.ComptimeFloat => { | |||
return @as(comptime_float, @log(@as(f64, x)) / @log(float_base)); | |||
}, | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add, that we should special case comptime-known divisions of power of two with shifts and/or make an issue.
lib/std/math/log_int.zig
Outdated
if (@typeInfo(T) != .Int or @typeInfo(T).Int.signedness != .unsigned) | ||
@compileError("log_int requires an unsigned integer, found " ++ @typeName(T)); | ||
|
||
assert(base > 1 and x != 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiousity: Did you test, if the assertion fires, if either base or x is known at comptime and satisfies the subexpressions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand the question. Do you mean whether an expression such as log_int(u8, 3, 82)
panics? In this case, it does not: it returns @as(u3, 4)
.
Did the methods explained here https://github.com/ziglang/zig/wiki/Contributing#directly-testing-the-standard-library-with-zig-test not work for you? Please link the errors and/or explain the behavior. |
They are missing because signed integers are unsupported: pub fn log_int(comptime T: type, base: T, x: T) Log2Int(T) {
if (@typeInfo(T) != .Int or @typeInfo(T).Int.signedness != .unsigned)
@compileError("log_int requires an unsigned integer, found " ++ @typeName(T));
// ...
} This is coherent with Lines 1245 to 1247 in 4a44b79
Lines 41 to 45 in 4a44b79
|
Yep, it worked! I was missing the |
c679e40
to
c64b9bd
Compare
What I forgot to mention is that, apart from all the tests which are always nice to have, the algorithm is provably correct. The idea is the following.
|
There got to be faster algorithms than this. By repeatedly squaring the base for example, we can reduce the complexity to Quick and dirty example implementationpub fn log_int2(comptime T: type, base: T, x: T) Log2Int(T) {
if (@typeInfo(T) != .Int or @typeInfo(T).Int.signedness != .unsigned)
@compileError("log_int requires an unsigned integer, found " ++ @typeName(T));
assert(base > 1 and x != 0);
var precomputedBases: [std.math.log2_int_ceil(usize, @bitSizeOf(T))]T = undefined;
precomputedBases[0] = base;
var i: usize = 0;
while (precomputedBases[i] <= x / precomputedBases[i]) {
precomputedBases[i+1] = precomputedBases[i]*precomputedBases[i];
i += 1;
}
i += 1;
var exponent: Log2Int(T) = 0;
var power: T = 1;
while(i > 0) {
i -= 1;
if(power <= x / precomputedBases[i]) {
power *= precomputedBases[i];
exponent += @as(Log2Int(T), 1) << @intCast(i);
}
}
return exponent;
} However this will likely cause slowdowns for small |
lib/std/math/log_int.zig
Outdated
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`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make it x > 0
then, if log_int is intended to only support unsigned integers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed: 8a29847
I do usually provide a link to the original idea etc in a brief manner and/or very brief reasoning. See Line 18 in 8fb4a4e
Not sure, if this is also needed in this case, but at least I find it usually nicer to have something on readign+reviewing the algorithms. In this case tests are mainly to uncover regressions and miscompilations (in Zig and LLVM or another backend). |
I agree, however I thought that as a starting point the priority was to replace the incorrect implementation with a correct one. |
Head branch was pushed to by a user without write access
Added comments in 55573d8. |
This addresses #17142.
Important
This patch leaves the
comptime_int
as is, i.e. broken. I haven't had the time to figure out where should I put the implementation for that.Given that the code for
log_int
is extremely simple, it should be straightforward for someone with knowledge of the compiler to port the implementation to cover that case too.DisclaimerUnfortunately, I'm not able to run the tests locally directly from the source tree. I tested the code as a separate module, not integrated in the rest of the standard library.Edit: I managed to run the tests directly in the standard library source tree.