diff --git a/docs/api/fexpr/__and__.rst b/docs/api/fexpr/__and__.rst index 42ca3f21f0..32ef0da6d1 100644 --- a/docs/api/fexpr/__and__.rst +++ b/docs/api/fexpr/__and__.rst @@ -1,6 +1,6 @@ .. xmethod:: datatable.FExpr.__and__ - :src: src/core/expr/fexpr.cc PyFExpr::nb__and__ + :src: src/core/expr/fbinary/bitwise_and_or_xor.cc PyFExpr::nb__and__ __and__(x, y) -- diff --git a/docs/api/fexpr/__lshift__.rst b/docs/api/fexpr/__lshift__.rst index 96e6c90008..f2f34a363c 100644 --- a/docs/api/fexpr/__lshift__.rst +++ b/docs/api/fexpr/__lshift__.rst @@ -1,6 +1,6 @@ .. xmethod:: datatable.FExpr.__lshift__ - :src: src/core/expr/fexpr.cc PyFExpr::nb__lshift__ + :src: src/core/expr/fbinary/bitwise_shift.cc PyFExpr::nb__lshift__ __lshift__(x, y) -- diff --git a/docs/api/fexpr/__or__.rst b/docs/api/fexpr/__or__.rst index f415371b70..cee868b438 100644 --- a/docs/api/fexpr/__or__.rst +++ b/docs/api/fexpr/__or__.rst @@ -1,6 +1,6 @@ .. xmethod:: datatable.FExpr.__or__ - :src: src/core/expr/fexpr.cc PyFExpr::nb__or__ + :src: src/core/expr/fbinary/bitwise_and_or_xor.cc PyFExpr::nb__or__ __or__(x, y) -- diff --git a/docs/api/fexpr/__rshift__.rst b/docs/api/fexpr/__rshift__.rst index 6e50b5aaca..820be93dfb 100644 --- a/docs/api/fexpr/__rshift__.rst +++ b/docs/api/fexpr/__rshift__.rst @@ -1,6 +1,6 @@ .. xmethod:: datatable.FExpr.__rshift__ - :src: src/core/expr/fexpr.cc PyFExpr::nb__rshift__ + :src: src/core/expr/fbinary/bitwise_shift.cc PyFExpr::nb__rshift__ __rshift__(x, y) -- diff --git a/docs/api/fexpr/__xor__.rst b/docs/api/fexpr/__xor__.rst index c6b5964735..634ba1ed41 100644 --- a/docs/api/fexpr/__xor__.rst +++ b/docs/api/fexpr/__xor__.rst @@ -1,6 +1,6 @@ .. xmethod:: datatable.FExpr.__xor__ - :src: src/core/expr/fexpr.cc PyFExpr::nb__xor__ + :src: src/core/expr/fbinary/bitwise_and_or_xor.cc PyFExpr::nb__xor__ __xor__(x, y) -- diff --git a/src/core/expr/fbinary/bimaker.cc b/src/core/expr/fbinary/bimaker.cc index d488f54eeb..9d8fddedce 100644 --- a/src/core/expr/fbinary/bimaker.cc +++ b/src/core/expr/fbinary/bimaker.cc @@ -45,12 +45,6 @@ static std::unordered_map bimakers_library; bimaker_ptr resolve_op(Op opcode, SType stype1, SType stype2) { switch (opcode) { - case Op::AND: return resolve_op_and(stype1, stype2); - case Op::OR: return resolve_op_or(stype1, stype2); - case Op::XOR: return resolve_op_xor(stype1, stype2); - case Op::LSHIFT: return resolve_op_lshift(stype1, stype2); - case Op::RSHIFT: return resolve_op_rshift(stype1, stype2); - case Op::ARCTAN2: return resolve_fn_atan2(stype1, stype2); case Op::HYPOT: return resolve_fn_hypot(stype1, stype2); case Op::POWERFN: return resolve_fn_pow(stype1, stype2); diff --git a/src/core/expr/fbinary/bimaker.h b/src/core/expr/fbinary/bimaker.h index 219cb0168e..04d56a584b 100644 --- a/src/core/expr/fbinary/bimaker.h +++ b/src/core/expr/fbinary/bimaker.h @@ -77,12 +77,6 @@ using bimaker_ptr = std::unique_ptr; // Main resolver, calls individual-op resolvers below bimaker_ptr resolve_op(Op, SType, SType); -bimaker_ptr resolve_op_and(SType, SType); -bimaker_ptr resolve_op_or(SType, SType); -bimaker_ptr resolve_op_xor(SType, SType); -bimaker_ptr resolve_op_lshift(SType, SType); -bimaker_ptr resolve_op_rshift(SType, SType); - bimaker_ptr resolve_fn_atan2(SType, SType); bimaker_ptr resolve_fn_hypot(SType, SType); bimaker_ptr resolve_fn_pow(SType, SType); diff --git a/src/core/expr/fbinary/bitwise.cc b/src/core/expr/fbinary/bitwise.cc deleted file mode 100644 index a177de0d52..0000000000 --- a/src/core/expr/fbinary/bitwise.cc +++ /dev/null @@ -1,438 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright 2019-2020 H2O.ai -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//------------------------------------------------------------------------------ -// AND, OR, XOR -// LSHIFT, RSHIFT -//------------------------------------------------------------------------------ -#include "expr/fbinary/bimaker.h" -#include "expr/fbinary/bimaker_impl.h" -#include "ltype.h" -#include "stype.h" -namespace dt { -namespace expr { - - -static SType _find_common_stype(SType stype1, SType stype2) { - while (stype1 != stype2) { - if (stype1 == SType::VOID) stype1 = SType::BOOL; else - if (stype2 == SType::VOID) stype2 = SType::BOOL; else - if (stype1 == SType::BOOL) stype1 = SType::INT8; else - if (stype2 == SType::BOOL) stype2 = SType::INT8; else - if (stype1 == SType::INT8) stype1 = SType::INT16; else - if (stype2 == SType::INT8) stype2 = SType::INT16; else - if (stype1 == SType::INT16) stype1 = SType::INT32; else - if (stype2 == SType::INT16) stype2 = SType::INT32; else - if (stype1 == SType::INT32) stype1 = SType::INT64; else - if (stype2 == SType::INT32) stype2 = SType::INT64; else - return SType::INVALID; - } - return stype1; -} - - -/** - * Find suitable common stype for logical operations AND, OR, XOR. - * If both operands are boolean then the common stype will also be - * BOOL. If both operands are integer (one may also be boolean), - * then the common stype will be the largest of two integer stypes. - * Floating-point and string stypes are not allowed. - */ -static SType _find_types_for_andor( - SType stype1, SType stype2, SType* uptype1, SType* uptype2, - const char* name) -{ - SType stype0 = _find_common_stype(stype1, stype2); - LType ltype0 = stype_to_ltype(stype0); - if (!(ltype0 == LType::BOOL || ltype0 == LType::INT)) { - throw TypeError() << "Operator `" << name << "` cannot be applied to " - "columns with types `" << stype1 << "` and `" << stype2 << "`"; - } - *uptype1 = (stype1 == stype0)? SType::AUTO : stype0; - *uptype2 = (stype2 == stype0)? SType::AUTO : stype0; - return stype0; -} - - -/** - * Find suitable stype(s) for bitwise shift operation. The stype of - * the result is always equal to `stype1` (which may only be integer) - * and the first argument is never promoted. The second argument can - * be either integer or boolean, and is always promoted into INT32. - */ -static void _find_types_for_shifts( - SType stype1, SType stype2, SType* uptype2, const char* name) -{ - LType ltype1 = stype_to_ltype(stype1); - LType ltype2 = stype_to_ltype(stype2); - if (ltype1 == LType::INT && (ltype2 == LType::INT || ltype2 == LType::BOOL)) { - *uptype2 = (stype2 == SType::INT32)? SType::AUTO : SType::INT32; - } - else { - throw TypeError() << "Operator `" << name << "` cannot be applied to " - "columns with types `" << stype1 << "` and `" << stype2 << "`"; - } -} - - - - -//------------------------------------------------------------------------------ -// Op::AND (boolean) -//------------------------------------------------------------------------------ - -/** - * Virtual column implementing short-circuit boolean-AND evalation. - * Specifically, if columns X and Y are boolean, then each value - * x and y can be in one of 3 possible states: 0, 1 and NA. The - * result of (x & y) is given by this table: - * y - * AND | 0 | 1 | NA - * ----+---+---+--- - * 0 | 0 | 0 | 0 <-- short-circuit - * x 1 | 0 | 1 | NA - * NA | 0 | NA| NA - * - * In particular, notice that `(0 & y) == 0` no matter what the - * value of `y` is, including NA. - * - * Also, the evaluation uses short-circuit semantics: if `x` - * evaluates to 0 (False), then `y` is not computed at all. - */ -class BooleanAnd_ColumnImpl : public Virtual_ColumnImpl { - protected: - Column arg1_; - Column arg2_; - - public: - BooleanAnd_ColumnImpl(Column&& col1, Column&& col2, size_t nrows) - : Virtual_ColumnImpl(nrows, SType::BOOL), - arg1_(std::move(col1)), arg2_(std::move(col2)) {} - - ColumnImpl* clone() const override { - return new BooleanAnd_ColumnImpl(Column(arg1_), Column(arg2_), nrows_); - } - - void verify_integrity() const override { - XAssert(arg1_.stype() == SType::BOOL); - XAssert(arg2_.stype() == SType::BOOL); - } - - size_t n_children() const noexcept override { - return 2; - } - - const Column& child(size_t i) const override { - xassert(i < 2); - return (i == 0)? arg1_ : arg2_; - } - - - bool get_element(size_t i, int8_t* out) const override { - int8_t x, y; - bool xvalid = arg1_.get_element(i, &x); - if (x == 0 && xvalid) { // short-circuit - *out = 0; - return true; - } - bool yvalid = arg2_.get_element(i, &y); - if (!yvalid) return false; - if (y == 0) { - *out = 0; - return true; - } - *out = 1; - return xvalid; - } -}; - - -class BooleanAnd_bimaker : public bimaker { - public: - Column compute(Column&& col1, Column&& col2) const override { - size_t nrows = col1.nrows(); - return Column( - new BooleanAnd_ColumnImpl(std::move(col1), std::move(col2), nrows)); - } -}; - - - - -//------------------------------------------------------------------------------ -// Op::AND (&) -//------------------------------------------------------------------------------ - -template -inline static T op_and(T x, T y) { - return (x & y); -} - - -template -static inline bimaker_ptr _and(SType uptype1, SType uptype2, SType outtype) { - return bimaker1::make(op_and, uptype1, uptype2, outtype); -} - - -bimaker_ptr resolve_op_and(SType stype1, SType stype2) -{ - if (stype1 == SType::BOOL && stype2 == SType::BOOL) { - return bimaker_ptr(new BooleanAnd_bimaker()); - } - SType uptype1, uptype2; - SType stype0 = _find_types_for_andor(stype1, stype2, &uptype1, &uptype2, "&"); - switch (stype0) { - case SType::INT8: return _and(uptype1, uptype2, stype0); - case SType::INT16: return _and(uptype1, uptype2, stype0); - case SType::INT32: return _and(uptype1, uptype2, stype0); - case SType::INT64: return _and(uptype1, uptype2, stype0); - default: return bimaker_ptr(); - } -} - - - - -//------------------------------------------------------------------------------ -// Op::OR (boolean) -//------------------------------------------------------------------------------ - -/** - * Virtual column implementing short-circuit boolean-OR evalation. - * Specifically, if columns X and Y are boolean, then each value - * x and y can be in one of 3 possible states: 0, 1 and NA. The - * result of (x | y) is given by this table: - * y - * OR | 0 | 1 | NA - * ----+----+---+--- - * 0 | 0 | 1 | NA - * x 1 | 1 | 1 | 1 <-- short-circuit - * NA | NA | 1 | NA - * - * In particular, notice that `(1 | y) == 1` no matter what the - * value of `y` is, including NA. - * - * Also, the evaluation uses short-circuit semantics: if `x` - * evaluates to 1 (True), then `y` is not computed at all. - */ -class BooleanOr_ColumnImpl : public Virtual_ColumnImpl { - protected: - Column arg1_; - Column arg2_; - - public: - BooleanOr_ColumnImpl(Column&& col1, Column&& col2, size_t nrows) - : Virtual_ColumnImpl(nrows, SType::BOOL), - arg1_(std::move(col1)), arg2_(std::move(col2)) {} - - ColumnImpl* clone() const override { - return new BooleanOr_ColumnImpl(Column(arg1_), Column(arg2_), nrows_); - } - - void verify_integrity() const override { - XAssert(arg1_.stype() == SType::BOOL); - XAssert(arg2_.stype() == SType::BOOL); - } - - size_t n_children() const noexcept override { - return 2; - } - - const Column& child(size_t i) const override { - xassert(i < 2); - return (i == 0)? arg1_ : arg2_; - } - - - bool get_element(size_t i, int8_t* out) const override { - int8_t x, y; - bool xvalid = arg1_.get_element(i, &x); - if (x == 1 && xvalid) { // short-circuit - *out = 1; - return true; - } - bool yvalid = arg2_.get_element(i, &y); - if (!yvalid) return false; - if (y == 1) { - *out = 1; - return true; - } - *out = 0; - return xvalid; - } -}; - - -class BooleanOr_bimaker : public bimaker { - public: - Column compute(Column&& col1, Column&& col2) const override { - size_t nrows = col1.nrows(); - return Column( - new BooleanOr_ColumnImpl(std::move(col1), std::move(col2), nrows)); - } -}; - - - - -//------------------------------------------------------------------------------ -// Op::OR (|) -//------------------------------------------------------------------------------ - -template -inline static T op_or(T x, T y) { - return (x | y); -} - - -template -static inline bimaker_ptr _or(SType uptype1, SType uptype2, SType outtype) { - xassert(compatible_type(outtype)); - if (uptype1 != SType::AUTO) xassert(compatible_type(uptype1)); - if (uptype2 != SType::AUTO) xassert(compatible_type(uptype2)); - return bimaker1::make(op_or, uptype1, uptype2, outtype); -} - - -bimaker_ptr resolve_op_or(SType stype1, SType stype2) -{ - if (stype1 == SType::BOOL && stype2 == SType::BOOL) { - return bimaker_ptr(new BooleanOr_bimaker()); - } - SType uptype1, uptype2; - SType stype0 = _find_types_for_andor(stype1, stype2, &uptype1, &uptype2, "|"); - switch (stype0) { - case SType::INT8: return _or(uptype1, uptype2, stype0); - case SType::INT16: return _or(uptype1, uptype2, stype0); - case SType::INT32: return _or(uptype1, uptype2, stype0); - case SType::INT64: return _or(uptype1, uptype2, stype0); - default: return bimaker_ptr(); - } -} - - - - -//------------------------------------------------------------------------------ -// Op::XOR (^) -//------------------------------------------------------------------------------ - -template -inline static T op_xor(T x, T y) { - return (x ^ y); -} - - -template -static inline bimaker_ptr _xor(SType uptype1, SType uptype2, SType outtype) { - xassert(compatible_type(outtype)); - if (uptype1 != SType::AUTO) xassert(compatible_type(uptype1)); - if (uptype2 != SType::AUTO) xassert(compatible_type(uptype2)); - return bimaker1::make(op_xor, uptype1, uptype2, outtype); -} - - -bimaker_ptr resolve_op_xor(SType stype1, SType stype2) -{ - SType uptype1, uptype2; - SType stype0 = _find_types_for_andor(stype1, stype2, &uptype1, &uptype2, "^"); - switch (stype0) { - case SType::BOOL: return _xor(uptype1, uptype2, stype0); - case SType::INT8: return _xor(uptype1, uptype2, stype0); - case SType::INT16: return _xor(uptype1, uptype2, stype0); - case SType::INT32: return _xor(uptype1, uptype2, stype0); - case SType::INT64: return _xor(uptype1, uptype2, stype0); - default: return bimaker_ptr(); - } -} - - - - -//------------------------------------------------------------------------------ -// Op::LSHIFT (<<) -//------------------------------------------------------------------------------ - -template -inline static T op_lshift(T x, int32_t y) { - return (y >= 0)? static_cast(x << y) : static_cast(x >> -y); -} - - -template -static inline bimaker_ptr _lshift(SType outtype, SType uptype2) { - xassert(compatible_type(outtype)); - return bimaker1::make(op_lshift, SType::AUTO, - uptype2, outtype); -} - - -bimaker_ptr resolve_op_lshift(SType stype1, SType stype2) -{ - SType uptype2; - _find_types_for_shifts(stype1, stype2, &uptype2, "<<"); - switch (stype1) { - case SType::INT8: return _lshift(stype1, uptype2); - case SType::INT16: return _lshift(stype1, uptype2); - case SType::INT32: return _lshift(stype1, uptype2); - case SType::INT64: return _lshift(stype1, uptype2); - default: return bimaker_ptr(); - } -} - - - - -//------------------------------------------------------------------------------ -// Op::RSHIFT (>>) -//------------------------------------------------------------------------------ - -template -inline static T op_rshift(T x, int32_t y) { - return (y >= 0)? static_cast(x >> y) : static_cast(x << -y); -} - - -template -static inline bimaker_ptr _rshift(SType outtype, SType uptype2) { - xassert(compatible_type(outtype)); - return bimaker1::make(op_rshift, SType::AUTO, - uptype2, outtype); -} - - -bimaker_ptr resolve_op_rshift(SType stype1, SType stype2) -{ - SType uptype2; - _find_types_for_shifts(stype1, stype2, &uptype2, ">>"); - switch (stype1) { - case SType::INT8: return _rshift(stype1, uptype2); - case SType::INT16: return _rshift(stype1, uptype2); - case SType::INT32: return _rshift(stype1, uptype2); - case SType::INT64: return _rshift(stype1, uptype2); - default: return bimaker_ptr(); - } -} - - - - -}} // namespace dt::expr diff --git a/src/core/expr/fbinary/bitwise_and_or_xor.cc b/src/core/expr/fbinary/bitwise_and_or_xor.cc new file mode 100644 index 0000000000..e37fd7a093 --- /dev/null +++ b/src/core/expr/fbinary/bitwise_and_or_xor.cc @@ -0,0 +1,211 @@ +//------------------------------------------------------------------------------ +// Copyright 2020 H2O.ai +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//------------------------------------------------------------------------------ +#include "column/func_binary.h" +#include "expr/fbinary/fexpr_binaryop.h" +namespace dt { +namespace expr { + +template +static bool op_and_bool(ref_t x, bool xvalid, ref_t y, bool yvalid, int8_t* out) +{ + if (x == 0 && xvalid) { // short-circuit + *out = 0; + return true; + } + if (!yvalid) return false; + if (y == 0) { + *out = 0; + return true; + } + *out = 1; + return xvalid; +} + + +template +static bool op_or_bool(ref_t x, bool xvalid, ref_t y, bool yvalid, int8_t* out) +{ + if (x == 1 && xvalid) { // short-circuit + *out = 1; + return true; + } + if (!yvalid) return false; + if (y == 1) { + *out = 1; + return true; + } + *out = 0; + return xvalid; +} + + +template +inline static T op_and(T x, T y) { + return (x & y); +} + +template +inline static T op_or(T x, T y) { + return (x | y); +} + +template +inline static T op_xor(T x, T y) { + return (x ^ y); +} + +template +class FExpr__andor__ : public FExpr_BinaryOp { + public: + using FExpr_BinaryOp::FExpr_BinaryOp; + using FExpr_BinaryOp::lhs_; + using FExpr_BinaryOp::rhs_; + + + std::string name() const override { return AND?"&":"|"; } + int precedence() const noexcept override { return AND?9:7; } + + + Column evaluate1(Column&& lcol, Column&& rcol) const override { + xassert(lcol.nrows() == rcol.nrows()); + size_t nrows = lcol.nrows(); + auto stype1 = lcol.stype(); + auto stype2 = rcol.stype(); + auto stype0 = common_stype(stype1, stype2); + + if (stype1 == SType::VOID || stype2 == SType::VOID) { + return Column::new_na_column(lcol.nrows(), SType::VOID); + } + if (stype1 == SType::BOOL && stype2 == SType::BOOL) { + if (AND) { + return Column(new FuncBinary2_ColumnImpl( + std::move(lcol), std::move(rcol), + op_and_bool, + nrows, SType::BOOL + )); + } + return Column(new FuncBinary2_ColumnImpl( + std::move(lcol), std::move(rcol), + op_or_bool, + nrows, SType::BOOL + )); + } + switch (stype0) { + case SType::INT8: return make(std::move(lcol), std::move(rcol), stype0); + case SType::INT16: return make(std::move(lcol), std::move(rcol), stype0); + case SType::INT32: return make(std::move(lcol), std::move(rcol), stype0); + case SType::INT64: return make(std::move(lcol), std::move(rcol), stype0); + default: + char op = AND?'&':'|'; + throw TypeError() << "Operator " << op << " cannot be applied to columns of " + "types `" << stype1 << "` and `" << stype2 << "`"; + } + } + + private: + template + static Column make(Column&& a, Column&& b, SType stype) { + xassert(compatible_type(stype)); + size_t nrows = a.nrows(); + a.cast_inplace(stype); + b.cast_inplace(stype); + if (AND) { + return Column(new FuncBinary1_ColumnImpl( + std::move(a), std::move(b), + op_and, + nrows, stype + )); + } + return Column(new FuncBinary1_ColumnImpl( + std::move(a), std::move(b), + op_or, + nrows, stype + )); + } +}; + + +class FExpr__xor__ : public FExpr_BinaryOp { + public: + using FExpr_BinaryOp::FExpr_BinaryOp; + using FExpr_BinaryOp::lhs_; + using FExpr_BinaryOp::rhs_; + + + std::string name() const override { return "^"; } + int precedence() const noexcept override { return 8; } + + + Column evaluate1(Column&& lcol, Column&& rcol) const override { + xassert(lcol.nrows() == rcol.nrows()); + auto stype1 = lcol.stype(); + auto stype2 = rcol.stype(); + auto stype0 = common_stype(stype1, stype2); + + switch (stype0) { + case SType::BOOL: return make(std::move(lcol), std::move(rcol), stype0); + case SType::INT8: return make(std::move(lcol), std::move(rcol), stype0); + case SType::INT16: return make(std::move(lcol), std::move(rcol), stype0); + case SType::INT32: return make(std::move(lcol), std::move(rcol), stype0); + case SType::INT64: return make(std::move(lcol), std::move(rcol), stype0); + default: + throw TypeError() << "Operator `^` cannot be applied to columns of " + "types `" << stype1 << "` and `" << stype2 << "`"; + } + } + + private: + template + static Column make(Column&& a, Column&& b, SType stype) { + xassert(compatible_type(stype)); + size_t nrows = a.nrows(); + a.cast_inplace(stype); + b.cast_inplace(stype); + return Column(new FuncBinary1_ColumnImpl( + std::move(a), std::move(b), + op_xor, + nrows, stype + )); + } +}; + + +py::oobj PyFExpr::nb__and__(py::robj lhs, py::robj rhs) { + return PyFExpr::make( + new FExpr__andor__(as_fexpr(lhs), as_fexpr(rhs))); +} + +py::oobj PyFExpr::nb__or__(py::robj lhs, py::robj rhs) { + return PyFExpr::make( + new FExpr__andor__(as_fexpr(lhs), as_fexpr(rhs))); +} + +py::oobj PyFExpr::nb__xor__(py::robj lhs, py::robj rhs) { + return PyFExpr::make( + new FExpr__xor__(as_fexpr(lhs), as_fexpr(rhs))); +} + + + +}} // namespace dt::expr + + diff --git a/src/core/expr/fbinary/bitwise_shift.cc b/src/core/expr/fbinary/bitwise_shift.cc new file mode 100644 index 0000000000..25943d0306 --- /dev/null +++ b/src/core/expr/fbinary/bitwise_shift.cc @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +// Copyright 2020 H2O.ai +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//------------------------------------------------------------------------------ +#include "column/func_binary.h" +#include "expr/fbinary/fexpr_binaryop.h" + +namespace dt { +namespace expr { + +template +inline static T op_lshift(T x, int32_t y) { + return (y >= 0)? static_cast(x << y) : static_cast(x >> -y); +} + +template +inline static T op_rshift(T x, int32_t y) { + return (y >= 0)? static_cast(x >> y) : static_cast(x << -y); +} + + +template +class FExprBitShift : public FExpr_BinaryOp { + public: + using FExpr_BinaryOp::FExpr_BinaryOp; + using FExpr_BinaryOp::lhs_; + using FExpr_BinaryOp::rhs_; + + + std::string name() const override { return LSHIFT?"<<":">>"; } + int precedence() const noexcept override { return 10; } + + + Column evaluate1(Column&& lcol, Column&& rcol) const override { + xassert(lcol.nrows() == rcol.nrows()); + auto stype1 = lcol.stype(); + auto stype2 = rcol.stype(); + xassert(Type::from_stype(stype1).is_integer()); + xassert(Type::from_stype(stype1).is_integer() || Type::from_stype(stype1).is_boolean()); + + auto stype0 = (stype2 == SType::INT32)? SType::AUTO : SType::INT32; + + switch (stype1) { + case SType::INT8: return make(std::move(lcol), std::move(rcol), stype0); + case SType::INT16: return make(std::move(lcol), std::move(rcol), stype0); + case SType::INT32: return make(std::move(lcol), std::move(rcol), stype0); + case SType::INT64: return make(std::move(lcol), std::move(rcol), stype0); + default: + std::string op = LSHIFT?"<<":">>"; + throw TypeError() << "Operator " << op << " cannot be applied to columns of " + "types `" << stype1 << "` and `" << stype2 << "`"; + } + } + + private: + template + static Column make(Column&& a, Column&& b, SType stype) { + xassert(compatible_type(a.stype())); + size_t nrows = a.nrows(); + b.cast_inplace(stype); + if (L_SHIFT) { + return Column(new FuncBinary1_ColumnImpl( + std::move(a), std::move(b), + op_lshift, + nrows, a.stype() + )); + } + return Column(new FuncBinary1_ColumnImpl( + std::move(a), std::move(b), + op_rshift, + nrows, a.stype() + )); + } +}; + + +py::oobj PyFExpr::nb__lshift__(py::robj lhs, py::robj rhs) { + return PyFExpr::make( + new FExprBitShift(as_fexpr(lhs), as_fexpr(rhs))); +} + +py::oobj PyFExpr::nb__rshift__(py::robj lhs, py::robj rhs) { + return PyFExpr::make( + new FExprBitShift(as_fexpr(lhs), as_fexpr(rhs))); +} + +}} // namespace dt::expr + + + + diff --git a/src/core/expr/fexpr.cc b/src/core/expr/fexpr.cc index dfd59391cc..e3a07a3be0 100644 --- a/src/core/expr/fexpr.cc +++ b/src/core/expr/fexpr.cc @@ -178,19 +178,6 @@ static oobj make_unexpr(dt::expr::Op op, const PyObject* self) { otuple{oobj(self)}}); } -static oobj make_binexpr(dt::expr::Op op, robj lhs, robj rhs) { - return robj(Expr_Type).call({ - oint(static_cast(op)), - otuple{lhs, rhs}}); -} - - -oobj PyFExpr::nb__and__(robj lhs, robj rhs) { return make_binexpr(dt::expr::Op::AND, lhs, rhs); } -oobj PyFExpr::nb__xor__(robj lhs, robj rhs) { return make_binexpr(dt::expr::Op::XOR, lhs, rhs); } -oobj PyFExpr::nb__or__(robj lhs, robj rhs) { return make_binexpr(dt::expr::Op::OR, lhs, rhs); } -oobj PyFExpr::nb__lshift__(robj lhs, robj rhs) { return make_binexpr(dt::expr::Op::LSHIFT, lhs, rhs); } -oobj PyFExpr::nb__rshift__(robj lhs, robj rhs) { return make_binexpr(dt::expr::Op::RSHIFT, lhs, rhs); } - bool PyFExpr::nb__bool__() { throw TypeError() << "Expression " << expr_->repr() << " cannot be cast to bool.\n\n" diff --git a/src/core/expr/op.h b/src/core/expr/op.h index 6ca51b8f29..81ce2454b7 100644 --- a/src/core/expr/op.h +++ b/src/core/expr/op.h @@ -55,13 +55,6 @@ enum class Op : size_t { UMINUS, // funary/basic.cc UINVERT = UNOP_LAST, // funary/basic.cc - // Binary - AND = 208, // fbinary/bitwise.cc - XOR = 209, // fbinary/bitwise.cc - OR = 210, // fbinary/bitwise.cc - LSHIFT = 211, // fbinary/bitwise.cc - RSHIFT = 212, // fbinary/bitwise.cc - // Reducers MEAN = REDUCER_FIRST, // head_reduce_unary.cc MIN, // head_reduce_unary.cc diff --git a/src/datatable/expr/expr.py b/src/datatable/expr/expr.py index a91782d4b7..c52964fe47 100644 --- a/src/datatable/expr/expr.py +++ b/src/datatable/expr/expr.py @@ -43,13 +43,6 @@ class OpCodes(enum.Enum): UMINUS = 102 UINVERT = 103 - # Binary - AND = 208 - XOR = 209 - OR = 210 - LSHIFT = 211 - RSHIFT = 212 - # Reducers STDEV = 404 FIRST = 405 diff --git a/tests/test-dt-expr.py b/tests/test-dt-expr.py index 3f36ab1181..d17d2279ae 100644 --- a/tests/test-dt-expr.py +++ b/tests/test-dt-expr.py @@ -53,6 +53,9 @@ (dt.str64, dt.str32), (dt.str64, dt.str64)] +src_integers = random.choices(range(100_000), k = 50) + + #------------------------------------------------------------------------------- # logical ops @@ -114,9 +117,38 @@ def test_logical_or2(seed): None if (src1[i] is None or src2[i] is None) else False for i in range(n)] + +@pytest.mark.parametrize("seed", [random.getrandbits(63)]) +def test_logical_xor(seed): + random.seed(seed) + n = 1000 + src1 = [random.choice([1, 0]) for _ in range(n)] + src2 = [random.choice([1, 0]) for _ in range(n)] + df0 = dt.Frame(A=src1, B=src2) + df1 = df0[:, f.A ^ f.B] + assert df1.to_list()[0] == \ + [False if (src1[i] == src2[i]) else + True + for i in range(n)] +#------------------------------------------------------------------------------- +# Lshift/Rshift operators +#------------------------------------------------------------------------------- +def test_lshift(): + DT = dt.Frame({'C0': src_integers}) + RES = DT[:, f.C0<<2] + EXP = [entry<<2 for entry in src_integers] + assert RES.to_list()[0] == EXP + +def test_rshift(): + DT = dt.Frame({'C0': src_integers}) + RES = DT[:, f.C0>>2] + EXP = [entry>>2 for entry in src_integers] + assert RES.to_list()[0] == EXP + + #------------------------------------------------------------------------------- # Equality operators #-------------------------------------------------------------------------------