Maybe<bool> Value::Equals(Local<Context> context, Local<Value> that) const {
auto self = Utils::OpenHandle(this);
auto other = Utils::OpenHandle(*that);
- if (self->IsSmi() && other->IsSmi()) {
- return Just(self->Number() == other->Number());
- }
- if (self->IsJSObject() && other->IsJSObject()) {
- return Just(*self == *other);
- }
- PREPARE_FOR_EXECUTION_PRIMITIVE(context, "v8::Value::Equals()", bool);
- i::Handle<i::Object> args[] = { other };
- i::Handle<i::JSFunction> fun = isolate->equals_builtin();
- i::Handle<i::Object> result;
- has_pending_exception =
- !i::Execution::Call(isolate, fun, self, arraysize(args), args)
- .ToHandle(&result);
- RETURN_ON_FAILED_EXECUTION_PRIMITIVE(bool);
- return Just(*result == i::Smi::FromInt(i::EQUAL));
+ return i::Object::Equals(self, other);
}
__ Push(lhs, rhs);
// Figure out which native to call and setup the arguments.
- if (cc == eq && strict()) {
- __ TailCallRuntime(Runtime::kStrictEquals, 2, 1);
+ if (cc == eq) {
+ __ TailCallRuntime(strict() ? Runtime::kStrictEquals : Runtime::kEquals, 2,
+ 1);
} else {
- int context_index;
- if (cc == eq) {
- context_index = Context::EQUALS_BUILTIN_INDEX;
+ int context_index = is_strong(strength())
+ ? Context::COMPARE_STRONG_BUILTIN_INDEX
+ : Context::COMPARE_BUILTIN_INDEX;
+ int ncr; // NaN compare result
+ if (cc == lt || cc == le) {
+ ncr = GREATER;
} else {
- context_index = is_strong(strength())
- ? Context::COMPARE_STRONG_BUILTIN_INDEX
- : Context::COMPARE_BUILTIN_INDEX;
- int ncr; // NaN compare result
- if (cc == lt || cc == le) {
- ncr = GREATER;
- } else {
- DCHECK(cc == gt || cc == ge); // remaining cases
- ncr = LESS;
- }
- __ mov(r0, Operand(Smi::FromInt(ncr)));
- __ push(r0);
+ DCHECK(cc == gt || cc == ge); // remaining cases
+ ncr = LESS;
}
+ __ mov(r0, Operand(Smi::FromInt(ncr)));
+ __ push(r0);
// Call the native; it returns -1 (less), 0 (equal), or 1 (greater)
// tagged as a small integer.
__ Push(lhs, rhs);
// Figure out which native to call and setup the arguments.
- if (cond == eq && strict()) {
- __ TailCallRuntime(Runtime::kStrictEquals, 2, 1);
+ if (cond == eq) {
+ __ TailCallRuntime(strict() ? Runtime::kStrictEquals : Runtime::kEquals, 2,
+ 1);
} else {
- int context_index;
- if (cond == eq) {
- context_index = Context::EQUALS_BUILTIN_INDEX;
+ int context_index = is_strong(strength())
+ ? Context::COMPARE_STRONG_BUILTIN_INDEX
+ : Context::COMPARE_BUILTIN_INDEX;
+ int ncr; // NaN compare result
+ if ((cond == lt) || (cond == le)) {
+ ncr = GREATER;
} else {
- context_index = is_strong(strength())
- ? Context::COMPARE_STRONG_BUILTIN_INDEX
- : Context::COMPARE_BUILTIN_INDEX;
- int ncr; // NaN compare result
- if ((cond == lt) || (cond == le)) {
- ncr = GREATER;
- } else {
- DCHECK((cond == gt) || (cond == ge)); // remaining cases
- ncr = LESS;
- }
- __ Mov(x10, Smi::FromInt(ncr));
- __ Push(x10);
+ DCHECK((cond == gt) || (cond == ge)); // remaining cases
+ ncr = LESS;
}
+ __ Mov(x10, Smi::FromInt(ncr));
+ __ Push(x10);
// Call the native; it returns -1 (less), 0 (equal), or 1 (greater)
// tagged as a small integer.
V(COMPARE_STRONG_BUILTIN_INDEX, JSFunction, compare_strong_builtin) \
V(CONCAT_ITERABLE_TO_ARRAY_BUILTIN_INDEX, JSFunction, \
concat_iterable_to_array_builtin) \
- V(EQUALS_BUILTIN_INDEX, JSFunction, equals_builtin) \
V(REFLECT_APPLY_PREPARE_BUILTIN_INDEX, JSFunction, \
reflect_apply_prepare_builtin) \
V(REFLECT_CONSTRUCT_PREPARE_BUILTIN_INDEX, JSFunction, \
__ push(eax);
// Figure out which native to call and setup the arguments.
- if (cc == equal && strict()) {
+ if (cc == equal) {
__ push(ecx);
- __ TailCallRuntime(Runtime::kStrictEquals, 2, 1);
+ __ TailCallRuntime(strict() ? Runtime::kStrictEquals : Runtime::kEquals, 2,
+ 1);
} else {
- int native_context_index;
- if (cc == equal) {
- native_context_index = Context::EQUALS_BUILTIN_INDEX;
- } else {
- native_context_index = is_strong(strength())
- ? Context::COMPARE_STRONG_BUILTIN_INDEX
- : Context::COMPARE_BUILTIN_INDEX;
- __ push(Immediate(Smi::FromInt(NegativeComparisonResult(cc))));
- }
+ int native_context_index = is_strong(strength())
+ ? Context::COMPARE_STRONG_BUILTIN_INDEX
+ : Context::COMPARE_BUILTIN_INDEX;
+ __ push(Immediate(Smi::FromInt(NegativeComparisonResult(cc))));
// Restore return address on the stack.
__ push(ecx);
// a1 (rhs) second.
__ Push(lhs, rhs);
// Figure out which native to call and setup the arguments.
- if (cc == eq && strict()) {
- __ TailCallRuntime(Runtime::kStrictEquals, 2, 1);
+ if (cc == eq) {
+ __ TailCallRuntime(strict() ? Runtime::kStrictEquals : Runtime::kEquals, 2,
+ 1);
} else {
- int context_index;
- if (cc == eq) {
- context_index = Context::EQUALS_BUILTIN_INDEX;
+ int context_index = is_strong(strength())
+ ? Context::COMPARE_STRONG_BUILTIN_INDEX
+ : Context::COMPARE_BUILTIN_INDEX;
+ int ncr; // NaN compare result.
+ if (cc == lt || cc == le) {
+ ncr = GREATER;
} else {
- context_index = is_strong(strength())
- ? Context::COMPARE_STRONG_BUILTIN_INDEX
- : Context::COMPARE_BUILTIN_INDEX;
- int ncr; // NaN compare result.
- if (cc == lt || cc == le) {
- ncr = GREATER;
- } else {
- DCHECK(cc == gt || cc == ge); // Remaining cases.
- ncr = LESS;
- }
- __ li(a0, Operand(Smi::FromInt(ncr)));
- __ push(a0);
+ DCHECK(cc == gt || cc == ge); // Remaining cases.
+ ncr = LESS;
}
+ __ li(a0, Operand(Smi::FromInt(ncr)));
+ __ push(a0);
// Call the native; it returns -1 (less), 0 (equal), or 1 (greater)
// tagged as a small integer.
// a1 (rhs) second.
__ Push(lhs, rhs);
// Figure out which native to call and setup the arguments.
- if (cc == eq && strict()) {
- __ TailCallRuntime(Runtime::kStrictEquals, 2, 1);
+ if (cc == eq) {
+ __ TailCallRuntime(strict() ? Runtime::kStrictEquals : Runtime::kEquals, 2,
+ 1);
} else {
- int context_index;
- if (cc == eq) {
- context_index = Context::EQUALS_BUILTIN_INDEX;
+ int context_index = is_strong(strength())
+ ? Context::COMPARE_STRONG_BUILTIN_INDEX
+ : Context::COMPARE_BUILTIN_INDEX;
+ int ncr; // NaN compare result.
+ if (cc == lt || cc == le) {
+ ncr = GREATER;
} else {
- context_index = is_strong(strength())
- ? Context::COMPARE_STRONG_BUILTIN_INDEX
- : Context::COMPARE_BUILTIN_INDEX;
- int ncr; // NaN compare result.
- if (cc == lt || cc == le) {
- ncr = GREATER;
- } else {
- DCHECK(cc == gt || cc == ge); // Remaining cases.
- ncr = LESS;
- }
- __ li(a0, Operand(Smi::FromInt(ncr)));
- __ push(a0);
+ DCHECK(cc == gt || cc == ge); // Remaining cases.
+ ncr = LESS;
}
+ __ li(a0, Operand(Smi::FromInt(ncr)));
+ __ push(a0);
// Call the native; it returns -1 (less), 0 (equal), or 1 (greater)
// tagged as a small integer.
}
-double Object::Number() {
+double Object::Number() const {
DCHECK(IsNumber());
return IsSmi()
- ? static_cast<double>(reinterpret_cast<Smi*>(this)->value())
- : reinterpret_cast<HeapNumber*>(this)->value();
+ ? static_cast<double>(reinterpret_cast<const Smi*>(this)->value())
+ : reinterpret_cast<const HeapNumber*>(this)->value();
}
}
+// static
+bool Simd128Value::Equals(Handle<Simd128Value> one, Handle<Simd128Value> two) {
+ return one->Equals(*two);
+}
+
+
#define SIMD128_VALUE_EQUALS(TYPE, Type, type, lane_count, lane_type) \
bool Type::Equals(Type* that) { \
for (int lane = 0; lane < lane_count; ++lane) { \
}
+// static
+Handle<Object> Oddball::ToNumber(Handle<Oddball> input) {
+ return handle(input->to_number(), input->GetIsolate());
+}
+
+
ACCESSORS(Cell, value, Object, kValueOffset)
ACCESSORS(PropertyCell, dependent_code, DependentCode, kDependentCodeOffset)
ACCESSORS(PropertyCell, property_details_raw, Object, kDetailsOffset)
if (input->IsNumber()) {
return input;
}
- Isolate* const isolate = Handle<HeapObject>::cast(input)->GetIsolate();
- if (input->IsOddball()) {
- return handle(Handle<Oddball>::cast(input)->to_number(), isolate);
- }
if (input->IsString()) {
return String::ToNumber(Handle<String>::cast(input));
}
+ if (input->IsOddball()) {
+ return Oddball::ToNumber(Handle<Oddball>::cast(input));
+ }
+ Isolate* const isolate = Handle<HeapObject>::cast(input)->GetIsolate();
if (input->IsSymbol()) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kSymbolToNumber),
Object);
}
+namespace {
+
+// TODO(bmeurer): Maybe we should introduce a marker interface Number,
+// where we put all these methods at some point?
+bool NumberEquals(double x, double y) {
+ // Must check explicitly for NaN's on Windows, but -0 works fine.
+ if (std::isnan(x)) return false;
+ if (std::isnan(y)) return false;
+ return x == y;
+}
+
+
+bool NumberEquals(const Object* x, const Object* y) {
+ return NumberEquals(x->Number(), y->Number());
+}
+
+
+bool NumberEquals(Handle<Object> x, Handle<Object> y) {
+ return NumberEquals(*x, *y);
+}
+
+} // namespace
+
+
+// static
+Maybe<bool> Object::Equals(Handle<Object> x, Handle<Object> y) {
+ while (true) {
+ if (x->IsNumber()) {
+ if (y->IsNumber()) {
+ return Just(NumberEquals(x, y));
+ } else if (y->IsBoolean()) {
+ return Just(NumberEquals(*x, Handle<Oddball>::cast(y)->to_number()));
+ } else if (y->IsString()) {
+ return Just(NumberEquals(x, String::ToNumber(Handle<String>::cast(y))));
+ } else if (y->IsJSReceiver() && !y->IsUndetectableObject()) {
+ if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(y))
+ .ToHandle(&y)) {
+ return Nothing<bool>();
+ }
+ } else {
+ return Just(false);
+ }
+ } else if (x->IsString()) {
+ if (y->IsString()) {
+ return Just(
+ String::Equals(Handle<String>::cast(x), Handle<String>::cast(y)));
+ } else if (y->IsNumber()) {
+ x = String::ToNumber(Handle<String>::cast(x));
+ return Just(NumberEquals(x, y));
+ } else if (y->IsBoolean()) {
+ x = String::ToNumber(Handle<String>::cast(x));
+ return Just(NumberEquals(*x, Handle<Oddball>::cast(y)->to_number()));
+ } else if (y->IsJSReceiver() && !y->IsUndetectableObject()) {
+ if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(y))
+ .ToHandle(&y)) {
+ return Nothing<bool>();
+ }
+ } else {
+ return Just(false);
+ }
+ } else if (x->IsBoolean()) {
+ if (y->IsOddball()) {
+ return Just(x.is_identical_to(y));
+ } else if (y->IsNumber()) {
+ return Just(NumberEquals(Handle<Oddball>::cast(x)->to_number(), *y));
+ } else if (y->IsString()) {
+ y = String::ToNumber(Handle<String>::cast(y));
+ return Just(NumberEquals(Handle<Oddball>::cast(x)->to_number(), *y));
+ } else if (y->IsJSReceiver() && !y->IsUndetectableObject()) {
+ if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(y))
+ .ToHandle(&y)) {
+ return Nothing<bool>();
+ }
+ x = Oddball::ToNumber(Handle<Oddball>::cast(x));
+ } else {
+ return Just(false);
+ }
+ } else if (x->IsSymbol()) {
+ return Just(x.is_identical_to(y));
+ } else if (x->IsSimd128Value()) {
+ if (!y->IsSimd128Value()) return Just(false);
+ return Just(Simd128Value::Equals(Handle<Simd128Value>::cast(x),
+ Handle<Simd128Value>::cast(y)));
+ } else if (x->IsJSReceiver() && !x->IsUndetectableObject()) {
+ if (y->IsJSReceiver()) {
+ return Just(x.is_identical_to(y));
+ } else if (y->IsNull() || y->IsSimd128Value() || y->IsSymbol() ||
+ y->IsUndefined()) {
+ return Just(false);
+ } else if (y->IsBoolean()) {
+ y = Oddball::ToNumber(Handle<Oddball>::cast(y));
+ }
+ if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(x)).ToHandle(&x)) {
+ return Nothing<bool>();
+ }
+ } else {
+ return Just(
+ (x->IsNull() || x->IsUndefined() || x->IsUndetectableObject()) &&
+ (y->IsNull() || y->IsUndefined() || y->IsUndetectableObject()));
+ }
+ }
+}
+
+
bool Object::StrictEquals(Object* that) {
if (this->IsNumber()) {
if (!that->IsNumber()) return false;
- double const x = this->Number();
- double const y = that->Number();
- // Must check explicitly for NaN:s on Windows, but -0 works fine.
- return x == y && !std::isnan(x) && !std::isnan(y);
+ return NumberEquals(this, that);
} else if (this->IsString()) {
if (!that->IsString()) return false;
return String::cast(this)->Equals(String::cast(that));
INLINE(bool IsFiller() const);
// Extract the number.
- inline double Number();
+ inline double Number() const;
INLINE(bool IsNaN() const);
INLINE(bool IsMinusZero() const);
bool ToInt32(int32_t* value);
bool BooleanValue(); // ECMA-262 9.2.
+ // ES6 section 7.2.12 Abstract Equality Comparison
+ MUST_USE_RESULT static Maybe<bool> Equals(Handle<Object> x, Handle<Object> y);
+
// ES6 section 7.2.13 Strict Equality Comparison
bool StrictEquals(Object* that);
// Equality operations.
inline bool Equals(Simd128Value* that);
+ static inline bool Equals(Handle<Simd128Value> one, Handle<Simd128Value> two);
// Checks that another instance is bit-wise equal.
bool BitwiseEquals(const Simd128Value* other) const;
inline byte kind() const;
inline void set_kind(byte kind);
+ // ES6 section 7.1.3 ToNumber for Boolean, Null, Undefined.
+ MUST_USE_RESULT static inline Handle<Object> ToNumber(Handle<Oddball> input);
+
DECLARE_CAST(Oddball)
// Dispatched behavior.
-----------------------------------
*/
-// ECMA-262 Section 11.9.3.
-function EQUALS(y) {
- if (IS_STRING(this) && IS_STRING(y)) return %StringEquals(this, y);
- var x = this;
-
- while (true) {
- if (IS_NUMBER(x)) {
- while (true) {
- if (IS_NUMBER(y)) return %NumberEquals(x, y);
- if (IS_NULL_OR_UNDEFINED(y)) return 1; // not equal
- if (!IS_SPEC_OBJECT(y)) {
- if (IS_SYMBOL(y) || IS_SIMD_VALUE(y)) return 1; // not equal
- // String or boolean.
- return %NumberEquals(x, %to_number_fun(y));
- }
- y = %to_primitive(y, NO_HINT);
- }
- } else if (IS_STRING(x)) {
- while (true) {
- if (IS_STRING(y)) return %StringEquals(x, y);
- if (IS_NUMBER(y)) return %NumberEquals(%to_number_fun(x), y);
- if (IS_BOOLEAN(y)) {
- return %NumberEquals(%to_number_fun(x), %to_number_fun(y));
- }
- if (IS_NULL_OR_UNDEFINED(y)) return 1; // not equal
- if (IS_SYMBOL(y) || IS_SIMD_VALUE(y)) return 1; // not equal
- y = %to_primitive(y, NO_HINT);
- }
- } else if (IS_SYMBOL(x)) {
- if (IS_SYMBOL(y)) return %_ObjectEquals(x, y) ? 0 : 1;
- return 1; // not equal
- } else if (IS_BOOLEAN(x)) {
- if (IS_BOOLEAN(y)) return %_ObjectEquals(x, y) ? 0 : 1;
- if (IS_NULL_OR_UNDEFINED(y)) return 1;
- if (IS_NUMBER(y)) return %NumberEquals(%to_number_fun(x), y);
- if (IS_STRING(y)) {
- return %NumberEquals(%to_number_fun(x), %to_number_fun(y));
- }
- if (IS_SYMBOL(y) || IS_SIMD_VALUE(y)) return 1; // not equal
- // y is object.
- x = %to_number_fun(x);
- y = %to_primitive(y, NO_HINT);
- } else if (IS_NULL_OR_UNDEFINED(x)) {
- return IS_NULL_OR_UNDEFINED(y) ? 0 : 1;
- } else if (IS_SIMD_VALUE(x)) {
- if (!IS_SIMD_VALUE(y)) return 1; // not equal
- return %SimdEquals(x, y);
- } else {
- // x is an object.
- if (IS_SPEC_OBJECT(y)) return %_ObjectEquals(x, y) ? 0 : 1;
- if (IS_NULL_OR_UNDEFINED(y)) return 1; // not equal
- if (IS_BOOLEAN(y)) {
- y = %to_number_fun(y);
- } else if (IS_SYMBOL(y) || IS_SIMD_VALUE(y)) {
- return 1; // not equal
- }
- x = %to_primitive(x, NO_HINT);
- }
- }
-}
-
-
// ECMA-262, section 11.8.5, page 53. The 'ncr' parameter is used as
// the result when either (or both) the operands are NaN.
function COMPARE(x, ncr) {
"compare_builtin", COMPARE,
"compare_strong_builtin", COMPARE_STRONG,
"concat_iterable_to_array_builtin", CONCAT_ITERABLE_TO_ARRAY,
- "equals_builtin", EQUALS,
"reflect_apply_prepare_builtin", REFLECT_APPLY_PREPARE,
"reflect_construct_prepare_builtin", REFLECT_CONSTRUCT_PREPARE,
]);
#ifndef _STLP_VENDOR_CSTD
-// STLPort doesn't import fpclassify and isless into the std namespace.
-using std::fpclassify;
+// STLPort doesn't import isless into the std namespace.
using std::isless;
#endif
}
-RUNTIME_FUNCTION(Runtime_NumberEquals) {
- SealHandleScope shs(isolate);
- DCHECK(args.length() == 2);
-
- CONVERT_DOUBLE_ARG_CHECKED(x, 0);
- CONVERT_DOUBLE_ARG_CHECKED(y, 1);
- if (std::isnan(x)) return Smi::FromInt(NOT_EQUAL);
- if (std::isnan(y)) return Smi::FromInt(NOT_EQUAL);
- if (x == y) return Smi::FromInt(EQUAL);
- Object* result;
- if ((fpclassify(x) == FP_ZERO) && (fpclassify(y) == FP_ZERO)) {
- result = Smi::FromInt(EQUAL);
- } else {
- result = Smi::FromInt(NOT_EQUAL);
- }
- return result;
-}
-
-
RUNTIME_FUNCTION(Runtime_NumberCompare) {
SealHandleScope shs(isolate);
DCHECK(args.length() == 3);
DCHECK(args.length() == 0);
return isolate->heap()->nan_value();
}
+
} // namespace internal
} // namespace v8
}
+RUNTIME_FUNCTION(Runtime_Equals) {
+ HandleScope scope(isolate);
+ DCHECK_EQ(2, args.length());
+ CONVERT_ARG_HANDLE_CHECKED(Object, x, 0);
+ CONVERT_ARG_HANDLE_CHECKED(Object, y, 1);
+ Maybe<bool> result = Object::Equals(x, y);
+ if (!result.IsJust()) return isolate->heap()->exception();
+ // TODO(bmeurer): Change this at some point to return true/false instead.
+ return Smi::FromInt(result.FromJust() ? EQUAL : NOT_EQUAL);
+}
+
+
RUNTIME_FUNCTION(Runtime_StrictEquals) {
SealHandleScope scope(isolate);
DCHECK_EQ(2, args.length());
}
-RUNTIME_FUNCTION(Runtime_SimdToObject) {
- HandleScope scope(isolate);
- DCHECK(args.length() == 1);
- CONVERT_ARG_HANDLE_CHECKED(Simd128Value, value, 0);
- return *Object::ToObject(isolate, value).ToHandleChecked();
-}
-
-
-RUNTIME_FUNCTION(Runtime_SimdEquals) {
- SealHandleScope scope(isolate);
- DCHECK_EQ(2, args.length());
- CONVERT_ARG_CHECKED(Simd128Value, x, 0);
- CONVERT_ARG_CHECKED(Simd128Value, y, 1);
- return Smi::FromInt(x->Equals(y) ? EQUAL : NOT_EQUAL);
-}
-
-
RUNTIME_FUNCTION(Runtime_SimdSameValue) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
F(NumberToIntegerMapMinusZero, 1, 1) \
F(NumberToSmi, 1, 1) \
F(NumberImul, 2, 1) \
- F(NumberEquals, 2, 1) \
F(NumberCompare, 3, 1) \
F(SmiLexicographicCompare, 2, 1) \
F(MaxSmi, 0, 1) \
F(ToNumber, 1, 1) \
F(ToString, 1, 1) \
F(ToName, 1, 1) \
+ F(Equals, 2, 1) \
F(StrictEquals, 2, 1) \
F(InstanceOf, 2, 1) \
F(HasInPrototypeChain, 2, 1) \
#define FOR_EACH_INTRINSIC_SIMD(F) \
F(IsSimdValue, 1, 1) \
- F(SimdToObject, 1, 1) \
- F(SimdEquals, 2, 1) \
F(SimdSameValue, 2, 1) \
F(SimdSameValueZero, 2, 1) \
F(CreateFloat32x4, 4, 1) \
__ Push(rax);
// Figure out which native to call and setup the arguments.
- if (cc == equal && strict()) {
+ if (cc == equal) {
__ PushReturnAddressFrom(rcx);
- __ TailCallRuntime(Runtime::kStrictEquals, 2, 1);
+ __ TailCallRuntime(strict() ? Runtime::kStrictEquals : Runtime::kEquals, 2,
+ 1);
} else {
- int context_index;
- if (cc == equal) {
- context_index = Context::EQUALS_BUILTIN_INDEX;
- } else {
- context_index = is_strong(strength())
- ? Context::COMPARE_STRONG_BUILTIN_INDEX
- : Context::COMPARE_BUILTIN_INDEX;
- __ Push(Smi::FromInt(NegativeComparisonResult(cc)));
- }
-
+ int context_index = is_strong(strength())
+ ? Context::COMPARE_STRONG_BUILTIN_INDEX
+ : Context::COMPARE_BUILTIN_INDEX;
+ __ Push(Smi::FromInt(NegativeComparisonResult(cc)));
__ PushReturnAddressFrom(rcx);
// Call the native; it returns -1 (less), 0 (equal), or 1 (greater)