cint university / 201 / ced, the cint editor
201-02: types and conversions
This is lesson 201-02 of CU-201.2, checked against cint 0.2.0.
You will meet the integer types, see that cint never converts a value implicitly and choose what happens at each edge of a conversion.
the program
The program packs an altitude in millimeters into a 16-bit field of decimeters, as a telemetry format might carry it:
// An altitude in millimeters, packed into a 16-bit field of
// decimeters. Each conversion says what happens at the edge.
void main() {
I64 low_mm = 3_276_700;
I16 low = (low_mm / 100) as I16;
"low: {low}\n";
I64 high_mm = 3_276_800;
I64 high_dm = high_mm / 100;
I16 wrapped = high_dm as% I16;
I16 clamped = high_dm as? I16 catch I16.max;
"wrapped: {wrapped}\n";
"handled: {clamped}\n";
I16 checked = high_dm as I16;
"not printed: {checked}\n";
}
run it
$ cint run downlink.ci
low: 32767
wrapped: -32768
handled: 32767
fault[E_NARROW]: the value does not fit the destination type
--> downlink.ci:15:27
|
15 | I16 checked = high_dm as I16;
| ^ as.checked.i64.i16
|
= operation: as.checked.i64.i16
= operand-count: 1
= operand: I64 32768
= exact: 32768
= limit: I16 32767
...
3276700 mm is 32767 dm, I16.max, and fits. 3276800 mm is 32768 dm, one past I16.max. The program converts that value three ways, and each line says what it wants at the edge.
the three conversions
| Form | When the value fits | When it does not |
|---|---|---|
x as T |
the value | fault E_NARROW |
x as% T |
the value | wraps: the low bits of x, read as T |
x as? T catch v |
the value | v, chosen by the program |
as is the default. as% is for formats and hashes where only the low bits matter. as? turns the out-of-range case into an ordinary error, and catch gives the value to use instead.
no implicit conversions
cint never converts between types on its own, not even to a wider type:
I64 big = 40_000;
I16 small = big;
error[C2001]: type mismatch
--> narrow_error.ci:2:13
|
2 | I16 small = big;
| ^
|
The same message comes from I64 wide = b; when b is a U8, although every U8 fits an I64. Each conversion is written, so each one is a place you can see and search for.
literals take their type from where they are used
A literal has no type until its context gives it one. That includes the arithmetic around it:
I16 seconds_per_day = 24 * 60 * 60;
error[C6001]: fault during constant evaluation
--> fold_error.ci:1:31
|
1 | I16 seconds_per_day = 24 * 60 * 60;
| ^
|
fault[E_OVERFLOW]: the exact result is outside the type's range
--> fold_error.ci:1:31
...
= operation: mul.checked.i16
= operand-count: 2
= operand: I16 1440
= operand: I16 60
= exact: 86400
= limit: I16 32767
...
The multiplication is done in I16 because that is the destination, and the compiler evaluates constant expressions with the same checks as the running program. The error comes before anything runs, although the full report still ends with "the program stopped".
elsewhere
These results were measured on 2026-10-10, on Ubuntu 24.04 for x86-64:
| Language | 32768 into 16 bits |
int16 x = 24 * 60 * 60 |
|---|---|---|
C, GCC 13.3, -O2 -Wall |
(int16_t)32768 gives -32768 |
compiles with the warning changes value from '86400' to '20864' and prints 20864 |
| NumPy 2.5.3 | np.array([32768]).astype(np.int16) gives [-32768]; np.int16(32768) raises OverflowError |
not applicable: Python has no typed variable declarations |
walk-through
The integer types are I8, I16, I32, I64, I128, I256, I512 and I1024, and U8 to U512 for the unsigned ones. T.min and T.max give each range. Bool is its own type: b as I64 gives 0 or 1, and like every other conversion it is written out.
Literals come in four bases, with _ as a separator anywhere between digits:
void main() {
U8 mask = 0b1010_0101;
U16 color = 0xFF_80;
I32 perms = 0o755;
I64 big = 1_000_000;
"{mask} {color} {perms} {big}\n";
"{mask:b} {color:x} {I16.min} {I16.max}\n";
}
165 65408 493 1000000
10100101 ff80 -32768 32767
A literal that cannot fit its type is a compile error, C2003. For example, U8 b = 256; is refused.
try this
- Change
high_mmto-3_276_900. What do the three conversions give? Which one faults? - Write a
U8checksum of the bytes 200, 100 and 50, where the sum is meant to wrap. Which conversion, or which operator from 201-03, says that?