0

I need to find whether two finite floating point values A and B have different signs or one of them is zero.

In many code examples I see the test as follows:

if ( (A <= 0 && B >= 0) || (A >= 0 && B <= 0) )

It works fine but looks inefficient to me since many conditions are verified here and each condition branch is a slow operation for modern CPUs.

The other option is to use multiplication of the floating-point values:

if ( A * B <= 0 )

It shall work even in case of overflow, since infinity values preserve proper sign.

What is the most efficient way for performing the check (one of these two or probably some other)?

phuclv
  • 37,963
  • 15
  • 156
  • 475
Fedor
  • 17,146
  • 13
  • 40
  • 131
  • Could you just `XOR` the highest bit? Assuming you use `IEEE 754` – Vlad Feinstein Aug 12 '21 at 16:41
  • 2
    both incorrectly accept A=0, B=0 if I'm reading this correctly – Kenny Ostrom Aug 12 '21 at 16:46
  • Also, throw `if (std::copysign(A, B) != A)` into the tests (with optimisations set as high as possible), and check the assembly. – Bathsheba Aug 12 '21 at 17:04
  • What results do you want if NaNs are involved? – Eric Postpischil Aug 12 '21 at 17:18
  • @KennyOstrom: The question states the test is for “two finite floating-point values `A` and `B` have distinct signs or one of them is zero.” So accepting “A=0, B=0” is correct; one of them is zero. The only cases to be rejected are A and B are both negative (truly negative, not just −0) and both are positive (truly positive, not just +0), except we do not know what OP wants for NaNs. – Eric Postpischil Aug 12 '21 at 17:19
  • 3
    `A*B <= 0` is broken; if `A` and `B` are the same sign (and not zero) but sufficiently small that `A*B` produces zero due to underflow, then `A*B <= 0` will evaluate as true but should evaluate as false. – Eric Postpischil Aug 12 '21 at 17:28
  • 1
    Do you care about the cases when the distinct signs for numbers which [`std::fpclassify`](https://en.cppreference.com/w/cpp/numeric/math/fpclassify) is something other than `FP_NORMAL`? (Or is that what you meant by *two finite floating point values*, in which case you're already golden.) – Eljay Aug 12 '21 at 18:42
  • I do not consider infinite and NaN values in A and B, only finite ones and zeros. As to not-normalized values, it is nice if they are supported, but not strictly required. – Fedor Aug 14 '21 at 16:53

2 Answers2

3

Efficient check that two floating point values have distinct signs

if ( A * B <= 0 ) fails to distinguish the sign when A or B are of the set -0.0, +0.0.

Consider signbit()

if (signbit(A) == signbit(B)) {
  ; // same sign
} else  {
  ; // distinct signs
}

Trust the compiler to form efficient code - or use a better compiler.


OP then formed a different goal: "ether two finite floating point values A and B have distinct signs or one of them is zero."

Of course if (signbit(A) != signbit(B) || A == 0.0 || B == 0.0) meets this newer functionality without the rounding to 0.0 issue @Eric Postpischil of if ( A * B <= 0 ), but may not meet the fuzzy requirement of most efficient way.

The best way to form efficient code and avoid premature optimization really the root of all evil is to see the larger context.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 1
    Why is everybody trying to distinguish the signs of zeros? The question states the test is for “two finite floating-point values `A` and `B` have distinct signs or one of them is zero.” If A or B is zero, the test should evaluate as true, and `A*B <= 0` accomplishes that. – Eric Postpischil Aug 12 '21 at 17:23
  • 1
    @EricPostpischil "Why is everybody trying to distinguish the signs of zeros?" --> Likely because OP's title fundamentally differs from the question details. – chux - Reinstate Monica Aug 12 '21 at 17:24
  • 1
    Titles are terse. The question text spells it out, and it matches the code in the question. – Eric Postpischil Aug 12 '21 at 17:25
  • You asked "Why is everybody trying to distinguish the signs of zeros?". My comment addressed that. OP is trying to find a "most efficient way for performing the check" without posting the larger context. Yes `A*B <= 0` accomplishes the functionality of the sub-question - usually, but without seeing the larger code and goal, "most efficient way for performing the check" is not well answerable. – chux - Reinstate Monica Aug 12 '21 at 17:30
  • Thanks, it is really efficient. Here is assembly comparison of all methods: https://gcc.godbolt.org/z/73hPPvvz3 – Fedor Aug 16 '21 at 12:46
1

It works fine but looks inefficient to me since many conditions are verified here and each condition branch is a slow operation for modern CPUs.

An optimizing compiler won't branch unnecessarily. Don't worry about premature optimization unless you're in a very tight hot loop. Your first snippet is compiled to only 2 branches in x86-64 and ARM64 in the compilers I tried. It can also be compiled to a branchless version. Of course it may still be slower than a simple A * B <= 0 but there's no way to know that for sure without a proper benchmark

If you don't care about NaN then you can simply do some bitwise operations:

auto a = std::bit_cast<uint64_t>(A);
auto b = std::bit_cast<uint64_t>(B);
const auto sign_mask = 1ULL << 63;
return ((a ^ b) & sign_mask) || (((a | b) & ~sign_mask) == 0);

If A and B have different signs then it'll be matched by (a ^ b) & sign_mask. If they have same sign then they both must be zero which will be caught by the latter condition. But this works in the integer so it may incur a cross-domain penalty when moving the value from float to int domain

If std::bit_cast not available then just replace with memcpy(&a, &A, sizeof A)

Demo on Godbolt

Again, do a benchmark to determine what's best for your target. There's no solution that's fastest on every microarchitecture available. If you really run this a lot of times in a loop then you should use SIMD instead to check for multiple values at the same time. You should also use profile-guided optimization in order for the compiler to know where and when to branch

phuclv
  • 37,963
  • 15
  • 156
  • 475