This month’s Exercise presents what I often refer to as a code “toggle.” Many paths lead to a solution.
Because of my interest in coding various math equations, my goal is to craft a solution distilled into a single expression. Turns out that all of my solutions easily fit into a single expression, even the one that didn’t work.
For my first solution, I use the modulus operator in a ternary expression:
2024_02-Exercise-a.c
#include <stdio.h> int main() { int a; for( a=0; a<14; a++ ) printf("%d\n",a%2?-1:1); return(0); }
Variable a
loops from 0 through 14. The expression in the printf() statement tests for odd and even values of a
. For odd values, -1 is output; for even values 1 is output.
For my second solution, I went binary. Bits toggle between two values, which can also be set in a single expression:
2024_02-Exercise-b.c
#include <stdio.h> int main() { int a; for( a=0; a<14; a++ ) printf("%d\n",a&0x01?-1:1); return(0); }
The bitwise manipulation a&0x01
masks bits 7 through 2 in 16-bit integer value. What remains is the right-most bit, which alternates between zero and one for increasing values of variable a
. The effect is the same as my first solution, resulting in TRUE/FALSE condition in a ternary operation.
For my last solution, I turned to the C23 standard and its variable integer width data specification, _BitInt(n). I figured that I’d be truly clever and create a one-bit integer.
2024_02-Exercise-c.c (C23)
#include <stdio.h> int main() { int a; _BitInt(2) toggle; toggle = 0; for( a=0; a<14; a++ ) { printf("%d\n",(int)toggle); toggle++; } return(0); }
As you can see in the source code, the _BitInt(n) declaration uses two bits, not one. Apparently the C23 standard doesn’t allow for single bit integers, at least on the clang-15 compiler I’m using.
Here is the output from my final attempt:
0
1
-2
-1
0
1
-2
-1
0
1
-2
-1
0
1
The values oscillate, as any integer would between its limits. The range for a 2-bit integer in C23 is from -2 through 1. Yes, it’s a signed value. I tried casting the variable to unsigned, but it didn’t work. So much for being clever. (And remember that you must use a compatible C23 compiler with the -std=c2x
switch.)
I hope you came up with something interesting or different. This type of exercise is one that yields a variety of results — perhaps even including a few obfuscated examples. This potential is why I so enjoy the C language.
Is it ever possible to cast a signed type to unsigned? I can’t see any reason and even if you did you’d get a meaningless result.
For example -54 as a signed type would be 11001010 but I assume casting that to an unsigned type would just interpret those bits as +202. If someone did this hoping to get an absolute value then they’d just end up with a bug to puzzle out. Unless maybe casting does actually work properly and give the correct positive value.
Anyone know?
The only time you need an unsigned value is when working with bits. Otherwise it’s a burden on the compiler. This probably explains why the BitInt() type dislikes the unsigned typecast.
@ChrisWebb
»For example -54 as a signed type would be 11001010 but I assume casting that to an unsigned type would just interpret those bits as +202.«
No, thatʼs not what happens. The resultant value depends on the type of the typecast: (unsigned char)-54 will result in the value 202, (unsigned short)-54 in 65482, (unsigned int)-54 in 4294967242, … and in general: (unsigned intn_t)-k will result in the value 2ⁿ-k.
Thatʼs because an unsigned typecast tells the compiler that the bit pattern of a given number isnʼt to be interpreted as a twoʼs complement value but as an unsigned one.
With that in mind, unsigned typecasts have one important use case—to generate the largest value that is storable in a variable of a given unsigned type:
size_t num = (size_t)-1;
/* → num == 2³²-1 on 32-bit platforms / num == 2⁶⁴-1 on 64-bit platforms */
For example, this can be useful if one wants to iterate over all values, beginning with zero, with a pre-increment instead of a post-increment (for some algorithms this may make things easier):
num = (size_t)-1;
do {
++num; /* num will be 0 on the first loop iteration */
… do something with num …
} while (num < 9);
Re-reading what I wrote I think I have to clarify: what I meant to say in my first paragraph was that (unsigned)-54 will not be 202 because integral values default to type
int
in “C”… that is, (unsigned)-54 will result in 4294967242.Regardless of that, I really do think that unsigned trickery such as the one presented has its places in real C code—especially if the bitness of the target architecture isnʼt known in advance, relying on constants like INT_MAX isnʼt an option.