Learning the C Programming Language as a Classical Musician [12]

Episode 12 – Types (Part 4: Practical Examples)

Welcome back!

In the previous episode, we finally managed to look at some practical examples of types in action, starting with arithmetic types. We met Mr. George Boole and his Boolean logic, with the introduction of the Boolean type, which allows us to represent the true and false dichotomy. That was an appropriate time, also, to introduce you to the concepts of logical operator, equal to, not equal to, or, and, respectively represented by ==, !=, ||, and &&. We also delved a bit further into characters, with some fascinating findings such as the realisation that a capital letter and its lowercase counterpart are 32 mapping units apart, and that we can directly print to screen the representation of a character by just associating to it its integer value.

Today, we are going to look at integer types.

Let’s get started.

Arithmetic types (Part 2)

Integer types

We all know from elementary school that an integer is a number that can be written without a fractional component, for example, 21, 4, 0, and -2048. The set of integers consists of the number zero 0, the positive natural numbers (1, 2, 3, ...), also called whole numbers or counting numbers, and their negative counterparts (known as additive inverses, or negative integers, e.g., -1, -2, -3, ...). That should suffice for a basic review of our math knowledge.

The most optimal integer type for the C programming language is, you name it, int, which can also be accessed by writing signed int (that is, an integer that supports both positive and negative values). The reason it is the best type to be used to represent whole numbers is because it is guaranteed to be at least 16-bits long, even though most current systems use 32 or even 64 bits. Why is this important, you may ask? Well, because numbers are infinite, while memory capacity in a computer is not! Therefore, we need to allocate enough space for the representation of the number we want to express, but also be careful not to allocate space we do not need, as this could severely hinder performance in the long run.

Coming back to int versus signed int, these two lines of code are the same:

int n = -4;
signed int sn = -4; 

This may seem obvious to us, but in computer science there is nothing obvious, as the computer is by default unable to understand on its own, and we have to teach it what to do. Today we have powerful AI and Machine Learning models that seem to learn things, but at the base there was actually a human (more than one, usually!) who taught that machine how to learn.

In modern computers which almost everywhere run 64-bit operating systems this distinction is not too relevant anymore, but for completeness I will include it. According to the C standard, there are four sizes of integer types: short, int, long, and long long, respectively at least 16, 32, 32, and 64 bit wide (wonder why they called them short and long if they are actually narrow and wide, mysteries of tech…). According to the data model adopted by the operative system, these types will have a specific size. Each of these types can be signed or unsigned, which is what we would use for absolute values and for modulo arithmetic. The modulo operator % is used to perform a division a : b and return any remainder of that operation:

int r = 5 % 4; // the value of 1 is stored in r 

Trying to fiddle a bit with the compiler, I managed to see that r is of type unsigned long, which means that, by default, the compiler knows that after a modulo operation, the type to be stored into r should be that of an unsigned integer at least 32-bit wide. It didn’t complain when I wrote int at the beginning of the line, but it did when I tried to use the %d conversion specifier to print it; it wanted %lu, which is the specifier for long unsigned integer.

printf("%lu\n", sizeof(r));

This line prints 4 to the console, that is 4 bytes, or (4×8) 32 bits. It works!

We can perform all kinds of operations with integers, and store their result into objects such as variables or constants (introduced by the const type qualifier). Check this out:

int a = 3, b = 4;
int sum = a + b;
int diff = a - b;
int prod = a * b;
int div = a / b;
printf("a + b is %d\na - b is %d\na x b is %d\na / b is %d\n", sum, diff, prod, div);

This will print out as follows:

a + b is 7
a - b is -1
a x b is 12
a / b is 0

It all kind of makes sense, but why does a / b prints out as zero? Remember that we told the compiler that we want integers, not decimals, thus if the division is not perfect it will return only the digit before the decimal point. The interesting thing is that there is no rounding in place: we know from practice that if we try to divide a bar of music of 3/4 time into 4 parts we need to use three quarters of each beat for each part, so 0.75.

Then why isn’t the result 1, as for rounding up? Because that’s how a computer works—until you tell it otherwise—, it only reads the part before the dot. We will perform many more operations with integers as time goes by.

Before moving on, please notice that if we #include the <stdint.h> header file, we can specify the size of the integers and even use some very useful macros to know more about integers. If you are into technical language, you can learn more here. Meanwhile, are some examples in code:

int min8bit = INT8_MIN;
int min16bit = INT16_MIN;
int min32bit = INT32_MIN;
long long min64bit = INT64_MIN;
printf("The minimum value of integer differs according to their size:\n8bit: %d\n16bit: %d\n32bit: %d\n64bit: %lld\n", min8bit, min16bit, min32bit, min64bit);

