-
Notifications
You must be signed in to change notification settings - Fork 639
Add binary-op pow #79
Conversation
Fixed the implemenation for int, trying a fast path first and then fall back to Long. Adding implementation for Long and Float
| } | ||
|
|
||
| func floatPow(f *Frame, v, w *Object) (*Object, *BaseException) { | ||
| return floatArithmeticOp(f, "__pow__", v, w, func(v, w float64) float64 { return math.Pow(v, w) }) |
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.
math.Pow() already takes 2 floats and returns another, so I think this could be reduced:
func floatPow(f *Frame, v, w *Object) (*Object, *BaseException) {
return floatArithmeticOp(f, "__pow__", v, w, math.Pow)
}Same goes for RPow below.
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.
RPow below can not changed this way, because the order of the arguments is reversed.
| @@ -0,0 +1,4 @@ | |||
|
|
|||
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.
Add the file header:
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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.
Minor nit, but I don't think we need to create a whole new file for just this one pow test. Put it in testing/op_test.py, maybe?
|
This looks great! Just a couple comments. 👍 |
| testBinOpArithmeticMod = _MakeExprTest('9 % 5') | ||
| testBinOpArithmeticMul = _MakeExprTest('3 * 2') | ||
| testBinOpArithmeticOr = _MakeExprTest('2 | 6') | ||
| testBinOpArithmeticPow = _MakeExprTest('2 ** 16') |
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 couple more test cases would be good here, e.g. negative numbers, floats, etc.
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.
This is the parser part. Does this really need more test?
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.
You're right, this is consistent with what's there. It's fine to leave it the way it is.
runtime/core_test.go
Outdated
| {Mul, newObject(ObjectType), newObject(fooType), nil, mustCreateException(TypeErrorType, "unsupported operand type(s) for *: 'object' and 'Foo'")}, | ||
| {Or, NewInt(-42).ToObject(), NewInt(244).ToObject(), NewInt(-10).ToObject(), nil}, | ||
| {Or, NewInt(42).ToObject(), NewStr("foo").ToObject(), nil, mustCreateException(TypeErrorType, "unsupported operand type(s) for |: 'int' and 'str'")}, | ||
| {Pow, NewInt(2).ToObject(), NewInt(16).ToObject(), NewInt(65536).ToObject(), nil}, |
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.
Again, a few more test cases would be nice.
| {Mul, NewFloat(math.Inf(1)).ToObject(), NewInt(-5).ToObject(), NewFloat(math.Inf(-1)).ToObject(), nil}, | ||
| {Mul, False.ToObject(), NewFloat(math.Inf(1)).ToObject(), NewFloat(math.NaN()).ToObject(), nil}, | ||
| {Mul, None, NewFloat(1.5).ToObject(), nil, mustCreateException(TypeErrorType, "unsupported operand type(s) for *: 'NoneType' and 'float'")}, | ||
| {Pow, NewFloat(2.0).ToObject(), NewInt(10).ToObject(), NewFloat(1024.0).ToObject(), nil}, |
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.
The "unsupported operand type" case would be good here.
runtime/int.go
Outdated
| } | ||
|
|
||
| func intCheckedPow(v, w int) (int, bool) { | ||
| // first try the shortcut - assuming the this operation is |
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.
Is this a reasonable assumption? I have no familiarity with the relative speed of float pow vs. a multiplication loop.
Also, please make comments full sentences (capitalize and period at the end.)
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.
On my system measuring a simple math.Exp vs. big.NewInt(0).Pow gives 4µs vs. 16µs. I have not yet tested the full code with all the surrounding functions.
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.
That's good enough for me. We can always tweak it later.
One other thing: doesn't it seem like on 64 bit systems there will be large integers that cannot be represented exactly by a float64 and therefore this won't work for some set of numbers?
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.
Yes. And the implementation of python x ** y differs a lot from Go big.Exp and math.Pow when x or y are negative.
A complete implementation will need some more time ...
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.
Strictly looking at math.Pow vs. float ** float, it seems like Go and Python are very similar:
Expression Restriction Go Python
Pow(x, ±0.0) 1.0 1.0
Pow(1.0, y) 1.0 1.0
Pow(x, 1.0) x x
Pow(NaN, y) NaN NaN
Pow(x, NaN) NaN NaN
Pow(±0.0, y) y an odd integer < 0 ±Inf ZeroDivisionError
Pow(±0.0, y) finite y < 0 and not an odd integer +Inf ZeroDivisionError
Pow(±0.0, y) y an odd integer > 0 ±0.0 ±0.0
Pow(±0.0, y) finite y > 0 and not an odd integer +0.0 +0.0
Pow(±0.0, -Inf) +Inf +Inf
Pow(±0.0, +Inf) +0.0 +0.0
Pow(-1.0, ±Inf) 1.0 1.0
Pow(x, +Inf) |x| > 1 +Inf +Inf
Pow(x, -Inf) |x| > 1 +0.0 +0.0
Pow(x, +Inf) |x| < 1 +0.0 +0.0
Pow(x, -Inf) |x| < 1 +Inf +Inf
Pow(+Inf, y) y > 0 +Inf +Inf
Pow(+Inf, y) y < 0 +0.0 +0.0
Pow(-Inf, y) y an odd integer < 0 -0.0 -0.0
Pow(-Inf, y) finite y < 0 and not an odd integer +0.0 +0.0
Pow(-Inf, y) y an odd integer > 0 -Inf -Inf
Pow(-Inf, y) finite y > 0 and not an odd integer Inf Inf
Pow(x, y) finite x < 0 and finite non-integer y NaN ValueError
|
Thanks a lot for the PR! This looks very complete. Thanks for taking the time to dig into it. I have just a few comments. |
trotterdylan
left a 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.
Thanks for doing this! The implementation is shaping up really well. I have a few more comments and suggestions.
| testBinOpArithmeticMod = _MakeExprTest('9 % 5') | ||
| testBinOpArithmeticMul = _MakeExprTest('3 * 2') | ||
| testBinOpArithmeticOr = _MakeExprTest('2 | 6') | ||
| testBinOpArithmeticPow = _MakeExprTest('2 ** 16') |
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.
You're right, this is consistent with what's there. It's fine to leave it the way it is.
runtime/int.go
Outdated
| // IEEE float64 has 52bit of precision, so the result should be | ||
| // less than MaxInt32 to be representable as an exact integer. | ||
| // This assumes that int is at least 32bit. | ||
| v_int := toIntUnsafe(v).Value() |
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.
use camelCase for vars: vInt, wInt
runtime/int.go
Outdated
| w_int := toIntUnsafe(w).Value() | ||
| if 0 < v_int && v_int <= math.MaxInt32 && 0 < w_int && w_int <= math.MaxInt32 { | ||
| res := math.Pow(float64(v_int), float64(w_int)) | ||
| // can the result be interpreted as an 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.
Capitalize comments please
runtime/int.go
Outdated
| if v_int == 0 { | ||
| if w_int < 0 { | ||
| return nil, f.RaiseType(ZeroDivisionErrorType, "0.0 cannot be raised to a negative power") | ||
| } else if w_int == 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.
Omit else when it doesn't change the semantics:
if wInt < 0 {
return nil, f.RaiseType(...)
}
if wInt == 0 {
return ...
}
return ...
| LongType.slots.New = &newSlot{longNew} | ||
| LongType.slots.NonZero = longUnaryBoolOpSlot(longNonZero) | ||
| LongType.slots.Or = longBinaryOpSlot(longOr) | ||
| LongType.slots.Pow = &binaryOpSlot{longPow} |
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.
Is there a reason not to use longBinaryOpSlot() here? It's a well established pattern here and it takes care of a little bit of the type coercion for you.
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.
longBinaryOpSlot expect a function which always return a Long. But in **, when the exponent is negative, we have to return a Float. So this function can not be used (or I'm missing something here).
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 see. No I don't think you're missing anything. It's probably worth adding a comment about why we don't use longBinaryOpSlot for this method.
runtime/long.go
Outdated
| } | ||
|
|
||
| func longRPow(f *Frame, v, w *Object) (*Object, *BaseException) { | ||
| return longPow(f, w, v) |
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.
w may not be a long which would cause an invalid unsafe cast on line 495. Incidentally, I think using longRBinaryOpSlot for the RPow slot will allow you to use a single longPow and handle swapping the operands for you.
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.
Thanks for spotting the error. I will add a test for this and a fix.
| assert large_number ** -1 == (1.0 / large_number), "large_number ** -1 == (1.0 / large_number)" | ||
| assert large_number ** 0 == 1, "large_number ** 0 == 1" | ||
| assert large_number ** 1 == large_number, "large_number ** 1 == large_number" | ||
|
|
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.
Remove excess trailing newlines (one is OK).
| assert 2 ** -1 == 0.5, "2 ** -1" | ||
| assert 2 ** 0 == 1, "2 ** 0" | ||
| assert 2 ** 1 == 2, "2 ** 1" | ||
| assert 2 ** 2 == 4, "2 ** 2" |
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.
Good to have some explicit long tests here, e.g. assert 2L ** 2 == 4, "2L ** 2"
| {Or, NewInt(-100).ToObject(), NewInt(50).ToObject(), NewInt(-66).ToObject(), nil}, | ||
| {Or, NewInt(MaxInt).ToObject(), NewInt(MinInt).ToObject(), NewInt(-1).ToObject(), nil}, | ||
| {Or, newObject(ObjectType), NewInt(-100).ToObject(), nil, mustCreateException(TypeErrorType, "unsupported operand type(s) for |: 'object' and 'int'")}, | ||
| {Pow, NewInt(2).ToObject(), NewInt(128).ToObject(), NewLong(big.NewInt(0).Exp(big.NewInt(2), big.NewInt(128), nil)).ToObject(), nil}, |
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 couple more test cases (e.g. TypeError case) wouldn't go amiss.
| {Or, -100, 50, NewLong(big.NewInt(-66)).ToObject(), nil}, | ||
| {Or, MaxInt, MinInt, NewLong(big.NewInt(-1)).ToObject(), nil}, | ||
| {Or, newObject(ObjectType), 100, nil, mustCreateException(TypeErrorType, "unsupported operand type(s) for |: 'object' and 'long'")}, | ||
| {Pow, 2, 128, NewLong(big.NewInt(0).Exp(big.NewInt(2), big.NewInt(128), nil)).ToObject(), nil}, |
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 couple more test cases would be good
- use camelCase for local variables - fix a problem in 1 ** 1L (and add a test) - add more test
|
Is this code ready for another round of reviews then? |
|
Yes, please review. |
trotterdylan
left a 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.
This is great! Thanks for working on this. Merging shortly.
added an implementation of the pow operator (**) for python. This should fix #36