Types
Numeric conversions and working types
How operands of different numeric types combine, and when a value is converted on its way into a typed variable, field, parameter or return.
since 0.1.0-alpha.1linuxwindowswasm
Description
Two things decide how numbers of different types behave.
- The working type of an operator. The operands are joined to a common type, the operation is computed in it, and the result has that type.
- The storage boundary. When a value is written into a slot with an explicit numeric type (a typed variable, field, parameter, return value, array element), it is converted to that type exactly once, after the whole right-hand expression has been computed in its own working type.
var and let keep the working type of the expression they are initialized from. The order of the operands never matters.
Working types and storage boundaries
void demo() {
var a = 10 / 4.0;
var b = 2 + 2.0;
bool d = 2.0 == 2;
console.writeln(a);
console.writeln(b);
console.writeln(d);
int n = 200.75 + 2000;
float f = 5;
console.writeln(n);
console.writeln(f);
n += 2.9;
console.writeln(n);
console.writeln((7.9).toInt() + 0.5);
}
demo();
2.500000
4.000000
true
2200
5.000000
2202
7.500000
Rules
Working type of a numeric operator.
- If either operand is floating, the operator computes in the float format whose storage width is the wider of the two operands:
floatwith anything isfloat,int16 + float8isfloat16,float16 + int32isfloat32. - Two integers of the same signedness give the wider of the two.
byte + intisint, andint8 + int16isint16. - A mixed signed and unsigned pair gives the smallest signed type that covers both ranges:
int8 + byteisint16.uintwith any signed integer isint. - The result is reduced to the working type, so integer operators wrap at the working width and narrow-float operators re-round in the working format. See
lang.sized-integersandlang.sized-floats. - Comparisons convert both operands to the same working type and return
bool. +with astringoperand is not numeric promotion. Either order converts the other operand to text and concatenates:"n=" + 5and5 + "n"are strings.
Storage boundaries.
- An explicitly typed declaration, field, parameter, return value, assignment, collection element or
for-invariable is a boundary. The incoming value is computed in its own working type and converted once.int n = 200.75 + 2000;computes2200.75as a float and stores2200. - A function's declared return type is a boundary, even for a lambda whose function type is known.
float half(int n) => n / 2;computes the integer division5 / 2 = 2first and returns2.0. - The same holds for generic parameters after substitution, and when a function is called through a function-typed value.
- Compound assignment computes the promoted result, converts it to the variable's type, and stores it:
n += 2.9;is a float addition followed by a truncating store into anint. - The arms of a conditional expression
c ? a : bare joined to one working type before the selected value leaves the conditional. An enclosing typed target is a separate final boundary. - Elements of an array literal are converted to the declared element type, including elements spread from a range:
Array<float> ys = [1..3];stores floats. - A numeric optional such as
float?keepsNoneand converts a present value. A non-optional numeric type rejectsNone.
Conversion rules.
inttofloatrounds to the nearest binary64 value.floattointtruncates toward zero, and throws a catchableRuntimeExceptionfor NaN, infinity or a value outside the 64-bit range.- A wide integer literal stored into a typed slot is converted from its own digits, not through a signed 64-bit overflow first:
float f = 18446744073709551615;stores18446744073709551616.0, not-1.0. - A conversion method such as
f.toInt()orn.toFloat()is an explicit boundary in the middle of an expression. Parentheses alone are not:(7.9).toInt() + 0.5is7.500000. - Narrowing between sized types follows the rules in
lang.sized-integersandlang.sized-floats: wrapping arithmetic never throws, a typed conversion that cannot represent the value does.
Overload selection.
- When overloads differ by numeric parameter type, an exact match is preferred, then a lossless conversion, then a lossy contextual conversion. Only the chosen overload converts its arguments.
- Two overloads that tie on different numeric targets make the call ambiguous, and the compiler reports "ambiguous numeric argument ... convert it explicitly". The result never depends on declaration order. Convert the argument explicitly to choose.
Examples
Boundaries on returns, arrays and optionals:
Storage boundaries
float half(int n) => n / 2;
int whole(float f) => f;
void demo() {
bool flag = true;
int s = flag ? 7.9 : 1;
console.writeln(s);
console.writeln(half(5));
console.writeln(whole(3.99));
Array<float> fs = [1, 2];
Array<float> ys = [1..3];
console.writeln(fs);
console.writeln(ys);
float? opt = 4;
console.writeln(opt);
}
demo();
7
2.000000
3
[1.000000, 2.000000]
[1.000000, 2.000000, 3.000000]
4.000000
Overload selection, and string concatenation:
Overloads and string +
string pick(int x) => "int";
string pick(float x) => "float";
string size(float x) => "float";
string size(int8 x) => "int8";
console.writeln(pick(1));
console.writeln(pick(1.5));
byte b = 3;
console.writeln(size(b));
console.writeln("n=" + 5);
console.writeln(5 + "n");
console.writeln("x" + 1.5);
int
float
float
n=5
5n
x1.500000
Notes
In the last example size(b) selects size(float) because converting a byte to float loses nothing, while converting it to int8 can.
See also
- Sized integers: byte, int8, int16, int32, uint — The fixed-width integer types, how arithmetic wraps, how mixed widths combine, and when conversions throw.
- Sized floats: float8, float16, float32 — The narrow floating-point formats, how every operation re-rounds, and where they overflow or throw.
- int — The signed 64-bit integer type, the default type of whole numbers.
- float — A 64-bit IEEE 754 floating-point number (binary64).