Skip to content
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

Merged
merged 19 commits into from May 16, 2022

Conversation

leesongun
Copy link
Contributor

@leesongun leesongun commented Apr 22, 2022

This PR changes std.math.powi

  1. use standard definition of underflow/overflow, and document it.
  2. implement u0, i0, i1 edge case
  3. better short-circuit
  4. more test cases

details :

  1. I think underflow means 'being to insignificant', rather than 'too close to -Infinity'. (IEEE754 and wikipedia)
  2. 1 overflows for u0, i0, i1.
  3. Using @bitSizeOf instead of 8 * @sizeOf.
  4. pow(i32, -2, 31) shouldn't overflow, while pow(i31, -2, 30) and pow(i32, -2, 32) should.

Copy link
Contributor

@matu3ba matu3ba left a 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" {
Copy link
Contributor

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

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor Author

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.

if (y > 0) {
return 0;
} else {
// not overflow in strict sense
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

@leesongun leesongun Apr 23, 2022

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.

/// - 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
Copy link
Contributor

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.

Copy link
Contributor Author

@leesongun leesongun Apr 23, 2022

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are those missing u1?

Copy link
Contributor Author

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

/// - 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same argument.

// powi(x, +-0) = 1 for any x
if (y == 0 or y == -0) {
return 1;
// integer value 1 cannot be coerced to type 'i1'
Copy link
Contributor

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.

Copy link
Contributor Author

@leesongun leesongun Apr 23, 2022

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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)

Copy link
Contributor

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) {
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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));
Copy link
Contributor

@matu3ba matu3ba Apr 23, 2022

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.

Copy link
Contributor Author

@leesongun leesongun Apr 23, 2022

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

Copy link
Contributor

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.

Copy link
Member

@andrewrk andrewrk left a 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.

Comment on lines 30 to 32
const bit_size = @bitSizeOf(T);

comptime assert(@typeInfo(T) == .Int);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const bit_size = @bitSizeOf(T);
comptime assert(@typeInfo(T) == .Int);
const bit_size = @typeInfo(T).Int.bits;

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const does_one_overflow = bit_size == 0 or T == i1;
const does_one_overflow = math.maxInt(T) < 1;

@andrewrk andrewrk merged commit 1de7b8d into ziglang:master May 16, 2022
andrewrk pushed a commit that referenced this pull request Jul 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants