Skip to content
This repository was archived by the owner on Mar 23, 2023. It is now read-only.

Conversation

@MirkoDziadzka
Copy link
Contributor

added an implementation of the pow operator (**) for python. This should fix #36

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

@ns-cweber ns-cweber Jan 9, 2017

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.

Copy link
Contributor Author

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 @@

Copy link
Contributor

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.

Copy link
Contributor

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?

@ns-cweber
Copy link
Contributor

This looks great! Just a couple comments. 👍

testBinOpArithmeticMod = _MakeExprTest('9 % 5')
testBinOpArithmeticMul = _MakeExprTest('3 * 2')
testBinOpArithmeticOr = _MakeExprTest('2 | 6')
testBinOpArithmeticPow = _MakeExprTest('2 ** 16')
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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.

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

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},
Copy link
Contributor

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

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.)

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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 ...

Copy link
Contributor

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

@trotterdylan
Copy link
Contributor

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.

Copy link
Contributor

@trotterdylan trotterdylan left a 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')
Copy link
Contributor

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

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

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

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

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.

Copy link
Contributor Author

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).

Copy link
Contributor

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

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.

Copy link
Contributor Author

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"

Copy link
Contributor

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

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},
Copy link
Contributor

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},
Copy link
Contributor

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
@trotterdylan
Copy link
Contributor

Is this code ready for another round of reviews then?

@MirkoDziadzka
Copy link
Contributor Author

Yes, please review.

Copy link
Contributor

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

@trotterdylan trotterdylan merged commit 3dc695e into google:master Jan 18, 2017
@MirkoDziadzka MirkoDziadzka deleted the add-binaryop-pow branch January 18, 2017 09:17
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

binary op not implemented: Pow

4 participants