Arithmetic is fundamental to computation. Virgil defines a number of arithmetic operators on the primitive types. Thankfully, these arithmetic operators have well-defined, platform-independent semantics: for the same inputs, they always compute the same result on any platform. (This is not true for languages like C, C++, and Go).
Virgil offers signed integers iN and unsigned integers uN of every width from 1 to 64.
intis an alias for the integer typei32longis an alias for the integer typei64byteis an alias for the integer typeu8
All integers are represented in two's complement, and all arithmetic operations use two's complement. There are no platform-dependent integer types or integer operations. Therefore addition, subtraction, multiplication, division, and modulus "wrap-around" as they do on all modern CPUs. (This is quite unlike C and C++ which define neither the size of integers nor the result of overflowing basic operators).
// with declared type
var a: int = 9 + 3; // add == 12
var b: int = 8 - 2; // subtract == 6
var c: int = 7 * 4; // multiply == 28
var d: int = 9 / 2; // divide == 4
var e: int = 5 % 3; // modulus == 2
// with inferred type
var f = 9 + 3; // add == 12
var g = 8 - 2; // subtract == 6
var h = 7 * 4; // multiply == 28
var i = 9 / 2; // divide == 4
var j = 5 % 3; // modulus == 2
Bitwise and, or, and exclusive-or work as expected.
// with declared type
var a: int = 0xA0 >> 4; // shift right == 0x0A == 10
var b: int = 0x0F << 8; // shift left == 0xF00 == 3840
var c: int = 0b1001 & 1; // and == 0b001 == 1
var d: int = 0b1100 | 1; // or == 0b1101 == 13
var e: int = 0b1101 ^ 1; // xor == 0b1100 == 12
// with inferred type
var f = 0xA0 >> 4; // shift right == 0x0A == 10
var g = 0x0F << 8; // shift left == 0xF00 == 3840
var h = 0b1001 & 1; // and == 0b001 == 1
var i = 0b1100 | 1; // or == 0b1101 == 13
var j = 0b1101 ^ 1; // xor == 0b1100 == 12
Notice that Virgil allows writing integer constants in decimal, hexadecimal, and binary, but not in octal.
Rules for integer literals are pretty simple.
- Decimal and hexadecimal literals are of type
intby default. - If a literal is too large to fit into the value range of
int, it is of typelong. - The
lorLsuffix explicitly specifies thelongtype for a literal. - The
uorUsuffix specifies unsigned, i.e.u32instead ofintoru64instead oflong.
An integer literal will be value-range checked and converted to the expected type if used in a context where a smaller integer type is expected.
// with declared type
var a: byte = 0xA0; // legal; 0xA0 fits in byte range
var b: i20 = 2L; // legal; 2 fits into i20 range
def foo(x: byte) {
foo(0); // legal; 0 of type int implicitly converted to byte
}
In Virgil III, shifts on integers can be thought of as occurring in a window of bits that is exactly as wide as the type. For a integer type iN, where N is a width in bits, a shift amount of more than N will result in shifting out all of the bits (as opposed to other languages that either leave overshifts undefined, mask the shift amount, etc). Right shift >> is arithmetic (meaning, the sign bit is copied down) for signed integer types and logical (meaning, 0 bits are copied down) for unsigned types. The >>> operator performs logical right shifts on either signed or unsigned integers.
// a shift of < 0 or >= 32 bits always produces 0
var a: int = 1 << 33; // == 0; unlike Java == 2
var b: int = 1 << -1; // == 0 unlike Java == MININT
var c: int = -1 << -1; // == 0; unlike Java == MININT
var d: int = 3 >> 33; // == 0; unlike Java == 1
var e: int = 3 >> -1; // == 0
var f: int = -1 >> -1; // == 0; unlike Java == -1