0

The Fenwick tree data structure requires a member function read. For a index idx, read must compute several indices idx[0], idx[1], idx[2], ..., idx[last]. Each idx[i] will be the value that results when you set the i least significant non-zero bits of idx to 0.

For example, if idx = 13 = ...00001101, then idx[1] = 13, idx[1] = 12 = ...00001100, and idx[2] = 8 = ...00001000.

If we assume, as most online sources on Fenwick trees do, that signed integers are represented using two's complement, idx[i] is easy to compute. In that case, idx[i+1] = idx[i] - (idx[i] & -idx[i]);. Justification for this line can be found here: Fenwick trees.

Prior to C++20, using bitwise & was implementation defined, but it appears C++20 now requires the use of two's complement. That leads to the following questions:

  1. Is it correct to say that, prior to C++20, idx[i+1] = idx[i] - (idx[i] & -idx[i]); is "incorrect" code?
  2. With C++20, is idx[i+1] = idx[i] - (idx[i] & -idx[i]); now correct?
  3. If the answer to (2) is "no", what is an efficient way to compute idx[]? If idx is unsigned, can I just do idx[i+1] = idx[i] - (((~idx[i]) + 1) & idx[i])?
mbang
  • 855
  • 2
  • 9

1 Answers1

0

If idx is unsigned, can I just do idx[i+1] = idx[i] - (((~idx[i]) + 1) & idx[i])?

In fact if idx is unsigned you can just do idx[i+1] = idx[i] - (idx[i] & -idx[i]);, so you can get the best of both worlds.

Contrary to popular myth, negating unsigned integers is well-defined, and moreover it does exactly what you need it to do here.

This has always been safe, and it's always convenient. There is no need to rewrite the negation.

From the C++ specification:

The operand of the unary - operator shall have arithmetic or unscoped enumeration type and the result is the negation of its operand. Integral promotion is performed on integral or enumeration operands. The negative of an unsigned quantity is computed by subtracting its value from 2n, where n is the number of bits in the promoted operand. The type of the result is the type of the promoted operand.

harold
  • 61,398
  • 6
  • 86
  • 164
  • I don't much about micro-optimizations, but wouldn't integral promotion + subtraction be slower than bitwise not + an addition? This guess is based on the assumption that CPU architectures probably aren't optimized for integral promotion. – mbang Aug 18 '23 at 19:35
  • 1
    @mbang that's just the standard talking at a high level, not representative of an actual implementation. Negating an unsigned integer produces exactly the same code as negating a signed integer when targeting any relevant CPU. [For example](https://godbolt.org/z/vdeMd5zrc) the entire thing (all 3 operations) might well be compiled to a single instruction, on modern x64. (if you allow the compiler to use modern instructions) – harold Aug 18 '23 at 19:40