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.powi
: use standard definition of underflow/overflow, implement u0
, i0
, i1
edge case
#11499
Conversation
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.
Nice attempt. Math is tricky.
The semantics for underflow
are abit brittle, as the return value may be <1 and additionally an overflow exists (which is not intuitive as people also refer to it as "integer underflow").
Aside, a bunch of test cases are missing, 1 optimization could be done and the "reasoning why this math works and correct" would be very nice..
} | ||
|
||
return acc; | ||
} | ||
|
||
test "math.powi" { |
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.
Unfortunately this is missing a bunch of edge cases.
Using pseudo code related to python
0**0 == 1 // u0,i0,u1,i1
1**1 == 1 // same
1**0 == 1
1**y == 1, y any
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.
it is on the first branch, if (x == 1 or y == 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.
I dont understand. The special cases u0,u1,i0,i1
, which you added in the PR, are not tested.
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.
sorry, i misunderstood your comment.
now i added test cases on u0, i0, i1
. u1
is not special cases as it can represent 1
.
lib/std/math/powi.zig
Outdated
if (y > 0) { | ||
return 0; | ||
} else { | ||
// not overflow in strict sense |
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 dont understand this. Please be more specific.
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 comment to
// Infinity/NaN, not overflow in strict sense
} | ||
} | ||
if (x == 0) { | ||
if (y > 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.
It must be y >= 0
, as 0^0 == 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.
0^0 is 1. added doc-comment on edge cases.
lib/std/math/powi.zig
Outdated
/// - powi(-1, y) = 1 for y an even integer | ||
/// - powi(x, y) = Overflow for y >= @sizeOf(x) - 1 or y > 0 | ||
/// - powi(x, y) = Underflow for y > @sizeOf(x) - 1 or y < 0 | ||
/// - powi(x, 0) = 1 unless T is i1, i0, u0 |
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.
nit: please use ascending sorting by x, then y.
T is not explained before T is i1, i0, u0
. I guess its the type for x, but then I still dont understand what the result semantics are from the text.
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.
It is ordered by precedence, added comment
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.
Are those missing u1?
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.
u1
can represent 1
, so no need for special case
lib/std/math/powi.zig
Outdated
/// - powi(x, 0) = 1 unless T is i1, i0, u0 | ||
/// - powi(0, x) = 0 | ||
/// - powi(0, x) = Overflow when x < 0 | ||
/// - powi(1, y) = 1 unless T is i1, i0, u0 |
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.
same argument.
lib/std/math/powi.zig
Outdated
// powi(x, +-0) = 1 for any x | ||
if (y == 0 or y == -0) { | ||
return 1; | ||
// integer value 1 cannot be coerced to type 'i1' |
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 dont see the coercion here. please be more specific. As I understand it, you try to special case any operation on u0,i0,u1,i1.
However, const t1: u1 = 0; const t2: u1 = 0;
with 0**0
and y being anything is a valid operation. And same for i1
.
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 added comment on why naive code won't compile when T
is i1
} | ||
} | ||
// x >= 2 or x <= -2 | ||
if (y >= bit_size) { |
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.
counterexample: x**1
with x=1
for u1
.
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.
powi(u1, 1, 1)
is checked at the first branch, if (x == 1
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.
my bad, mark as resolved then.
if (y >= bit_size) { | ||
return error.Overflow; | ||
} | ||
if (y < 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.
should be checked first to remove the x==0 and
y<0` branch.
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.
0^(-1)
should be an overflow, not an underflow?
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.
Julia thinks 0^(-1)
is Inf.
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.
which is overflow per the code's definition? I don't see how that would remove branch, as x==0 and y<0
and x !=0 and y<0
(and implictly x != -1 and x !=1
, because it is already checked) should give different result (Overflow and Underflow respectively)
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.
correct, my bad then.
var exp = y; | ||
var acc: T = if (does_one_overflow) unreachable else 1; | ||
|
||
while (exp > 1) { |
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.
can you write as comment the used approach? ideally with invariant(s) to show why this works?
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.
added one invariant. a direct invariant would require another tracked variable to be concise, so i hope this would be sufficient.
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.
did you forget to push? The rest is looking decent now.
try testing.expectError(error.Underflow, powi(i64, -2, 64)); | ||
try testing.expectError(error.Underflow, powi(i17, -2, 17)); | ||
try testing.expectError(error.Underflow, powi(i42, -2, 42)); | ||
try testing.expectError(error.Overflow, powi(i8, -2, 8)); |
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.
-2**8
is in python -256, which clearly is < 1. But this gives Overflow. Probably renaming the error into something different than Underflow
would help.
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.
(-2)**8
is 256. also, abs(-256)
is 256
, which is larger than 1
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.
ups. my bad. mark as resolved then.
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.
Two small fixups requested, other than that, looks ready to merge.
lib/std/math/powi.zig
Outdated
const bit_size = @bitSizeOf(T); | ||
|
||
comptime assert(@typeInfo(T) == .Int); |
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.
const bit_size = @bitSizeOf(T); | |
comptime assert(@typeInfo(T) == .Int); | |
const bit_size = @typeInfo(T).Int.bits; |
lib/std/math/powi.zig
Outdated
if (y == 0 or y == -0) { | ||
return 1; | ||
// `y & 1 == 0` won't compile when `does_one_overflow`. | ||
const does_one_overflow = bit_size == 0 or T == i1; |
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.
const does_one_overflow = bit_size == 0 or T == i1; | |
const does_one_overflow = math.maxInt(T) < 1; |
…ment `u0`, `i0`, `i1` edge case (#11499)
This PR changes
std.math.powi
u0
,i0
,i1
edge casedetails :
1
overflows foru0
,i0
,i1
.@bitSizeOf
instead of8 * @sizeOf
.pow(i32, -2, 31)
shouldn't overflow, whilepow(i31, -2, 30)
andpow(i32, -2, 32)
should.