Here's the method used in the musl C library (with an explanation thrown in).

Consider the relationship between uppercase and lowercase ASCII (for example here). You should note that 01100001-01111010 are lowercase (all lowercase start with 011) and 01000001-01011010 are uppercase numbers (all uppercase begin with 010). In fact, if you look, you will notice that except for the first three digits, the entire code is the same. For example, lowercase 'a' changes to uppercase 'A' simply by changing the third number to a zero.
Code:
``````
//convert the int to unsigned so negative numbers will simply roll around to very large numbers
//a is 01100001, but is first converted to unsigned (because c is unsigned.
//The conversion doesn't actually change this number as it is already positive)
//by subtracting 01100001 from the character, if the character is a lowercase character, it will be between 0 (a - a) and 25 (z - a)
//numbers less than 01100001 (for other non-alphabet characters) will become large numbers due to wrapping
//numbers greater than 01111010 will be greater than 26
//finally, return true if we are less than 26
int islower(int c) {
return (unsigned)c - 'a' < 26;
}

//0x5f is 01011111
//a bitwise AND will force those zeroed numbers to stay zero while the rest of the number
//will stay just the way it is
//eg
//01110110
//01011111 &
//---------
//01010110
int toupper(int c) {
if (islower(c)) return c & 0x5f;
return c;
}```
```