0

At the outset, I realize what I did was bad. I relied on what is now (at least) undefined behavior, if not explicitly forbidden. It used to work, and I thought I was being clever. Now it doesn't and I'm trying to fix it.

I have positive power-of-2 numbers (FFT bin index, but not important). I want to effectively FFT-shift a set of bin indices by wrapping the second half of the values to the negative range. That is, given an FFT size of 512,

0 ... 255 -> 0 ... 255
256 ... 511 -> -256 ... -1

What used to work was

template <size_t N>
struct Wrapper {
    int val : N;
};

auto constexpr index = 42u;
auto wrapper = Wrapper<9>{ index }; // warning: invalid narrowing conversion from "unsigned int" to "int"
auto val = wrapper.val; // signed value

This relied on the truncation of overflowed assignment, but was empirically tested and Just Worked(tm).

Now, it doesn't compile (cleanly).

How should I perform this conversion now?

jwm
  • 1,504
  • 1
  • 14
  • 29
  • Maybe I am misunderstanding, but isn't this just a standard sign extension from a selectable bit position? So for a 512 = 2⁹ point FFT, and `uint32_t x;`, you could call a function `map_uint_to_int (x, 9)` defined as follows: `int32_t map_uint_to_int (uint32_t a, int n) { uint32_t msb = 1u << (n - 1); return (int32_t)((a ^ msb) - msb); }`. – njuffa Nov 18 '22 at 01:34
  • I don't think you are misunderstanding, but it's hard to read code in a comment. Please post an answer – jwm Nov 18 '22 at 02:09
  • In this case this question seems to be a duplicate of [that question](https://stackoverflow.com/questions/42534749/signed-extension-from-24-bit-to-32-bit-in-c) – njuffa Nov 18 '22 at 02:54
  • apparently so, but I never would have found that question – jwm Nov 18 '22 at 22:29
  • I think the many `auto`-typed variables are very dangerous, it is not explicit, whether they are signed/unsigned or which length they have. But perhaps it is just me. – Sebastian Nov 18 '22 at 22:52
  • @Sebastian `auto` is like religion; either you "get it" or you don't. I won't discuss the benefits of `auto` here, but the typing is explicit, just on the right side of the assignment. What is missing are any implicit conversions because the declaration doesn't match the assignment. – jwm Nov 20 '22 at 00:44
  • Like 4 religions: *Always Auto*, *Almost Always Auto*, *Almost Always Avoid Auto*, *Always Avoid Auto*. Now updated with the people preferring concepts over auto and over specific types. – Sebastian Nov 20 '22 at 10:38

2 Answers2

2

How about:

auto wrapper = Wrapper<9>{ index & (1 << (9 - 1)) ? long(index) - 2 * (1 << (9 - 1)) : index };

If, for some reason (e.g. performance), ternary is not preferred, then you might also try:

auto wrapper = Wrapper<9>{ long(index) - 2 * (index & (1 << (9 - 1))) };
lorro
  • 10,687
  • 23
  • 36
  • I edited the question to make it clear that the initialization of `wrapper` is the problem. That said, I suppose a ternary operation is reasonable – jwm Nov 18 '22 at 00:36
  • 1
    @jwm Also, if - for some reason - you don't like ternary (e.g. it compiles to conditional jump and it's slow), you might translate it to multipltication: `index - (index & (1 << 9))` – lorro Nov 18 '22 at 00:42
  • I don't think this is working as desired, since 9-bit integers above 256 will have `1 << 8` set, not `1 << 9`. But it's not so hard to tweak, e.g. `index - 2 * (index & (1 << 8)). You could also use `index - ((index & (1 << 8)) << 1)`, but the expression is confusing enough as-is, and any modern compiler will compile the two to the same machine code when you tell it to optimize. – Wintermute Nov 18 '22 at 01:05
  • @Wintermute Yep, I had that error & fix while writing but apparently didn't update. Thanks for spotting! – lorro Nov 18 '22 at 01:13
  • I think there are also some cast issues to take care of, since if `index` is unsigned, all the intermediate results are also unsigned and cannot be negative. Come to think of it, if we can rely on C++20 or common practice before that, `int32_t(index) << 23 >> 23` could be the easiest and fastest way, making use of sign extension in bit shifts. Since C++20, signed ints are guaranteed to be two's complement, that's why that's required; before then it's implementation-defined (and all implementations defined it the same way). – Wintermute Nov 18 '22 at 01:16
  • @Wintermute Fixed to `long` which is signed. I wouldn't force 2's complement here (yet) as c++20 tag was not in the question, but it makes sense that you mentioned it in comments. – lorro Nov 18 '22 at 01:18
0

As njuffa suggested, the answer is (unintuitively, to me at least) here. In practice, they are the same thing, but I was explicitly converting from an unsigned range to a signed range.

lorro gave a good answer, but I was left thinking there was a simplification missing.

I ended up creating a functor that passed my unit tests:

template <unsigned N>
struct Wrapper {
    int operator()(unsigned bin)
    {
        auto constexpr sign_mask = (1u << (N-1));

        auto val = int(bin ^ sign_mask) - sign_mask;
        return val;
    }
};

BOOST_AUTO_TEST_CASE(test_tf_fft_shift_index)
{
    auto f = Wrapper<9>();
    auto n255 = f(255u);
    auto n256 = f(256u);
    auto n511 = f(511u);

    BOOST_TEST(n255 == 255);
    BOOST_TEST(n256 == -256);
    BOOST_TEST(n511 == -1);
}

This is the bit-twiddling magic I was hoping for.

jwm
  • 1,504
  • 1
  • 14
  • 29