I am trying to solve the Ex 2-1 of K&R's C book. The exercise asks to, among others, determine the ranges of char by direct computation (rather than printing the values directly from the limits.h
). Any idea on how this should be done nicely?
5 Answers
Ok, I throw my version in the ring:
unsigned char uchar_max = (unsigned char)~0;
// min is 0, of course
signed char schar_min = (signed char)(uchar_max & ~(uchar_max >> 1));
signed char schar_max = (signed char)(0 - (schar_min + 1));
It does assume 2's complement for signed and the same size for signed and unsigned char. While the former I just define, the latter I'm sure can be deduced from the standard as both are char and have to hold all encodings of the "execution charset" (What would that imply for RL-encoded charsets like UTF-8).
It is straigt-forward to get a 1's complement and sing/magnitude-version from this. Note that the unsigned version is always the same.
One advantage is that is completely runs with char
types and no loops, etc. So it will be still performant on 8-bit architectures.
Hmm ... I really thought this would need a loop for signed. What did I miss?

- 12,050
- 4
- 30
- 52
-
What is the type of ~0? Is the cast '(unsigned char)' necessary? – zell May 19 '15 at 03:45
-
@zell it is int originally but than converted to char. Funny but true I learned that ~0 is int on the same K&R exercise 2.1. :) – May 19 '15 at 04:54
-
Good answer but did you know that you are not suppose to cast in C? 99 standard prohibits casting. – May 19 '15 at 04:54
-
@GRC: Huh? Where did you get that? It is terribly wrong. (just a hint: if the standard would prohibit casts, why would it provide them?). You might confuse this with casting `between void *` and other pointer types. _this_ is actually prohibited; just because it is not necesary as `void *` is assignment-compatible with them _by_ _definition_. And: hadn't we been talking 'bout ANSI-C? ;-) – too honest for this site May 19 '15 at 11:53
-
@zell: You should enable warnings and read the standard. There are some implicit conversion (aka coercion) rules. Basically, the results above are calculated as int, so assigning them to char would generate a (appreciated) truncation warning without the cast. – too honest for this site May 19 '15 at 15:14
Assuming that the type will wrap intelligently1, you can simply start by setting the char variable to be zero.
Then increment it until the new value is less than the previous value.
The new value is the minimum, the previous value was the maximum.
The following code should be a good start:
#include<stdio.h>
int main (void) {
char prev = 0, c = 0;
while (c >= prev) {
prev = c;
c++;
}
printf ("Minimum is %d\n", c);
printf ("Maximum is %d\n", prev);
return 0;
}
1 Technically, overflowing a variable is undefined behaviour and anything can happen, but the vast majority of implementations will work. Just keep in mind it's not guaranteed to work.
In fact, the difficulty in working this out in a portable way (some implementations had various different bit-widths for char
and some even used different encoding schemes for negative numbers) is probably precisely why those useful macros were put into limits.h
in the first place.

- 854,327
- 234
- 1,573
- 1,953
-
Not sure about ANSI-C, but for C99+ only unsigned is guarnteed to wrap (as one would expect). But is assumed `unsigned char` has the same size as `signed char` (I think this can be decuced from the standard), one might `ch <<= 1` (ch is unsigned char) stepwise until it becomes 0 (get range of unsigned), then cast the last non-zero value to a signed char, which would be SCHAR_MIN for 2s complement; SCHAR_MAX would be abs(SCHAR_MIN +1) then. However, I don't think there is a standard way to get these if the sign-representation is unknown. – too honest for this site May 19 '15 at 01:52
-
Olaf, c89 doesn't distinguish, it simply states (3.3) `If an exception occurs during the evaluation of an expression (that is, if the result is not mathematically defined or not representable), the behavior is undefined.`. – paxdiablo May 19 '15 at 01:54
-
So it's even worse. Also, char by itself can be signed or unsigned. _That_ has never been changed by the standard. So your code might very well yield (0, 255). I would say a proper solution had to conform to the standard. However, there would be one for unsigned char, but not for signed fo C99+, but none for ANSI then (unless there is another section about that - no interest to read _this_ standard anymore;-). – too honest for this site May 19 '15 at 02:05
-
@Olaf, yes but, in that case, `0..255` would be the right answer, since the question asked for the range of char (signed char in the case you bring up). Bottom line, it can't be done portably without `limits.h`. – paxdiablo May 19 '15 at 02:07
-
Yeah, exactly _that_ might be the reason the standard does require limits.h (not sure about ANSI-C; do not use it. I also would strongly discourage from using or even learning it anyways. That thing did so much harm to the language (ok, the old version was even worse). RIP – too honest for this site May 19 '15 at 02:25
You could always try the ol' standby, printf...
let's just strip things down for simplicity's sake.
This isn't a complete answer to your question, but it will check to see if a char is 8-bit--with a little help (yes, there's a bug in the code). I'll leave it up to you to figure out how.
#include <stdio.h>
#DEFINE MMAX_8_BIT_SIGNED_CHAR 127
main ()
{
char c;
c = MAX_8_BIT_SIGNED_CHAR;
printf("%d\n", c);
c++;
printf("%d\n", c);
}
Look at the output. I'm not going to give you the rest of the answer because I think you will get more out of it if you figure it out yourself, but I will say that you might want to take a look at the bit shift operator.

