5

A straight forward way would be

uint32_t diff = abs(num1 - num2);
bool isZeroOrOne = (diff == 0 || diff == 1);

or to simply check for all possible cases:

int32_t diff = num1 - num2;
bool isZeroOrOne = (diff == 0 || diff == 1 || diff == -1);

Is there a more optimized way?

mad0x60
  • 312
  • 1
  • 14
  • 6
    `(uint32_t)(num1 - num2 + 1) <= 2` – Raymond Chen May 27 '23 at 13:30
  • 4
    Are you actually having a performance issue? Only start optimizing something after profiling your whole code. – Pepijn Kramer May 27 '23 at 13:30
  • 2
    Both your solutions are correct only for a particular range of num1 and num2, because either they can cause signed int overflow, or you take abs of INT_MIN -- both of which are undefined behavior. – Paul Hankin May 27 '23 at 13:37
  • 3
    @PepijnKramer: “Only start optimizing something after profiling your whole code” is bad advice. Optimization should begin in design, because choice of algorithm can have a huge affect on performance. Low-level optimizations such as the one OP asks about can come later, but optimization generally is something that should be considered throughout a project. – Eric Postpischil May 27 '23 at 13:47
  • First, achieve correctness and then measure performance. Most of the time, the compiler will do better than you on low-level operations. – RandomBits May 27 '23 at 13:48
  • A correct solution for all a, b of the same integer type: `a==b || a – Paul Hankin May 27 '23 at 13:55
  • 2
    @EricPostpischil I stand by my advice about bits of code. But yes I agree, design is important. But then it is design at the interface/decomposition level. So you can unit (and performance) test individual parts (and modify internal datastructures/algorithms later). Correctness should come first. – Pepijn Kramer May 27 '23 at 13:56
  • @RaymondChen: to avoid signed arithmetics overflow, you should write `(uint32_t)num1 - (uint32_t)num2 + 1) <= 2` – chqrlie Jun 09 '23 at 17:55
  • @chqrlie: That fails for `num1` = `INT_MIN`, `num2` = `INT_MAX`, as `(uint32_t)num1 - (uint32_t)num2` then produces 1, so `(uint32_t)num1 - (uint32_t)num2 + 1)` is 2, and `(uint32_t)num1 - (uint32_t)num2 + 1) <= 2` evaluates as true, but the correct result is false. – Eric Postpischil Jun 09 '23 at 18:00
  • @EricPostpischil: fixing this corner case is actually easy by switching to 64-bit arithmetics, as posted in my answer. – chqrlie Jun 09 '23 at 18:36
  • @chqrlie: Yes, I already voted up. The code Clang generates sign extends, adds 1, subtracts, clears `%eax`, compares, and uses `setb` to set a bit in `%eax` if appropriate. And that is the same code it generates for `int64_t d = (int64_t) n1 - (int64_t) n2; return -1 <= d && d <= 1;`, so the compiler is already implementing the optimization of the compare. It’s a shame not to have an efficient workaround for converting to 64 bits. – Eric Postpischil Jun 09 '23 at 18:40
  • @EricPostpischil: by *efficient workaround*, do you mean without explicit casts? My expression `return 1ULL + num1 - num2 <= 2;` is somewhat cryptic but less clunky :) – chqrlie Jun 09 '23 at 18:45
  • @chqrlie: I mean I want assembly instructions that provide the desired result efficiently. – Eric Postpischil Jun 09 '23 at 19:03

6 Answers6

7

Compilers already knows optimization techniques for range comparison like this, no need to do anything fancy and just use this

int32_t diff = uint32_t(num1) - uint32_t(num2);
bool isZeroOrOne = diff >= -1 && diff <= 1;

The compiler will optimize into a single comparison (uint32_t)(num1 - num2 + 1) <= 2 as Raymond Chen commented

The cast to unsigned is required to avoid undefined behavior due to integer overflow. Or use -fwrapv in supported compilers

Demo on Godbolt. As you can see compilers compile the below function into exactly the same instructions

#include <cstdint>

bool diff1(int32_t num1, int32_t num2)
{
    int32_t diff = uint32_t(num1) - uint32_t(num2);
    return diff >= -1 && diff <= 1;
}

bool diff2(int32_t num1, int32_t num2)
{
    int32_t diff = uint32_t(num1) - uint32_t(num2);
    return (uint32_t)(diff - (-1)) <= (1 - (-1));
}
phuclv
  • 37,963
  • 15
  • 156
  • 475
  • 6
    It's an edge case, but `diff1(INT32_MAX, INT32_MIN)` returns true... – Paul Hankin May 27 '23 at 13:50
  • I'm curious why you wrote diff2 like that and not `(uint32_t)(diff+1) <= 2`. I note that `diff2` still has undefined behavior when `diff` is `INT_MAX`, for example `diff2(INT_MAX, 0)` (assuming 32-bit int). – Paul Hankin May 27 '23 at 13:59
  • 3
    `uint32_t diff = (uint32_t)num1 - (uint32_t)num2; return diff+1 <= 2;` is a better implementation that avoids undefined and implementation defined behavior, but still gets the `INT_MIN` vs `INT_MAX` case wrong. – Paul Hankin May 27 '23 at 14:05
  • @PaulHankin it's how the expression is tranformed. `1 - (-1)` is how it worked out the `2` value. I'm thinking how to fix this case. Probably it's tricky because of the absolute value, unlike the normal comparison which clamps only one way – phuclv May 27 '23 at 14:46
  • @phuclv I don't think it's possible to deal with overflow or comparing `INT32_MAX` and `INT32_MIN` like this without explicitly checking for those values and/or overflow. – Andrew Henle May 29 '23 at 00:13
  • 2
    This relies on implementation-defined behaviour , as the result of the subtraction might be out of range of `int32_t` – M.M Jun 06 '23 at 04:01
  • @PaulHankin, that's because in our computer science unsigned round numberline, they _are_ only one value apart! :) – Gabriel Staples Jun 10 '23 at 02:08
  • 1
    Using uint32_t for the subtract doesn't help much, since when the result is converted back to int32_t, if it is out-of-range, the result is implementation defined. So on a 1s complement machine it would incorrectly return true for diff1(1, 3), and on a sign-magnitude, would incorrectly return false for diff1(1,2). Or they might cause a trap, if that is what the implementation does for out-of-range unsigned to signed conversions – Chris Dodd Jun 10 '23 at 02:45
4

Here is a simple solution that

  • has defined behavior;
  • works for all int32_t values including INT32_MIN and INT32_MAX;
  • generates branchless code, as can be verified on this Godbolt comparison.
int check_adjacent(int32_t num1, int32_t num2) {
    return 1ULL + num1 - num2 <= 2;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
0

this behaves correctly for INT_MIN - INT_MAX, INT_MIN+1 - INT_MIN, 1 - 0, 0 - 1 etc.

bool bTrueIfDiffOneOrZero( int32_t n32_1, int32_t n32_2 ) {
    int64_t bIs1or0, d, n1 = n32_1, n2 = n32_2;
    bIs1or0 = (d = n1 - n2) ? ( d == 1 || d == -1 ? 1 : 0) : 1;
    return bIs1or0;
}

there are other changes that can be done like not having the bIs1or0, just return the expression. as far as comparing assembly that is a new skill for me. it is interesting, and now for unknown reasons i am getting the predicted overflow, so i concede on that point. i am in the camp that its a mistake to try and optimize something like this so i will not attempt to compare the above answer with the others at the assembler level. thanks for your comments.

SpaceCadet
  • 25
  • 4
  • thanks Eric, i will fix it and edit accordingly – SpaceCadet Jun 09 '23 at 16:27
  • this is a problem with stack overflow design, i fixed the code but erics comment remains, which is fine, it is historically accurate, but confusing to others. someone did undemerit the answer which is cool! i don't have a better answer for the stack overflow design, which is is pretty good in general. but that is why i deleted my other answers, to get a zero demerits answer posted. – SpaceCadet Jun 10 '23 at 01:48
0

in spite of my comments about optimization i did it anyway, at least a subset. to optimize code you need to include the context. in our case here, what are the statistical frequencies of the two arguments n32_1 and n32_2? the optimal solution will deal with the most frequent case first, which has led me to the optimal answer.

i am showing the optimal solution for the case where both arguments are random in the range INT_MIN through INT_MAX inclusive. brave words considering how flawless my track record is to date.

i assert the best thing to do first is to take the difference of the two arguments. then discover the most common case, which is they do NOT fall in the interesting space. that leads to the answer quite nicely:

int64_t d = (int64_t)n32_1 - (int64_t)n32_2;
if (d > 1 || d < -1) 
    return 0;
return 1;

now whether using if or ? is faster i do not know. as chqrlie's solution shows, there is a better way to do this that doesn't have to consider anything about the input probabilities and produces much less assembler code to get the job done. i would write it this way return (uint64_t)1 + n32_1 - n32_2 <= 2; it is amazing how much strange assembler code is generated when you promote the int32 to int64 instead of promoting it to uint64.

SpaceCadet
  • 25
  • 4
  • 1
    chqrlie has the right answer above. using the 1ULL is the key, it promotes the two arguments to 64bit unsigned. that is the promotion i was almost getting in my earlier attempts. – SpaceCadet Jun 10 '23 at 02:55
-2

Your question means you have either two successive or two identical numbers. If one is even, the other has to be odd to fulfil returning 1. Have a look at this conditional solution.

bool res = ((num1%2) && (!(num2%2))) ?
    true : ((!(num1%2)) && (num2%2))) ? true : false;

Cheers.

  • This will return true if one number is even and the other is odd even if the difference between them is more than 1 – mad0x60 May 28 '23 at 13:46
  • Why would it then throw 1 or 0 (as you asked for) if the two numbers' difference is more than one or zero? – Iman Abdollahzadeh May 28 '23 at 13:57
  • I tried to answer **YOUR** question nothing more in general. Interpretating what you wrote means what I said. – Iman Abdollahzadeh May 28 '23 at 15:58
  • Hmm, this implementation has both conditional branching *and* modulo operations. I doubt it will be faster than the naive implementation (not that I suspect anyone will be able to measure any performance distance either way) – Jeremy Friesner Jun 06 '23 at 03:27
  • @JeremyFriesner a good compiler will convert `%2` to a bit masking operation, so it's not as bad as it looks. Still I don't see how this could be a correct solution, much less faster. – Mark Ransom Jun 07 '23 at 02:57
  • @JeremyFriesner as Mark Ransom said, I could have mentioned bitwise masking but it was still less readable. I think this solution has less overhead than converting two ```uint32``` to ```int32``` – Iman Abdollahzadeh Jun 10 '23 at 07:24
  • @ImanAbdollahzadeh AFAIK converting a `uint32` to `int32` has *zero* overhead; it doesn't even require copying the 4 bytes of RAM that represent the value to a separate location, since the two representations are bitwise-identical. All it does is change which integer-handling opcodes the compiler emits to handle that value (from instructions that are appropriate for unsigned math, to instructions that are appropriate for signed math). – Jeremy Friesner Jun 10 '23 at 14:04
-2

This works for any two int32_t n1 and n2 including INT32_MAX, INT32_MIN, no overflow. the return value is bIs1or0.

double d, bIs1or0 = (d = n1 - n2) ? ( d == 1 ? 1 : 0) : 1;

SpaceCadet
  • 25
  • 4
  • 1
    *This works for any two int32_t including INT32_MAX, INT32_MIN, no overflow.* Wrong. What about `n1 - n2`? – Andrew Henle Jun 06 '23 at 19:50
  • 3
    You have posted three wrong answers (and deleted two of them, so far). To start with, an answer that just shows code without explanation is usually not a good answer. It ought to explain the code. Not only would that help readers understand the answer (if it is correct), it would help people explain why you are on the wrong track. `n1 - n2` is wrong from the start. For some inputs, it will overflow, and assigning it to a `double` will not fix that, because the conversion to `double` does not happen before the overflow. – Eric Postpischil Jun 06 '23 at 20:32
  • 4
    When it does overflow, the behavior is not defined by the C standard. Even if it is defined to wrap modulo 2^32, which is a common behavior, that means `INT32_MIN - INT32_MAX` will produce 1, which will trigger an incorrect result of 1 instead of 0. You could avoid that by converting to `double` first. However, involving `double` at all is a bad idea for performance; conversion between integer and floating-point is often not very fast, and the point of this question is to find a fast solution. – Eric Postpischil Jun 06 '23 at 20:35
  • It doesn't overflow. n1 and n2 are promoted to double before the operation, at least according to my clang compiler in debug. however i did make yet another arithmetic error, which i will now fix. by the way, i did delete my other mistakes, so what? – SpaceCadet Jun 07 '23 at 02:31
  • 2
    You should check with clang again. The expression `n1 - n2` does an integer subtraction **before** the result is assigned to `d`, even in [debug mode](https://godbolt.org/z/Kq6or1cbc). – Blastfurnace Jun 07 '23 at 04:08
  • `n1` and `n2` are not promoted to `double` before subtraction, the expression does overflow, and the code produces wrong answers, as demonstrated by the fact that `int32_t n1 = INT32_MIN, n2 = INT32_MAX; double d, bIs1or0 = (d = n1 - n2) ? ( d == 1 ? 1 : 0) : 1; printf("%g\n", bIs1or0);` prints “1” in one C implementation (Apple Clang 11 with Xcode 11.3.1 on macOS 10.14.6). – Eric Postpischil Jun 07 '23 at 11:34
  • int64_t n1 = INT_MIN, n2 = INT_MAX; double d, bIs1or0 = (d = n1 - n2) ? ( d == 1 ? 1 : 0) : 1; – SpaceCadet Jun 08 '23 at 02:53
  • i have already double checked this. i am running in c++, its not obvious which clang, its the default, freebsd 13.2 codeblocks20. it still works today just like yesterday int64_t n1 = INT_MIN, n2 = INT_MAX; double d, bIs1or0 = (d = n1 - n2) ? ( d == 1 ? 1 : 0) : 1; does not overflow. in this instance bIs1or0 is 0. furthermore d == -4billion and change. the debugger is gdb. so n1 and n2 ARE promoted in this implementation. regardless, if i ran into overflow real time i would just promote the int32s to int64 and do the exact same thing. except i would use int64 instead of double. – SpaceCadet Jun 08 '23 at 03:09
  • by the way, this is c++ not xcode. i can't speak for how xcode works. anyway i can write it again if the thought police don't mind, and fix this beside the point nit picking. this time for sure – SpaceCadet Jun 08 '23 at 03:11
  • *`bIs1or0 = (d = n1 - n2) ? ( d == 1 ? 1 : 0) : 1;` does not overflow.* If `n1 - n2` doesn't overflow, your compiler does not conform to either the C or C++ standards. The `n1 - n2` calculation should be done per [the "usual arithmetic conversions](https://port70.net/~nsz/c/c11/n1570.html#6.3.1.8). As both `n1` and `n2` are `int32_t` values, the calculation of `n1 - n2` is done as a 32-bit integer value that **is subject to overflow** and therefore undefined behavior. – Andrew Henle Jun 09 '23 at 20:21