This proposal introduces four new number types: int
, uint
, int<N>
and uint<N>
, where N is any positive integer literal. An int
is signed, it can contain negative integers, positive integers and zero. An uint
is an unsigned integer, it can contain positive integers and zero. int<N>
and uint<N>
are integers limited to N
bits:
int<N>
can contain integers in the range -Math.pow(2, N-1) ... Math.pow(2, N-1) - 1
.uint<N>
can contain integers in the range 0 ... Math.pow(2, N) - 1
This proposal doesn't use type information for emit. That means this doesn't break compilation using isolatedModules
.
Note: since JavaScript uses 64 bit floating point numbers, not all integers can be used at runtime. Declaring a variable with type like uint<1000>
doesn't mean it can actually store a number like Math.pow(2, 999)
. These types are here for completeness and they can be used in type widening as used in type inference of binary operators. Most languages only support integer types with 8, 16, 32 and 64 bits. Integers with other size are supported because they are used in the type inference algorithm.
Ideas were borrowed from:
int
and uint
are both subtypes of number
. These types are the easiest integer types. They are not limited to a certain amount of bits. int<N>
and uint<N>
are subtypes of int
and uint
. These types have a limited size.
In languages like C, the result type of an operator is usually the same as the input type. That means you can get strange behavior, and possibly bugs:
uint8_t a = 200;
int b = a + a; // = 144 = 400 % 256, not 400
float c = 1 / 2; // = 0, not 0.5
To mimic that behavior we would need to add type converters everywhere in the emitted code, and those heavily rely on type information. We don't want that, so instead we widen the return types. That means there is no runtime overhead. For example, adding two values of type uint<8>
would result in uint<9>
. To assign that to a uint<8>
, you can use an conversion function, like uint<8>(x)
. That function converts x to an uint<8>
.
This design means that operations like x++
, x += 10
and x /= 2
are not allowed, as they are not safe. Instead you can use x = uint<8>(x + 1)
and x = uint<8>(x / 2)
.
int
and uint
allow more operations. Since they are not defined with a limited size, x++
and x += 10
are allowed. x--
is only allowed on an int
, as an uint
might become negative. Below is an overview of which operators are allowed on which number types.
Since emit isn't based on type information, integers can be used in generics. They can also be used in unions.
int
and uint
are assignable to number
uint
is assignable to int
int<N>
is assignable to int
and number
uint<N>
is assignable to int
, uint
and number
int<N>
is assignable to int<M>
iff N <= Muint<N>
is assignable to uint<M>
iff N <= Mint<N>
is not assignable to uint<M>
for all N, Muint<N>
is assignable to int<M>
iff N < MInfinity
, -Infinity
and NaN
are not assignable to any integer type.If a type is not assignable to some other type, you can use a normal cast (<int<8>> x
or x as int<8>
), which has no impact at runtime, or a cast function (int<8>(x)
).
A cast function takes a number and converts it to the target integer type.
Syntax:
int<N>(x);
uint<N>(x);
int(x); // Alias of int<32>(x);
uint(x); // Alias of uint<32>(x);
Note: even though an int
doesn't have a fixed size, we use the 32 bit cast function as that's easy and fast JavaScript.
Semantics:
This gives the same behavior as type casts in languages like C. If the operand is not a number, TS should give a compile time error. Emit should succeed (unless --noEmitOnError
is set), the operand should be converted to a number at runtime, using same semantics as +x
. undefined
and null
should also be converted the same way.
Implemented in TypeScript:
function castToInt(n: int, x: number) {
x = +x;
const m = Math.pow(2, n - 1);
if (x > 0) {
x = Math.floor(x);
while (x > m) x -= 2 * m;
} else {
x = Math.ceil(x);
while (x < -m) x += 2 * m;
}
return x;
}
function castToUint(n: int, x: number) {
x = +x;
const m = Math.pow(2, n);
if (x > 0) {
x = Math.floor(x);
while (x > m) x -= m;
} else {
x = Math.ceil(x);
while (x < 0) x += m;
}
return x;
}
These functions are not always used in the generated code. When n <= 32
, these functions are not needed.
The generated code:
If n === 32
:
int<32>(x); // TS
x | 0; // JS
uint<32>(x); // TS
x >>> 0; // JS
If n < 32
,
uint(x);
uint<n>(x);
(x | 0) & b; // where b = pow(2, n) - 1
int(x);
int<n>(x);
(x | 0) << a >> a;
// where a = 32 - n
// Examples:
int<8>(x);
(x | 0) << 24 >> 24
uint<8>(x);
(x | 0) & 255;
Question: can we make the emit of int<n>(x)
better? The current isn't very nice and performs bad.
Solved it using http://blog.vjeux.com/2013/javascript/conversion-from-uint8-to-int8-x-24.html
If n > 32
:
uint<n>(x);
__castToUint(n, x);
int<n>(x);
__castToInt(n, x);
__castToUint
and __castToInt
are the functions above, emitted as helper functions.
You can only use these cast functions in call expressions:
int(x); // Ok
let intFunction = int; // Error
[1].map(int); // Error
We cannot solve that with helper functions, as int
wouldn't be equal to int
if they don't come from the same file when using external modules.
Instead we should dissallow this.
Introducing integer types can break existing code, like this:
let x = 1;
x = 1.5;
But we do want to type 1
as an int
:
let y: int = 1; // Don't show error that `number` is not assignable to `int`
There are several options:
In option two we infer to int
, as let a = 0
would otherwise infer to uint<1>
, which would mean that the variable can only contain
0 and 1.
Examples of option 3:
let a: int = 1;
let b = a + 3; // int
let c = 1 + 1; // number
function returnInt(): int {
return 1;
}
let d = returnInt() - 1; // int
let e = int(1) * 1; // int
let f = <int> 1 + 1; // int
let g = <number> a + 1; // number
A literal will be infered to the smallest integer type that can contain the number. Examples:
0 -> uint<1>
1 -> uint<1>
2 -> uint<2>
-1 -> int<1>
-2 -> int<2>
Operators should always infer to the smallest integer type that can contain all possible values.
(int, int<N>, uint or uint<N>) + number -> number
int + any integer type -> int
uint + (uint or uint<N>) -> uint
int<N> + int<M> -> int<max(N, M) + 1>
int<N> + uint<M> -> int<max(N, M + 1) + 1>
uint<N> + uint<M> -> uint<max(N, M) + 1>
- is almost the same as +, with two exceptions:
uint - (uint or uint<N>) -> int
uint<N> - uint<M> -> int<max(N, M) + 1>
int * (uint, int) -> int
uint * uint -> uint
int<N> * int<M> -> int<N + M>
(int<N + M - 1> is not big enough, consider -2^(N-1) * -2^(M-1) = 2^(N + M - 2)
/ always return `number`
int % (int, uint, int<N>, uint<N>) -> int
uint % (int, uint, int<N>, uint<N>) -> uint
int<N> % (int, uint) -> int<N>
uint<N> % (int, uint) -> uint<N>
int<N> % int<M> -> int<min(N, M)>
int<N> % uint<M> -> int<min(N, M+1)>
uint<N> % int<M> -> uint<max(min(N, M - 1), 1)>
uint<N> % uint<M> -> uint<min(N, M)>
int & (int or int<N>) -> int<32>
int<N> & int<M> -> int<min(N, M, 32)>
uint<N> follows the rules of int<N+1>
(uint & uint !== uint, for example 4294967295 & 4294967295 === -1)
| and ^ have the same behavior as &
~int -> int
~uint -> int
~int<N> -> int<N>
~uint<N> -> int<N + 1>
<< always returns an int<32>
(number, uint) >> any -> uint<32>
int >> any -> int<32>
int<N> >> any -> int<min(N, 32)>
uint<N> >> any -> uint<min(N, 32)>
(number, uint or int) >> any -> uint<32>
int<N> >>> any -> uint<32> (Consider -1 (int<1>), -1 >>> 0 === 429467295 === max value of uint<32>
uint<N> >>> any -> uint<min(N - 1, 32)>
Certain assignment operators are not supported on integer types. In short:
Let Op
be an operator. x Op= y
is allowed iff x = x Op y
is allowed.
That means that the following operators are not supported and usage will give an error:
int: x /= y
uint: x /= y, x--, --x
int<N>, uint<N>: x /= y, x++, ++x, x--, --x, x += y, x -= y, x *= y
int<N>
if N < 32, then: x <<= y
if N <= 32, then: x >>>= y
uint<N>
if N < 32, then: x <<= y, x >>>= y
Type inference can change depending on how it will be implemented. Also changing existing definitions can break things:
let arr = [];
let length = arr.length;
length = 1.5;
Changing the definition of the length
property to a uint
would break this code, though I don't think this pattern will be used a lot.
Such problems can easily be fixed using a type annotation (in this case let length: number = arr.length;
).
number
? Thus, let x: int
vs. let x: number.int
int<8>
/number.int<8>
or int8
/number.int8
?int<n>(x)
(n < 32) better? The current looks and performs bad.undefined
and null
be assigned to an integer type? The conversion functions convert them to 0. Allowing undefined
would mean that int + int
could be NaN
(if one operand is undefined
), while NaN
is not assignable to an int
. I'd say that undefined
and null
shouldn't be assignable to an integer type, and that declaring a variable (also class property) with an integer type and without an initializer would be an error.All feedback is welcome. If you're responding to one of these questions, please include the number of the question. If this proposal will be accepted, I can try to create a PR for this.
For the more common cases of uint8
, uint16
, uint32
, int8
, int16
, int32
there are simple tricks/bit "twiddles" that can make the truncations much faster/simpler. I tried to make them fit in one statement (sacrificing a tiny bit of performance in some), to make them better candidates for inlining (either through the JS runtime or TypeScript itself).
Unsigned:
function toUint8(num: number): uint8 {
return num & 255;
}
function toUint16(num: number): uint16 {
return num & 65535;
}
function toUint32(num: number): uint32 {
return (num | 0) >= 0 ? (num | 0) : (num | 0) + 4294967296;
}
Signed:
function toInt8(num: number): int8 {
return (num & 255) < 128 ? (num & 255) : (num & 255) - 256;
}
function toInt16(num: number): int16 {
return (num & 65535) < 32768 ? (num & 65535) : (num & 65535) - 65536;
}
function toInt32(num: number): int32 {
return num | 0;
}
These functions would truncate to the least significant 8
, 16
or 32
integer bits of any floating point number. Numbers outside of the range [-2^52..2^52 - 1]
would still work but would be naturally limited in resolution.
I've ran some automated testing for these with millions of random values against the expected truncation behavior in typed arrays and they seem to give exactly similar results including when the number is NaN
, -Infinity
and +Infinity
, null
, undefined
, or any other type, including a string
or an object
. anything | 0
and anything & num
always resolve to 0
in these cases [with the special exception of boolean
where true
is automatically converted to 1
and false
to 0
].
There is an alternative method to do this that may or may not be faster (depending on the runtime), but would require runtime support for typed arrays (only available in IE10+):
Unsigned:
uint8Array_dummy = new Uint8Array(1);
uint16Array_dummy = new Uint16Array(1);
uint32Array_dummy = new Uint32Array(1);
function toUint8(num: number): uint8 {
uint8Array_dummy[0] = num;
return uint8Array_dummy[0];
}
function toUint16(num: number): uint16 {
uint16Array_dummy[0] = num;
return uint16Array_dummy[0];
}
function toUint32(num: number): uint32 {
uint32Array_dummy[0] = num;
return uint32Array_dummy[0];
}
Signed:
int8Array_dummy = new Int8Array(1);
int16Array_dummy = new Int16Array(1);
int32Array_dummy = new Int32Array(1);
function toInt8(num: number): int8 {
int8Array_dummy[0] = num;
return int8Array_dummy[0];
}
function toInt16(num: number): int16 {
int16Array_dummy[0] = num;
return int16Array_dummy[0];
}
function toInt32(num: number): int32 {
int32Array_dummy[0] = num;
return int32Array_dummy[0];
}
Anyway, this should be used as a reference to test the correctness of the truncations.
[Edit: I retested the first version and it does seem to return 0
for NaN
, Infinity
, undefined
etc. as expected, the problems happened in previous versions of the functions]
I've changed the emit for int<n>(x)
where n < 32
. It now uses bit shifting as explained here. This is probably the best solution for question 3.
@rotemdan Those conversions exist for all integer types up to 32 bits. You can see them all in the section "Cast function". TS should inline these functions, and browsers can optimize these constructs. The castToInt
and castToUint
are functions that demonstrate the expected behavior, the alternatives (like x | 0
) follow that behavior.
@ivogabe
Great find! so let's try to summarize the simplest, most efficient implementations we have so far for the most important cases:
Unsigned:
num & 255
num & 65535
num >>> 0
Signed:
(num << 24) >> 24
(num << 16) >> 16
num | 0
I ran them through the automated tests (1M random FP values up to about +/- 2^71) and they seem to work correctly.
It seems reasonable to have the behavior standardized against assignments to typed arrays so these should all yield 0
for NaN
, undefined
, string
etc. not sure about boolean
(right now some of these will give 1
for true
but that's a really minor detail).
[Edit: I confirmed typedArray[0] = true
would set the value to 1
, so there is no need for a correction there.]
Another minor detail: I'm not sure if adding | 0
is needed for to be recognized as integers by asm.js (I'm not very familiar with it), I assume the JS runtime could be "smart" enough to recognize them?
@rotemdan Most JS engines can optimize | 0
very well, but other binary operators are less optimized. I haven't measured the difference though. NaN, +/-Infinity, undefined and null should be converted to 0, strings are first converted to a number (eg "120" -> 120, "1.5" -> 1.5 -> 1). For booleans, true is converted to 1 and false to 0. That behavior is consistent between the proposed casts and typed arrays.
You mentioned that operations like x++
, x += 10
and x /= 2
are not allowed without a cast. I understand the desire for safety but I still think that having an automatic cast/truncation on assignment is still reasonable for practical purposes (and is actually a useful/desirable thing to have for some applications).
I mean that:
let num: uint8 = 255;
num = num + 1;
Would emit:
var num = 255;
num = num + 1;
num &= 255;
The automatic casts would only happen on assignments, "anonymously typed" intermediate results would not be effected:
let num: uint8 = 255;
num = (num * num) + (num * 2);
Still emits only a single truncation:
var num = 255;
num = (num * num) + (num * 2);
num &= 255;
The reason I wanted to "optimize" and simplify the casts (including the logic for them) as much as possible was because I wanted to make them extremely cheap for these purposes (In terms of performance, I think the operations turned up cheap enough to be called very frequently, including in tight loops).
Since typed arrays automatically truncate on assignment and don't error on overflows or even on type mismatches, it seems like a reasonable compromise to have a somewhat more permissive logic. Not having these automatic casts would make it difficult to work with these types in practice (in some cases an explicit cast would be needed at every single line).
It is still possible to limit automatic casts/truncations to only work between integer types, though they would not be required to be compatible:
let num: uint8 = 255;
num = num * 23; // OK, truncated to least significant 8 bits on assignment. ("modulo 2^8").
num = num * 23.23423425; // Error, incompatible numeric types
num = <uint8> num * 23.23423425; // OK
_Edit_: It is possible to limit this even further to only apply to anonymous/intermediate expressions. Assignments between named variables may still be strict:
let num1: uint8 = 45;
let num2: uint16 = 11533;
num1 = num2; // Error: incompatible numeric types
num1 = <uint8> num2; // OK
It might be possible to strengthen it back with even more restrictions. I guess there's a balance here between safety and usability.
@rotemdan The behavior you suggest would require emit based on type information. That means isolatedModules
/ transpile
(compilation per file in stead of one compilation of the whole project) cannot be used with integers. The key feature of this proposal is that it does not use type information for emit. That way the feature fits better in TypeScript.
If you want less restrictions you can use int
or uint
, which are not restricted to a specific size. You can write this:
let x: uint = 0;
x++;
The int
and uint
types give less guarantees (the only guarantees are that a value is round and in case of an uint
, not negative), but are easier to use. In most cases you can use these types.
@ivogabe Very correct.
I would think it would make more sense to just start out with four types:
int
- A 32-bit signed integer. This would go in line with most of JavaScript's bitwise operators.uint
- A 32-bit unsigned integer. This would account for the following cases (and a few others on the Math object):int >>> int -> uint
uint >>> (int | uint) -> uint
int + int -> uint
int - int -> uint
float
- Math.fround(x) -> float
double
- Everything else.This is very similar to the asm.js types, and are already heavily optimized (V8 and Spidermonkey both generate type-specific code already for non-asm.js code that rely on these types). Matter of fact, almost all the asm.js types can be represented by these types + the type system.
| asm.js | TypeScript |
| :-: | :-: |
| void
| void
|
| signed
| int
|
| unsigned
| uint
|
| int
| int | uint
|
| fixnum
| int & uint
|
| intish
| int | uint
|
| double
| double
|
| double?
| double | void
|
| float
| float
|
| float?
| float | void
|
| floatish
| float | double
|
| extern
| any
|
Note: asm.js requires intish
to be coerced back into int
or uint
and floatish
to be coerced back into double
or float
. TypeScript wouldn't require it, but people can still remain explicit when necessary.
As for implementation, there is no realistic way to guarantee these types without relying on explicit coercions.
int -> x | 0
uint -> x >>> 0
int & uint -> x & <int & uint> y // e.g. y = 0x7fffffff
float -> Math.fround(x) or a Float32Array member
double -> +x
// int, uint, and float are each subtypes of double
// double is a subtype of number
function f(x: number): int {
// Error!
// return x
return x | 0
}
Even asm.js has to use such coercions to keep the math correct. Unlike asm.js, though, you don't have to coerce everything, so you don't have to write expr(x) | 0
a bunch of times. You also don't have to coerce your arguments, as the argument types are checked statically.
Floats and doubles would indeed be a good addition. For those who don't see the advantage, I'd suggest reading this: https://blog.mozilla.org/javascript/2013/11/07/efficient-float32-arithmetic-in-javascript/. I would call these types float32
and float64
, as that's more in line with the names for typed arrays and SIMD in JS.
The reason I chose to introduce more integer types is type safety. int32 + int32
isn't always an int32
, so we must either add a conversion around it (implicitly), which would require type information, or widen the type to an int33
. I chose the latter. The user would need to add the conversions himself. If he doesn't want that, he can use the 'unsized' integer type int
, which doesn't check the size.
My proposal introduces syntactic sugar for those casts, as I'd rather write int(x)
than x | 0
and uint<8>(x)
than (x | 0) & 256
. Of course this can be extended to floats: float32(x)
-> Math.fround(x)
and float64(x)
-> +x
.
@impinball int + int -> uint
isn't always true, consider 1 + -3 === -2
@ivogabe I forgot about that... Oops. ;)
And I'm aware that type casts that end up in the emit would definitely be useful. But I'm taking into account the TypeScript compiler devs generally steer away from it.
If you want to create a third party compiler (a la CoffeeScriptRedux) with that as a nonstandard extension, go for it!
Or even edit the current one to do that, that'd still be fine.
And as for the link, I read it while creating this post. (I optimized the polyfill shortly after.)
One thing that will have to be addressed with yours is right shifts with integers smaller than 32-bit. That can easily bring garbage in. And that would require those types making it into the emit.
@impinball At first I found it strange that they didn't want to use type info, but now I think they're right about that. Using isolatedModules
compilation times can get decreased from a few seconds to less than a second. Introducing a feature that would break that would be a very bad idea. Someone of the team can explain this better probably.
I don't really like the idea of forking, it's not hard to create a fork, maintenance is usually the problem.
One thing that will have to be addressed with yours is right shifts with integers smaller than 32-bit. That can easily bring garbage in. And that would require those types making it into the emit.
I'm not sure what you mean with this, can you clarify that? Right shift (x >> y
) removes bits and is sign preserving, so it's guaranteed that abs(x >> y) <= abs(x)
. Left shift doesn't have such guarantee, it should always infer to int<32>
.
@ivogabe
Where the shifts can lead to unexpected behavior is this:
Let's assume only the rightmost 8 bits are used. JavaScript numbers are only 32-bit, but for the sake of brevity, let's assume they're 16-bit.
Let's try a left shift:
|-------| <-- The digits we care about
0000 0110 1100 0111 <-- x
0001 1011 0001 1100 <-- x << 2, okay if uint8
0000 0110 1100 0111 <-- original x
0000 0001 1011 0001 <-- x >> 2, naive
0000 0000 0011 0001 <-- x >> 2, correct
The example of the left shift introduces artifacts for signedness because then if you try to add, it's adding a positive. The example of the right shift is that you're literally introducing garbage into the program, like what you can get from allocating a raw memory pointer without zeroing the region out first.
Now, let's assume only the leftmost 8 bits are used. That'll address the issue of sign propagation and addition, subtraction, etc., but what about left and right shifts?
|-------| <-- The part we are using
1101 1001 1100 0000 <-- x
0110 0111 0000 0000 <-- x << 2, naive
0110 0100 0000 0000 <-- x << 2, correct
Now, there's a similar garbage problem with left shifts. Simply because the other half wasn't zeroed out first.
Obviously, these are only 16-bit integers and JavaScript uses 32-bit for its bitwise operations, but the argument could easily be expanded to cover those.
In order to fix this, you need those types to correct the emit. And I didn't get into addition, subtraction, etc., because that's already been made.
As another nit, I don't like the generic syntax, because a) you'd have to special case the parser (numbers aren't valid identifiers for generics), b) they're a finite, limited set of types, not some sort of parameterized type, and c) it doesn't seem right with the rest of the language, as primitives are just a simple identifier word.
And every time I can recall a suggestion that would use/require type info in the emit, they've declined it. Including that of optimizing for types (they declared that out of scope).
@impinball I don't think you understand the idea. If the rightmost 8 bits are used, I think you mean we're using an int8. If that's the case, the value must be in the range -128, 127. That means the 8 rightmost bits are used to represent the value, the other bits have the same value as the 8th bit, which is 0 for a positive number and 1 for a negative number. There's no situation where only the leftmost 8 bits are used. There is no difference in representation of the same number in different types (though the JS engine might do that internally).
In short, by using compile time checks we know that a number is of a certain size. You can trick the type system with casts (<int8> 100000
), but that's already possible with the types we have today. If there are no compile errors and you don't use such casts, types should always be right. The compiler doesn't add implicit casts, but instead it errors on places where the user should add them.
When you use the >>
operator on an int<N>
or uint<N>
, the result will be int<min(N, 32)>
or uint<min(N, 31)>
, since JS uses 32 bit signed integers for bitwise operators. The <<
operator always returns an int<32>
, we have no guarantees of what the result might look like.
I'll demonstrate this using some code:
let a: int32;
let b: int8;
b = 200; // Compile time error, 200 not in the range of an int8
b = 100; // Ok, since -128 <= 100 <= 127
// b >> a should return an int8, so this is ok
b = b >> a;
// b << a should return an int32, so
a = b << a; // this is ok
b = b << a; // and this is a (compile time) error
b = int8(b << a); // but this is ok
The int8( ... )
call converts the operand to an int8. This is emitted as ((b << a) | 0) << 24 >> 24
. It removes the garbage of the 24 bits on the left.
I'm not yet sure about the syntax, too. I chose the generics style, since it's a family of types that have a small difference between them. You can describe generics on interface or objects the same way. There is no limit on the size of the integer, you can declare an int<100>
, but not all values of such int are safe in JS, since JS uses a double to store the value. All integers up to int<53>
are safe. An int<54>
is almost safe, only -Math.pow(2, 53)
is not safe. Also I didn't like adding a big (~ infinite) list of types top level. That could be solved by namespacing them like number.int8
. Would like to hear what everyone thinks about this.
When will this be implemented ?
@xooxdoo I don't think this got to any conclusive decision, and for what it's worth, this is probably not as important to me as some of the other more recent additions like non-nullable types.
I'm with this, one thing I've found lacking is more types of numbers in typescript. There's definitely a difference between an byte, int, float, and double but there's no difference in typescript at the moment. I'd love to require a specific type of number than a generic number type.
I think addition of int and float types will pave the way to efficient WebAssembly code generation in the future
TypeScript needs more refined types for numbers indeed. At least int
and float
to begin with.
@yahiko00 Well javascript numbers are by default doubles, so we should probably have a double type to start with.
As a tribute to Turbo Pascal, I would suggest integer
and real
for the names of these new numeric types. The advantage is they do not suggest a specific implementation (number of words), in the same way as number
.
@yahiko00 I'd personally prefer int
over integer
(3 vs 7 characters to type), but I agree. I disagree that real
should exist, because number
is already sort-of a real
in practice (it's technically mandated by the ECMAScript standard to be a IEEE 754 double-precision floating-point number), but having mandated integers would be nice.
I will note that int + int
should yield int
, though, just for convenience, even though it's technically incorrect for anything that adds up higher than 253-1.
Int over Integer is also preferable, since the typed array classes all start with "Int".
Great post, I think that Flash ES4/AS3 (Actionscript) implementation for int, uint, would be a very good start point for Typescript.
Also we should have a look to WebAssembly constraints, in order to prepare some possibilities for compile some parts of Typescript in AST?
Any idea for an implementation date?
Cast functions (e.g. int<n>(x)
) in the proposal sound like expression level syntax which is listed as a thing to avoid in the design goal. However, they could be implemented in a 3rd party library (along with *.d.ts file) once proper type system gets done.
It'll require some language level support (at least nominal subtypes and
operator overloads for each). But I agree most of it (like casting) doesn't
have to belong in core.
On Wed, Aug 17, 2016, 03:38 Nobuhiro Nakamura [email protected]
wrote:
Cast functions (e.g. int
(x)) in the proposal sound like expression
level syntax which is listed as a thing to avoid in the design goal
https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals.
However, they could be implemented in a 3rd party library (along with
*.d.ts file) once proper type system gets done.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/4639#issuecomment-240336660,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AERrBORCcgKCvTtYHQc55lcKQGPogyCSks5qgrp7gaJpZM4F3_Wi
.
Being able to compile TypeScript to WebAssembly would be gahmazing. If this brings it closer, yes indeedy.
@matthew-dean This is offtopic, but the issue with compiling TypeScript -> WebAssembly is that even though TypeScript has types, you'd still have to implement JavaScript's entire engine (and all its quirks.)
integer
types are not affecting the semantic of the operators, except for validation.
var x: integer;
var y: integer;
var z = x + y; // only number, sorry
var z_fix = (x + y)|0; // now this is integer!
Effectively, we align with ASM.js: there are exact rules for checking types, but the onus is on the code writer to keep it safe.
That would fit into isolatedModules
compilation too!
integer
will remain integer
.The new emit pipeline (introduced for downlevelling async/await
) can be made extendable for the latter. Some obvious targets for TS->* are:
All of them strongly rely on integer
type/types.
Some obvious targets for TS->* are:
I think wasm target is too long-term objective, a mere typing for emscripten-generated code may be in shorter term.
True. Technically, the proposal is not about delivering those use-cases — rather enabling them in future.
BTW, my hidden agenda is GLSL shader language. It's very powerful, but at the moment the tooling is all semi-safe string concatenation and kludges.
I like the idea, but I have a few nits to point out inline.
On Thu, Dec 15, 2016, 09:24 mihailik notifications@github.com wrote:
Non-emit-affecting proposal
integer types are not affecting the semantic of the operators, except for
validation.var x: integer;var y: integer;
var z = x + y; // only number, sorryAddition and subtraction of integers always result in integers. If you're
willing to count the infinities as integers, you don't need the cast.
var z_fix = (x + y)|0; // now this is integer!
Effectively, we align with ASM.js: there are exact rules for checking
types, but the onus is on the code writer to keep it safe.That would fit into isolatedModules compilation too!
With no fancy type-safe emit, what's the valueadd?
- Minor feature: enforce and check a field declared as integer will
remain integer.- Major enabler: cross-compilation scenarios from Typescript into
other targets.The new emit pipeline (introduced for downlevelling async/await) can be
made extendable for the latter. Some obvious targets for TS->* are:
- ASM.js
- GLSL shader language
- WebAssembly
- LLVM bytecode
All of them strongly rely on integer type/types.
You need not only generic integers, but 8-bit, 16-bit, and 32-bit integers
(64-bit for 64-bit LLVM) for that to work. The only exception is asm.js,
which exclusively operates with 32-bit integers.
There's also the issue of GC (or the lack thereof).
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/4639#issuecomment-267339184,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AERrBOcr7b2oh-JRnWWm5-_Jibz73KBqks5rIU2IgaJpZM4F3_Wi
.
Addition and subtraction of integers always result in integers.
I didn't know that, but how about in this case?
let a = Number.MAX_SAFE_INTEGER;
for (let i = 0; i < 100; i++) {
a += a;
}
// a: 1.1417981541647678e+46
My suggestion is to stick to ASM.js treatment: require canonic 32-bit integer and no floating-point edge cases.
8-bit, 16-bit and unsigned/signed variants may exist too, in the same spirit of strict operator checks.
var x: int8;
var y: int8;
var z: int8 = x + y; // FAIL
var z_fix: int8 = (x+y) & 0xFF; // GOOD: explicit coercion to byte
Of course literal types should be compatible with corresponding integer types. Statically-known expressions ideally should retain that quality too.
function withByte(b: int8);
withByte(1); // GOOD
withByte(1000); // FAIL
withByte(10*10); // GOOD
withByte(10*10*10); // FAIL
@SaschaNaz I forgot the "assuming Infinity
and -Infinity
are counted as integers". My bad.
I didn't know that, but how about in this case?
It's a loss of precision, but in theory, it's still an integer despite the imprecision.
Any progress on it?
This will be very useful for me! So I'm +1 on this feature.
Holding off on this until ES gets the int64 type or we get Web Assembly as a compile target
That's a pity. I'm actually working on a TS to WebAssembly prototype as part of my master thesis and hoped that this feature would land soon so that I can take profit of using ints in the computation instead of doubles.
In this case, I might have to fall back to a simple implementation that just adds int as a TS type but without changing the emitting of JS Code and type inference.
@RyanCavanaugh 64-bit ints aren't happening, bigints are now being tried.
Apparently, current TS already supports a subset of my proposal above:
type byte = 0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116|117|118|119|120|121|122|123|124|125|126|127|128|129|130|131|132|133|134|135|136|137|138|139|140|141|142|143|144|145|146|147|148|149|150|151|152|153|154|155|156|157|158|159|160|161|162|163|164|165|166|167|168|169|170|171|172|173|174|175|176|177|178|179|180|181|182|183|184|185|186|187|188|189|190|191|192|193|194|195|196|197|198|199|200|201|202|203|204|205|206|207|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|223|224|225|226|227|228|229|230|231|232|233|234|235|236|237|238|239|240|241|242|243|244|245|246|247|248|249|250|251|252|253|254|255;
type sbyte = -128|-127|-126|-125|-124|-123|-122|-121|-120|-119|-118|-117|-116|-115|-114|-113|-112|-111|-110|-109|-108|-107|-106|-105|-104|-103|-102|-101|-100|-99|-98|-97|-96|-95|-94|-93|-92|-91|-90|-89|-88|-87|-86|-85|-84|-83|-82|-81|-80|-79|-78|-77|-76|-75|-74|-73|-72|-71|-70|-69|-68|-67|-66|-65|-64|-63|-62|-61|-60|-59|-58|-57|-56|-55|-54|-53|-52|-51|-50|-49|-48|-47|-46|-45|-44|-43|-42|-41|-40|-39|-38|-37|-36|-35|-34|-33|-32|-31|-30|-29|-28|-27|-26|-25|-24|-23|-22|-21|-20|-19|-18|-17|-16|-15|-14|-13|-12|-11|-10|-9|-8|-7|-6|-5|-4|-3|-2|-1|0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116|117|118|119|120|121|122|123|124|125|126|127;
function takeByte(x: byte): byte {
x++;
return x + 1;
// ~~~~~~~~~~~~~ Type 'number' is not assignable to type 'byte'.
}
takeByte(0);
takeByte(10);
takeByte(888);
// ~~~ Argument of type '888' is not assignable to parameter of type 'byte'.
function byte2sbyte(x: byte): sbyte {
if (this) return x + 1;
// ~~~~~~~~~~~~~ Type 'number' is not assignable to type 'byte'.
else return x;
// ~~~~~~~~~ Type 'byte' is not assignable to type 'sbyte'.
// Type '128' is not assignable to type sbyte.
}
The missing pieces are:
myNumber & 0xFF
don't produce byte
) — unlike ASM.js which has coercions that ensure output is always integer (or float).takeByte
function above), increment operator shouldn't be allowed on literal union.int
s would be nice. For example, consider this class:
class KeyFrameManager {
currentFrame:number
// the rest of class exposes API for switching to different frames of a key-framed animation.
}
There's currently no way to enforce that frames should be whole number
s, which means if a developer introduces a calculation that produces a non-whole number there could be strange bugs.
Let us enforce this. đź‘Ť For example:
class KeyFrameManager {
currentFrame:int
// the rest of class exposes API for switching to different frames of a key-framed animation.
}
This is simply a matter of enforcing program correctness for me, I don't care that number
s are actually all the same in JavaScript.
Here is a definition file for numeric types used in AssemblyScript, an experimental compiler from TypeScript to WebAssembly, written in TypeScript: https://github.com/dcodeIO/AssemblyScript/blob/master/assembly.d.ts
This could be a good basis for the future numerical types in the official TypeScript compiler.
Also, until this time, I plan to use this definition file for my projects.
@yahiko00 There's also TurboScript which does this: https://github.com/01alchemist/TurboScript
That's right. I've also had a look at this project but it seems a little but more "experimental" than AssemblyScript, and its definition file is much less complete.
@yahiko00 The main difference is that TurboScript aims to be a bit more fully featured and is closer to a TS derivative rather than AssemblyScript is (which is more or less just a retargeted TS).
@isiahmeadows You are probably right since I am discovering these repos. Although, I like the idea of AssemblyScript to be a subset of TypeScript. Maybe I am wrong, but I think, in the future, this could be easier to merge AssemblyScript into the official TypeScript compiler.
Here is another project, wasm-util, from TypeScript to WebAssembly: https://github.com/rsms/wasm-util
In case you missed https://github.com/Microsoft/TypeScript/issues/15096 I think TC39 BigInt is the best way forward to get true integers in TypeScript.
This proposal would enable an arbitrary precision integer
type and could also hint to JavaScript VMs when they could use e.g. native 64-bit integers, enabling things like an int64
and uint64
type.
The proposal is presently stage 2 and has multi-vendor support. In theory the syntax is mostly stable at this point.
There's a proposal to support it in Babel as well: https://github.com/babel/proposals/issues/2
I like how TypeScript follows the spec and tries not to add too much to it, but an integer type is just so fundamental. Would love minimal integer support that's based on safe integers in ES6. Here's a concise description of it: http://2ality.com/2015/04/numbers-math-es6.html
GraphQL supports signed 32 bit integers and doesn't address other integer types. IMO that's a great starting point. http://graphql.org/learn/schema/
@benatkin if TC39 BigInt ever ships, there's a pretty big drawback to defining integer types based on number
: TC39 BigInt is a separate type at the VM level, and one which for which an exception is raised if mixed number
/BigInt
arithmetic is attempted.
If people were to add int32
types to their type declarations for untyped JavaScript code which are actually applying constraints to number
, and TypeScript were to switch to TC39 BigInts as the underlying JavaScript backing type for int32
, then all code which has int32
in their type declarations would be broken.
The TC39 BigInt proposal is now stage 3 and I think stands a pretty decent chance of eventually shipping, so I think it's probably best to "reserve" any potential integer types for ones which are actually integers at the VM level.
BigInt is scheduled for TS 3.0 (#15096) 🎉! Adding different syntax for "double based integers" as proposed in this issue would be confusing, so I think we can close this. See also #195.
Most helpful comment
int
s would be nice. For example, consider this class:There's currently no way to enforce that frames should be whole
number
s, which means if a developer introduces a calculation that produces a non-whole number there could be strange bugs.Let us enforce this. đź‘Ť For example:
This is simply a matter of enforcing program correctness for me, I don't care that
number
s are actually all the same in JavaScript.