- 31
- 2
There are 3 relatively simple functions that can cover both the signed and unsigned types on both x86
& x86_64
:
/* signed data type low storage limit */
long long limit_s_low (unsigned char bytes)
{ return -(1ULL << (bytes * CHAR_BIT - 1)); }
/* signed data type high storage limit */
long long limit_s_high (unsigned char bytes)
{ return (1ULL << (bytes * CHAR_BIT - 1)) - 1; }
/* unsigned data type high storage limit */
unsigned long long limit_u_high (unsigned char bytes)
{
if (bytes < sizeof (long long))
return (1ULL << (bytes * CHAR_BIT)) - 1;
else
return ~1ULL - 1;
}
With CHAR_BIT
generally being 8.

- 81,885
- 6
- 58
- 85
-
See olaf's comments above. CHAR_BIT can be more than 8 for unicode cases etc, right? – zell May 19 '15 at 02:23
-
-
@zell, yes CHAR_BIT can be something other than 8, that's why I said generally... 8. However, it is a defined quantity for each platform. You now understand the numeric suffixes U-`unsigned`, L-`long`, etc.. – David C. Rankin May 19 '15 at 02:36
-
Using CHAR_BIT is like cheating as it is in limits.h which is not allowed to be used;-). Also you imply 2's complement. (Well, I also have no idea how to make this protable even through different sign-representations). – too honest for this site May 19 '15 at 03:04
-
Hmm, might be wrong with my assumption. However, if it is allowed, that would be easy-peasy:-) – too honest for this site May 19 '15 at 03:12
the smart way, simply calculate
sizeof()
of your variable and you know it's that many times larger than whatever hassizeof()=1
, usuallychar
. Given that you can use math to calculate the range. Doesn't work if you have odd sized types, like 3 bitchar
s or something.the try hard way, put 0 in the type, and increment until it doesn't increment anymore (wrap around or stays the same depending on machine). Whatever the number before that was, that's the max. Do the same for min.

- 65,249
- 10
- 91
- 131
-
Thanks. I hope to understand your smart way. Would you elaborate a little bit more? What do you mean by "you know it's that many times larger than whatever has sizeof()=1, usually char"? Basically, with sizeof, we only know how many numbers can be placed in that type, ie, 2^{sizeof(type)}, don't we? So I do not see how to use sizeof to obtain the max and min... – zell May 19 '15 at 01:20
-
1I'm having a hard time understanding the first point (which variable are you talking about?), and you're not guaranteed just those two behaviours in the second. It can literally do _anything_ when it overflows such wrap, stay the same, exit program, wipe your hard disk and so on :-) – paxdiablo May 19 '15 at 01:51
-
Well you program for machines that wipe your hard disk when you overflow variables, I'll program on mine. @zell, for unsidend variables the range is from 0 to max, so `sizeof(T)/sizeof(char)*256` is the max, and for unsigned it's -max/2 to max/2, +-1 depending on architecture. – Blindy May 19 '15 at 01:55
-
-
A char is _not_ guaranteed to be 8 bits! That makes the `256` incorrect even _if_ you change it to the more correct `255` :-) In other words, `sizeof(char)/sizeof(char)*255` gives you the wrong answer for a 12-bit char. – paxdiablo May 19 '15 at 01:59
-
sizeof(char) is guaranteed to be 1 by the standard (n1570, 6.5.3.4/4). So what does that add to the solution? – too honest for this site May 19 '15 at 02:00
-
Olaf, `sizeof(char)` is guaranteed to be one and that means one byte. But a byte in the C standard is not necessarily an octet (8 bits). It's defined to be large enough to hold a `char` which, is a stunning display of circularity, renders solutions using it useless. What you need is `CHAR_BIT` but, unfortunately, that's in `limits.h` as well :-) – paxdiablo May 19 '15 at 02:12
-
@paxdiablo: A byte is actually nowhere defined to be 8 bits. As much as a word is not defined to be 16 or 32 bits. (your'r right about the octet, that's why network protocols use this therm: Ethernet, IP, ...). And that's what the MIME-type octet-stream means. The size required for char has already stated here in a comment; I just left the reference to bytes out as being unnecessary here. Not sure what your up to. I just asked Blindy how sizeof would help to get the range of `char`. So, please: what exactly did I miss?. – too honest for this site May 19 '15 at 02:37
-
@Olaf, apologies, there was no indication in your comment who it was directed at so I thought it was a general request. As to what I'm "up to", I'm just answering questions and spreading knowledge, nothing more. If you wish, I can stop responding unless the comment is directed specifically at me (for this question anyway), or you can target your questions to specific people (though you may well have tried to do that - putting `@bob` into a comment on bob's post will have the `@bob` removed - just use `bob` without the `@`). – paxdiablo May 19 '15 at 02:47
-
@paxdiablo: No problem! I thought the comment was clearly addressed, so did not add the tag. You might notice I do very well use this to address specific ppl. Just see my other posts here and there and everywhere. – too honest for this site May 19 '15 at 03:00
-
Blindy: regading your comment: for singed 2's complement it would actually be `smin = -umax/2 - 1`, not `+1` and `smax = umax/2`. I just ignore other encodings for now. – too honest for this site May 19 '15 at 03:20