Outputs as:

The minimum value of integer differs according to their size:
8bit: -128
16bit: -32768
32bit: -2147483648
64bit: -9223372036854775808

The same is possible for maximum values, returning the reverse of these numbers -1. It is also funny to check the maximum/minimum values of unsigned integers, to see if they can actually occupy more space. The output is just so very intriguing:

int U_max8bit = UINT8_MAX;
int U_max16bit = UINT16_MAX;
int U_max32bit = UINT32_MAX;
long long U_max64bit = UINT64_MAX;
printf("The maximum value of unsigned integers differs according to their size:\n8bit: %d\n16bit: %d\n32bit: %d\n64bit: %lld\n", U_max8bit, U_max16bit, U_max32bit, U_max64bit);

The output is this:

The maximum value of unsigned integers differs according to their size:
8bit: 255
16bit: 65535
32bit: -1
64bit: -1

This means that while for 8 and 16 bits we could get to a value, for 32 and 64 we could not and the integer overflowed, meaning that it reached the end of the available space and came back, counting backwards.

Conversion specifiers & non-decimal numbers

About conversion specifiers, we can use %d or %i for a signed decimal integer value; %u for an unsigned decimal integer value, %o for an unsigned octal integer value, and %x or %X for an unsigned lowercase or uppercase hexadecimal integer number.

As a quick debriefing on non-decimal number types, it all comes down to what we did probably in the first year of primary school and then never again (big mistake, education system!), that is, dividing digits into boxes representing their unit value. For example, the number 496 is 6 units, 9 tens, and 4 hundreds, that is 6*10^0 plus 9*10^1 plus 4*10^2.

Binary

Now, a computer knows only zeros and ones, so we need to help convert it to binary. To do so, we need to convert our columns from powers of 10 to powers of 2. The highest fitting power of two in 496 is 2^8 (which is the 9th bit and thus requires us to use a type that is at least 16-bit long), 256, and since we use this bit we put a 1 in our column. Next we have 2^7, which is 128, there’s place for 1 of it, so we put a 1 here as well. Then, 2^6 = 64 we subtract it from 112 (our remainder), put a 1 and move on, until 2^4 = 16 which will absorb and complete our number. So, what do we do with digits that do not fit? We just put 0 in them, and our final representation of 496 in binary is 111110000, even if we could pad it with zeroes before the first 1 until we get to 16 bits.

Octal

You will now have for sure understood how this works, so let’s try to express the same number in octal, which is a base 8 system. We have two ways of doing this, the first being the same we used before: powers of 8 are much bigger, so already 8^3 = 512 will be out of the question. 8^2 = 64 will fit seven times, so we write 7 in our first column, with a remainder of 48. Next, we will divide this number by 8^1 = 8, getting 6. The representation of 496 in octal will therefore be 760.

The second method is indeed faster if you are keen on reading binary numbers. Group the binary version into groups of three digits: 111|110|000 and then read those digits as if they were binaries. 111 is 7, 110 is 6, and 000 is just 0.

Hexadecimal

To end this episode, we try the same with base-16 numbers, known as hexadecimals. These represent values from 0 to 9 with the same digits, and 10 to 15 with letters A through F (uppercase or lowercase is the same). The reason these exist is that they provide a human-friendly representation of binary-coded values. Each hexadecimal digit represents four bits (binary digits), also known as a nibble. For example, an 8-bit byte can have values ranging from 00000000 to 11111111 in binary form, which can be conveniently represented as 00 to FF in hexadecimal. In the C programming language, we use the prefix 0x before the number to denote that it is a hexadecimal number.

Let’s first try the quick route, dividing our binary into groups of four: 0001|1111|0000. Now let’s read this, knowing that it will have 3 digits: 0001 is 1, 1111 is 15 so F, and 0000 is 0. Our number is thus 0x1F0.

What’s next?

In the next episode, we will tackle a beast of a subject: floating-point numbers!

See you then, and thank you!

Bottom Line

Thank you for reading today’s article.

If you have any question or suggestion, please leave a comment below or contact me using the dedicated contact form. Assuming you do not already do so, please subscribe to my newsletter on Gumroad, to receive exclusive discounts and free products.

I hope you found this article helpful, if you did, please like it and share it with your friends and peers. Don’t forget to follow me on this blog and to let me know what you think.

If you are interested in my music engraving services and publications don’t forget to visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).

You can also support me by buying Paul Hudson’s Swift programming books from this Affiliate Link or BigMountainStudio’s books from this Affiliate Link.

Thank you so much for reading!

Until the next one, this is Michele, the Music Designer.

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

3 thoughts on “Learning the C Programming Language as a Classical Musician [12]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: