Description
Some background and comparisons can be found in the following blog articles: On arithmetics and overflow, Overflow trapping in practice.
Current rules in Zig using peer resolution + assigned type has issues. One is that occasionally the type of a literal cannot be resolved, but also the peer resolution gives unexpected results. This proposal does affect the mod arithmetics as well, since they become less useful.
The main idea of the proposal is make all integer arithmetics act as if it was done with bigint, but trap / UB if the conversion any truncation occurs.
EDIT I've updated the algorithm quite a bit, although the overall behaviour (as stated above) should be the same.
EDIT2 Added blog articles as references.
To explain the algorithm, we need to define the following:
- The target type, this is the type that the variable or parameter has that we assign the expression to. It may be empty.
- The resulting type, this is the type we are performing calculations using.
In addition to this we define a base arithmetics bit width (BABW), which typically is 32 or 64 which is the platform dependent minimum type that is efficient to perform calculations on and a max arithmetics bit width (MABW) which is the corresponding maximum efficient type.
The following occurs:
- For each operand, recursively resolve the type passing down the target type. E.g. analysing
i16 b = (a + b) * c
we first analyse,a
, thenb
, thena + b
andc
, then(a + b) * c
. In each of these we pass along the i16 type. - If casting expression is reached, the target type is set to empty when recursively processing its sub expression.
- For each operand, if it has a fixed type size and it is greater than the target type then this is a compile type error. E.g.
i16 s = 1 + @as(i16, 1)
would be an error as the right sub expression is of type i32, which is wider than i16. - Looking at the operands, find the smallest power of 2 integer type that can represent the ranges of both operands, that is at least the same as BABW. For instance if BABW = 32, then
i16 + i31 => i32
i32 + u32 => i64
i8 + u16 => i32
u8 + u16
=>u32
. This is the resulting type. If the required width would exceed MABW, then this is a compile time error. - If the width of the resulting type is smaller than the bit width of the target type, change the bit width of the resulting type to be the same as the target type. E.g.
u64 = i8 + u16
=> resulting type isi64
. - Perform the calculation, with overflow being trapped (debug mode) or considered undefined behaviour (release mode).
- If the resulting type is of different signedness from the target type, cast the result to the target type. If under/overflow would occur, then this is trapped (debug mode) or considered undefined behaviour (release mode).
Examples:
var x: u8 = 0xFF;
var b: i16 = 0x7FFF;
var z: i32 = x + b + 3;
// Same as today's:
var z: i32 = @as(i32, x) + @as(i32, b) + @as(i32, 3);
// Note that we take advantage of the fact that overflow is UB/Trap
var sz: i16 = x + b + 3;
// Same as today's:
var sz: i16 = @truncate(i16, @as(i32, x) + @as(i32, b) + @as(i32, 3));
var sz2: i16 = @as(i16, x + b + 3);
// Same as today's:
var sz2: i16 = x +% @as(i16, b) +% 3;
Some more complex examples:
var a: i32 = 1;
var b: u8 = 3;
var c: i16 = 4;
var e: u32 = 5;
var f: i64 = 6;
var g: i32 = a + (b * a) / (c * f); // Error, requires explicit cast due to f > i32
var h: i32 = a + (b * c);
// Same as today's
var h: i32 = a + (@as(i32, b) * @as(i32, c));
// Uses overflow UB for + and *
var i: i32 = b + (a * e);
// Same as today's
var i: i32 = @intCast(@as(i64, b) + (@as(i64, a), @as(i64, e)));
// Uses overflow UB for + and *, then truncate to check it is in range.
var j: i32 = e - 10;
// Same as today's
var i: i32 = @intCast(e - @as(u32, 10);
// Note that an extra rule can be inserted to instead make this:
var i: i32 = @intCast(@as(i64, e) - @as(i64, 10));
As an optional extra rule we can say that given an expression with a high type A and a low type B, doing &
with a constant of bit size N will reduce the size of the low type to N:
var a: i16 = 12345;
var b: u8 = a & 0xFF;
var c: u1 = a & 0x01;
Note that #7416 also touches on some aspects of this.