5.1.2 · D4C Programming

Exercises — Data types — char, short, int, long, float, double, size_t

2,528 words11 min readBack to topic

This page is a staircase: we start by recognising the types, then apply the range formulas, then analyse tricky overflow/float traps, then synthesise multi-step programs, and finally reach mastery problems that combine everything. Every symbol used here was built in the parent note the parent Data Types note — if you want the "from zero" build-up of , two's complement, or the IEEE-754 formula, read that first.

Before we count anything, one picture fixes the vocabulary we lean on the whole page.

Figure — Data types — char, short, int, long, float, double, size_t

The two formulas we grind against, both derived in the parent note:


Level 1 — Recognition

Goal: read a type and immediately recall its size, its family (integer vs float), and whether it is signed.

L1.1

State: Which single data type has a sizeof that the C standard guarantees to equal ?

Recall Solution — L1.1

What we're asked: the one type whose byte-size is fixed by the standard (not by platform). Answer: char. Every other size (int, long, size_t, …) is only bounded by the standard, not pinned. By definition sizeof(char) == 1 — the byte is the unit of sizeof. See sizeof Operator.

L1.2

State: For each of unsigned int, size_t, signed char, double — say whether it can hold a negative value.

Recall Solution — L1.2
  • unsigned intno (all patterns are non-negative; range starts at ).
  • size_tno (it is unsigned — a size can never be below ).
  • signed charyes (top bit carries negative weight in two's complement).
  • doubleyes (it has a dedicated sign bit in ).

L1.3

State: Order these by typical size on a 64-bit Linux (LP64), smallest first: long, char, int, short.

Recall Solution — L1.3

char (1) short (2) int (4) long (8). Why: this matches the standard's guaranteed ordering . Sizes are bytes; the picture of stacked boxes in Figure s01 shows why bigger boxes hold bigger numbers.


Level 2 — Application

Goal: plug numbers into the two range formulas and read off exact bounds.

L2.1

State: Compute the exact range of a signed char ().

Recall Solution — L2.1

What we do: substitute into the signed formula. Why vs : there are negative patterns but only strictly-positive ones, because zero uses a slot on the non-negative side. The asymmetry is not a bug — it is the two's-complement design.

L2.2

State: Compute the range of unsigned short ().

Recall Solution — L2.2

What we do: unsigned formula with . Why not : counts the patterns; subtract because pattern number one is the value .

L2.3

State: What is INT_MAX and INT_MIN for a 32-bit int?

Recall Solution — L2.3

Signed formula, : Why it matters: adding to INT_MAX is undefined behaviour in signed arithmetic — see Integer Overflow and Undefined Behaviour.

L2.4

State: How many distinct values can a 4-bit unsigned "nibble" hold, and what is its max?

Recall Solution — L2.4

Count of patterns . Max value . So the range is — exactly 16 values, matching Figure s02's little clock of 16 positions.


Level 3 — Analysis

Goal: predict what a program prints when wrap-around, conversion, or float rounding bites.

L3.1 — signed wrap

State:

signed char c = 127;
c = c + 1;
printf("%d\n", c);

Predict the output.

Recall Solution — L3.1

What happens: is 0111 1111. Adding gives 1000 0000. Read as two's complement, 1000 0000 is the most-negative pattern . Why: the clock in Figure s02 wraps — one tick past the highest positive lands on the lowest negative. Output: -128. (Note: at wider int width this addition would be fine; the wrap here happens because c is stored back into an 8-bit signed char. Signed overflow at full int width is UB, see Integer Overflow and Undefined Behaviour.)

L3.2 — the char=200 classic

State:

char c = 200;            // assume char is signed
unsigned char u = 200;
printf("%d %d\n", c, u);

Predict.

Recall Solution — L3.2

Pattern: 1100 1000.

  • As unsigned char: value is literally .
  • As signed char: top bit set ⇒ value . Why : the two's-complement rule "if top bit is 1, subtract " with . See Type Conversion and Promotion for why the same bits get two readings. Output: -56 200.

L3.3 — the float equality trap

State: Does 0.1 + 0.2 == 0.3 evaluate to true in C?

Recall Solution — L3.3

No. Neither nor has a finite binary fraction (just as has no finite decimal). Each is rounded to the nearest representable double, and the rounded sum is 0.30000000000000004, which differs from the rounded 0.3. Fix: compare fabs((0.1+0.2) - 0.3) < 1e-9. Details in IEEE-754 Floating Point.

L3.4 — the unsigned countdown

State:

for (size_t i = 3; i >= 0; i--)
    printf("%zu ", i);

What happens?

Recall Solution — L3.4

Infinite loop. size_t is unsigned, so i >= 0 is always true. After i == 0, i-- computes 0u - 1, which wraps to the maximum size_t (e.g. ), and the loop never ends. It prints 3 2 1 0 then a giant number, then keeps running. Fix: loop with a signed index, or use for (size_t i = 3; i-- > 0; ) idiom.


Level 4 — Synthesis

Goal: chain several ideas — bit layout, conversion, precision — into one answer.

L4.1 — decimal digits of float

State: From its bit layout, derive how many significant decimal digits a float carries.

Recall Solution — L4.1

Step 1 (WHAT): a float has explicit mantissa bits plus implicit leading bit bits of significand. Step 2 (WHY the log): each binary bit multiplies the number of representable values by ; to convert "how many bits" into "how many decimal digits" we ask how many powers of ten fit in that many powers of two — that is exactly decimal digits per bit. Step 3: ~7 significant decimal digits. For double: ~15–16 digits.

L4.2 — encode a value in IEEE-754 float

State: Give sign , stored exponent , and the value of for the number in 32-bit float (bias ).

Recall Solution — L4.2

Step 1 (sign): so . Step 2 (normalize): , so the significand is (i.e. ) and the true exponent is . Step 3 (bias it): stored . Check: . ✅ Formula recalled: , from IEEE-754 Floating Point.

L4.3 — promotion in a mixed expression

State:

unsigned int a = 1;
int b = -2;
printf("%s\n", (a + b > 0) ? "positive" : "not");

What prints, and why?

Recall Solution — L4.3

What: in a + b, the int b is converted to unsigned int (usual arithmetic conversions promote the signed operand to unsigned). -2 becomes . Then: (mod ), which is . Output: positive. Why the surprise: you expected , but signed→unsigned conversion happened before the comparison. See Type Conversion and Promotion.


Level 5 — Mastery

Goal: reason about limits, degenerate inputs, and full programs where several traps stack.

L5.1 — the most-negative has no positive twin

State: For 8-bit signed char, what does -INT_MIN-style negation do? Concretely, what is the value of -(signed char)(-128) when kept in a signed char?

Recall Solution — L5.1

What: is 1000 0000. To negate in two's complement: invert bits → 0111 1111, add 1000 0000. We got the same pattern back! Why: is not representable in signed char (max is ). The most-negative value is its own negation modulo . So -(-128) stays -128. Lesson: the range asymmetry () means "negate" is not always safe. At full int width, -INT_MIN is UB — Integer Overflow and Undefined Behaviour.

L5.2 — precision gap of float near a big number

State: Around , a 32-bit float cannot represent every integer. What is the spacing between consecutive representable floats there, and hence: does (float)16777216 + 1 equal 16777217?

Recall Solution — L5.2

Step 1: with significand bits, consecutive floats near are spaced apart (the last mantissa bit has weight ). Step 2: at : spacing . So representable values step by : — the odd integer is skipped. Step 3: (float)16777216 + 1 rounds to the nearest representable value. is exactly halfway... actually it rounds to the even neighbour . Answer: (float)16777216 + 1 == 16777216, not . Precision is relative: the higher you go, the coarser the grid (Figure s03).

L5.3 — zero and degenerate width

State: (a) What is the range of a hypothetical -bit signed integer? (b) What is when , and does a -bit type make sense?

Recall Solution — L5.3

(a) signed: . Just two values: 1 means , 0 means . There is no positive value — all the asymmetry collapsed onto the single negative. (b) : — exactly one pattern (the empty pattern), which can only encode the value . C has no -bit type, but the formula stays consistent: one pattern, range . This is the degenerate limit that confirms the counting principle never breaks.

L5.4 — full program forecast

State:

#include <stdio.h>
int main(void) {
    unsigned char x = 250;
    x += 10;                 // (A)
    signed char y = x;       // (B)  assume signed char, value-preserving mod 256
    printf("%u %d\n", x, y);
    return 0;
}

Forecast the two printed numbers.

Recall Solution — L5.4

(A): unsigned char range is . . Unsigned arithmetic wraps mod : . So x == 4. (B): 4 fits in signed char , so y == 4 (no re-interpretation needed). Output: 4 4. Why (A) is defined but signed overflow isn't: unsigned wrap-around is specified by the standard (mod ); signed overflow is UB. This is the single most important distinction from Integer Overflow and Undefined Behaviour.


Active Recall

Recall One-line self-test (cover the answers)
  • signed char range? →
  • unsigned short max? →
  • (char)200 as signed? →
  • float significant digits & why? → ~7, from
  • Spacing of floats near ? →
  • 250u + 10 in unsigned char? → (wraps mod 256)
  • -(signed char)(-128)? → still
Range of an n-bit signed integer?
unsigned char max value?
(char)200 printed with %d (signed char)?
Does 0.1 + 0.2 == 0.3?
false (binary rounding)
Value of (float)16777216 + 1?
(odd integer is skipped)
unsigned char x = 250; x += 10; value of x?
(wraps mod 256)