cint university / 201 / ced, the cint editor
201-08: control flow
This is lesson 201-08 of CU-201.2, checked against cint 0.2.0.
You will write cint's loops and branches and see where they differ from C: braces are required, switch never falls through by accident and a loop counter faults instead of wrapping.
the program
The Collatz rule halves an even number and turns an odd number n into 3n + 1. Every positive whole number checked so far reaches 1 when the rule is repeated, and nobody has proved that all do. This program counts the steps:
// Collatz paths: the steps a start takes to reach 1,
// halving each even number and tripling each odd one, plus one.
I64 next(I64 n) {
if (n % 2 == 0) {
return n / 2;
}
return 3 * n + 1;
}
I64 steps_to_one(I64 start) {
I64 n = start;
I64 taken = 0;
while (n != 1) {
n = next(n);
taken++;
}
return taken;
}
void main() {
// Starts 1, 10, 100, 1000: the range form has no step, so this is a C-form loop.
for (I64 start = 1; start <= 1_000; start *= 10) {
"{start:>4} takes {steps_to_one(start)} steps\n";
}
// Sort the starts below 1000 by how long they take.
I64 quick = 0;
I64 middling = 0;
I64 slow = 0;
for start in 1..1_000 {
switch (steps_to_one(start)) {
case 0..=19:
quick++;
case 20..=99:
middling++;
default:
slow++;
}
}
"below 1000: {quick} quick, {middling} middling, {slow} slow\n";
// The first start whose path climbs past 100,000.
I64 found = 0;
I64 first_over = 0;
starts: for start in 1..1_000 {
I64 n = start;
while (n != 1) {
n = next(n);
if (n > 100_000) {
found = start;
first_over = n;
break starts;
}
}
}
"first past 100000: {found}, first goes over 100000 at {first_over}\n";
}
run it
$ cint run collatz.ci
1 takes 0 steps
10 takes 6 steps
100 takes 25 steps
1000 takes 111 steps
below 1000: 133 quick, 618 middling, 248 slow
first past 100000: 703, first goes over 100000 at 148444
The same rule written in Python 3.13 on Ubuntu 24.04 prints the same six lines.
if and while
if, else if, else and while read as they do in C, with one rule C does not have: every body needs its braces, even a body of one statement.
void main() {
I64 c = 3;
if (c > 2) "big\n";
}
error[C1043]: missing parentheses or braces
--> braces.ci:3:16
|
3 | if (c > 2) "big\n";
| ^
|
An assignment is a statement, not a value, so if (x = 5) is an error where C would assign and test:
void main() {
I64 x = 3;
if (x = 5) {
"five\n";
}
}
error[C1041]: assignment in a condition
--> assign.ci:3:9
|
3 | if (x = 5) {
| ^
|
taken++; is a statement too, and a checked one: at the largest value of its type it faults. It cannot appear inside an expression, so a[i++] is an error. cint 0.2.0 has no do-while loop; write a while loop instead.
two kinds of for
The range form counts through a range of I64 values:
for i in 0..nrunsifrom 0 up to n - 1.for i in 1..=nrunsifrom 1 up to n, including n.
The bounds are computed once, before the first pass, and the body cannot assign to i (error C2052). An empty range, such as 5..5, runs the body no times. cint 0.2.0 does not support a step on the range form, so any other step needs the C form:
for (I64 start = 1; start <= 1_000; start *= 10) {
The C form has the three parts C has: a declaration, a condition and an update. The update can be any statement without a block, such as start *= 10. Its variable can be assigned in the body.
Because ++ is checked, a C-form loop cannot run past the end of its type:
void main() {
I64 steps = 0;
for (I8 i = 0; i <= 127; i++) {
steps++;
}
"{steps}\n";
}
fault[E_OVERFLOW]: the exact result is outside the type's range
--> i8loop.ci:3:31
|
3 | for (I8 i = 0; i <= 127; i++) {
| ^ add.checked.i8
|
= operation: add.checked.i8
= operand-count: 2
= operand: I8 127
= operand: I8 1
= exact: 128
= limit: I8 127
...
i <= 127 is always true for an I8, so this loop can never end on its condition. In cint it faults when i++ tries to step past 127, after 128 passes.
switch
A switch tests one integer against cases. Each case holds constants, ranges written with ..= or a list of both, such as case 1..=4, 9:. A case's body ends at the next case, so the program's three counters never mix. Where one body should go on into the next, it says so with fallthrough;:
I64 code = 6;
switch (code) {
case 0:
"ok\n";
case 1..=4, 9:
"warning {code}\n";
case 5..=8:
"error {code}\n";
fallthrough;
case 10:
"logged\n";
default:
"unknown {code}\n";
}
This prints error 6 and then logged.
Four switch mistakes are errors:
| Written | Error | Why |
|---|---|---|
case 1: with no body, followed by case 2: |
C4023 | This is C's way to share a body. Write case 1, 2:. |
case 1..5: |
C4022 | A case range must include its end, so it is written 1..=5. |
a switch that leaves out some values |
C4020 | Every value must have a case, or the switch needs a default. |
| the same value in two cases | C4021 | Only one case may claim a value. This is an error in C too. |
break; inside a switch leaves the switch, as in C.
labels
To leave more than one loop at once, label the outer loop and name the label:
starts: for start in 1..1_000 {
...
break starts;
break starts; leaves the for loop and the while loop inside it, so the search stops at 703. continue starts; would go on to the next start. Without a label, break and continue apply to the innermost loop. cint has no goto.
elsewhere
The same int8_t loop in C, built with GCC 13.3 and -O2 -Wall -Wextra on Ubuntu 24.04 for x86-64, warns that i <= 127 is always true. Built with -Wall alone it gives no warning. Either way the program ran until it was stopped after 10 seconds, because i++ wraps to -128 and the condition never fails.
In C, a case with no break falls into the next one, and missing a break is a common mistake. GCC and Clang can warn about it with -Wimplicit-fallthrough. In cint, falling through is something you write.
walk-through
I64 next(I64 n) returns early with return n / 2; for an even number, so the last line handles only odd ones.
3 * n + 1 is checked, as every plain operator is. No start below 1000 climbs anywhere near the limit of I64, but a path that did would fault instead of wrapping into a wrong path.
{start:>4} prints start right-aligned in four characters, which lines up the first four rows.
I64 taken cannot be named count, because count is a built-in function and a local variable cannot take its name.
try this
- Change the first loop to start at 1 and multiply by 3 up to 2187. How many steps does 2187 take?
- Change the
switchso that starts taking exactly 111 steps count in a fourth counter. Does it matter where the new case goes? Why? - Change
break starts;tobreak;. What does the program print now, and why?
reference
if, while, for, switch, break and continue, checked arithmetic, print formats
Next: 201-09, functions.