Add binary-op pow#79
Add binary-op pow#79trotterdylan merged 9 commits intogoogle:masterfrom MirkoDziadzka:add-binaryop-pow
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.
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.
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.
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.
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.
A couple more test cases would be good here, e.g. negative numbers, floats, etc.
There was a problem hiding this comment.
This is the parser part. Does this really need more test?
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
This is great! Thanks for working on this. Merging shortly.
added an implementation of the pow operator (**) for python. This should fix #36