Bit twiddling: What does warning CS0675 mean?

Bit twiddling: What does warning CS0675 mean?

Rate This
  • Comments 17

From the sublime level of continuation passing style we go back to the mundane level of twiddling individual bits.

int i = SomeBagOfBits();
ulong u = SomeOtherBagOfBits();
ulong result = u | i; // combine them together

Whoops, that's an error. "Operator | cannot be applied to operands of type int and ulong." There are bitwise-or operators defined on int, uint, long and ulong, but none between int and ulong. You cannot use the int version because the ulong might not fit, and you cannot use the ulong version because the int might be negative.

I demand that the compiler do my bidding regardless!

ulong result = u | (ulong) i;

There, now the compiler has to choose the ulong operator, and the explicit conversion from int to ulong we know never fails.

Argh, now we've got a warning! "CS0675: Bitwise-or operator used on a sign-extended operand; consider casting to a smaller unsigned type first."

I am often asked what the meaning of this warning is. The crux of the matter is that the conversion from int to ulong does sign extension. Let's make a more concrete example:

int i = -17973521; // In hex that is FEEDBEEF
ulong u = 0x0123456700000000;
ulong result = u | (ulong)i;
Console.WriteLine(result.ToString("x"));

What is the expected result? Most people expect that the result is 1234567FEEDBEEF. It is not. It is FFFFFFFFFEEDBEEF. Why? Because when converting an int to a ulong, first the int is converted to a long so that the sign information is not lost. The long -17973521 is in hex FFFFFFFFFEEDBEEF. That long is then converted to that ulong, which is then or'd in the natural way to produce the unexpected result.

The compiler warns you in this case because this pattern is almost always wrong. To eliminate the warning, first decide whether you want the sign extension or not. If you do not want it, then follow the advice given in the warning. The warning text says "consider casting to a smaller unsigned type" for a reason!

ulong result = u | (uint)i;

This tells the compiler "first convert the int to a uint then convert the uint to a ulong". Since all the math is now done in unsigned types, there is no sign to extend.

If for some strange reason what you want is the sign extension semantics then tell the compiler that explicitly:

ulong result = u | (ulong)(long)i;

And add a comment pointing out why you are doing this crazy thing, please.

However, better to not get into this bizarre situation altogether. First off, if you can avoid bit twiddling, do so. I very rarely have to twiddle a bit in a "primitive" integral type these days. Use enums with the Flags attribute if you want to represent a set of bit flags in a compact space. Second, if you must use primitive types then don't be doing bitwise operations on signed values; that is almost never the right thing to do. Third, try to avoid doing bitwise operations on operands of different bit size; that is also waving a red flag.

  • (Obviously) the situation is not specific to C# (although the semantics are). Every time I hear it, it brings back memories of the (sophmoric) humor when dealing with this on a Motorola 6809 uP [late 1970's]..Sign Extension was a specific assembly instruction with the mnemonic SEX...I will leave the details of the "humor" up to the reader.

  • Ah yes, the 6809.  That was a fun little processor, wasn't it?

    I think I can count on one hand the number of times I've had to resort to bit twiddling in C# in the past 8 years.  I always err on the side of redundant casts and parentheses when twiddling bits, just to make sure that I, the compiler and any future reader share a common understanding of exactly what's being done.  

  • Even with multiple explicit casts, I still make mistakes when bit-twiddling with C#. It would be nice to provide some standard helper functions for producing an Int64 from two Int32, and similar stuff.

    Also I often have to convert int array to string, for hashing purposes ('cause I found Dictionary<string,T> makes a faster cache than Dictionary<int[],T>).

  • As someone who seems to mostly use C# for dealing with binary formats, I have quite a lot of use for bit twiddling :)

    @Olivier: You could try sticking the hash code in the first element (xor over the original if you don't need it), then implement IEqualityComparer<int[]> to return it for GetHashCode().

  • @Olivier: Well, you could just use a C# union (use StructLayout(LayoutKind.Explicit) with overlapping FieldOffset values) rather than coding the bit-twiddling code yourself.  That's a relatively easy way to go back and forth between differently sized numbers.  See msdn.microsoft.com/.../acxa5b99(VS.80).aspx .

  • @Oliver: You missed Step 2: Never use signed *anything* for bit-twiddling. Once you fix that, the functions are relatively simple.

    ulong Combine(uint high, uint low) {

       return ((ulong)high) << 32 | low;

    }

    uint Combine(ushort high, ushort low) {

       return ((uint)high) << 16 | low;

    }

    ushort Combine(byte high, byte low) {

       return (ushort)(high << 8 | low);

    }

  • This would be a good moment to remind the readers of the BitArray class in .Net

  • @brian thatt only goes to the byte level, not bit. ;)

  • thx for your advice.

    @Simon: it's not about the hashcode, the array creation is too expensive. int/long/struct make the fastest dict keys, then string, int tuple, then int array.

    So we work with long, or string if the key doesn't fit in 64b.

  • "Use enums with the Flags attribute ... compact space"

    This is an attempt at optimizing for space. Programmers learned to do this when every bit was precious. Today it's just premature optimization. We write more code and create more bugs and deny the compiler the opportunity to optimize for us and give up much of the help of IntelliSense.

    I always encapsulate in a new type and store the various flags as separate `bool` fields. It's easy to go back later and change the storage to `[Flags] enum` or `int` or whatever, if that turns out to be critical.

  • @Ferdinand Swaters: One thing to bear in mind is that BitArray hasn't been updated for generics, so the IEnumerable implementation will box and unbox every single bool it returns!

  • @fowl: Yes, I was providing a suggestion for how to implement "some standard helper functions for producing an Int64 from two Int32, and similar stuff."

  • I wonder what the impact would be on C# if (from the beginning) there were another variety of ints which only supported the bitwise operators. int and uint would have the arithmetic operators and not the bitwise operators, while bint (!) would only support the bitwise operators and not the arithmetic operators. (Like how bool is now a separate type with it's own set of operators. It used to be a regular int in the precursor languages.) Mixing the two varieties of operator seems like the wrong thing to do, so shouldn't be allowed without an explicit cast.

    (I added "from the beginning" as I imagine it's too late to do this now.)

  • there's nothing stopping you adding such a set of structs, with the appropriate explict conversion operators and operator overloads. You could then add something like an FXCop rule which scanned for uses of the bitwise operators outside of these structs and treated them as errors.

    IT is annoyng that you would not be able to do this generically (Bitwise<T>) in the current system but you could generate them from a T4 template without much effort (and you'd really only need to do it for the relevant bit sizes, 8,16,32,64) though having  well defined operations on Bitwise32 and Bitwise64 would need rather more sophisticated templates so you might find doing it by hand was simpler.

  • "add a comment pointing out why you are doing this crazy thing, please." - this should be included in every source file's header.  Good advice for anyone.

Page 1 of 2 (17 items) 12