Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Types

Bitwise operators

int r = (colour >> 16) & 255; // & | ^ ~ << >>

`&`, `|`, `^`, `~`, `<<` and `>>` work on `int`, with C#'s meanings and C#'s precedence. Unlike `+`, `-`, `*` and `/`, they do not raise on overflow — a bit operation is defined modulo 2^32, so `1 << 31` is a negative number rather than an error. The operands must be integers.

stable3 examples compiled by CItypesoperatorsauthoring

Summary#

Osy# has the whole C# bitwise family — & (and), | (or), ^ (exclusive or), ~ (complement), << (left shift) and >> (right shift) — together with the compound forms &=, |=, ^=, <<= and >>=.

They work on int, and they mean exactly what they mean in C#. The two differences worth knowing are both about what they do not do: they never raise on overflow, and they do not accept a bool.

Signature#

int r = (colour >> 16) & 255;      // read one byte out of a packed value
int packed = (r << 16) | (g << 8) | b;   // put three back together

int flags = Read | Write;          // set bits
bool canWrite = (flags & Write) != 0;    // test one
flags &= ~Write;                   // clear it

int doubled = value << 1;          // shift
int halved = value >> 1;           // arithmetic — the sign is preserved

Description#

They are unchecked#

Osy# integer arithmetic is checked: +, -, * and / raise rather than wrapping to a wrong number when the result leaves the range of int. Bitwise operators are the deliberate exception, because a bit operation is defined modulo 2^32 rather than as arithmetic on a magnitude.

So 1 << 31 is -2147483648, and that is the answer rather than an error — the same as in C#. If it raised, a perfectly ordinary bit pattern could not be written down.

The shift count wraps at 32#

x << 33 means x << 1: the count is masked to its low five bits, as in C#. A shift by a multiple of 32 is therefore a shift by nothing, not a way to clear a value.

>> keeps the sign#

Right shift is arithmetic: -8 >> 1 is -4, not a large positive number. The sign bit is copied rather than zeros being shifted in. This is why the family is defined on int and not on a wider or unsigned type — there is exactly one integer width whose bit behaviour is identical everywhere an Osy# expression can run.

Precedence is C#'s#

From loosest to tightest:

||   <   &&   <   |   <   ^   <   &   <   ==  !=   <   <  <=  >  >=   <   <<  >>   <   +  -   <   *  /  %

Two consequences catch people out in C too, and they are the same here:

  • a & b == c is a & (b == c) — equality binds tighter than &.
  • 1 << 2 + 1 is 1 << 3, which is 8 — addition binds tighter than a shift.

Parenthesise when the reading matters. The compiler will not warn, because the expression is not wrong.

The operands must be integers#

A double has no bit pattern the language exposes, so x & 255 on a double is refused rather than rounded — the same refusal C# makes.

A bool is also refused, and here Osy# is narrower than C#. C# lets you write a & b on two bools as a non-short-circuiting logical and: both sides are evaluated, and the result is a bool. That is a genuinely different operation from the integer one, and it is not implemented. It is refused by name rather than quietly treated as &&, which would be the same expression meaning two different things depending on a type you cannot see at the call site. Use && and ||.

Where they run#

Everywhere. A bitwise expression compiles for a server function, a client action and a query filter alike, and a hot client region containing one still compiles to JavaScript — JavaScript's bitwise operators are specified over the same 32-bit conversion, so the compiled form is the operator itself with nothing added.

Examples#

Packing and unpacking a colour, which is what per-pixel graphics code spends its time on:

int Shade(int colour, int percent) {
  int r = (colour >> 16) & 255;
  int g = (colour >> 8) & 255;
  int b = colour & 255;
  return ((r * percent / 100) << 16) | ((g * percent / 100) << 8) | (b * percent / 100);
}

A flags value, set, tested and cleared:

int None() { return 0; }
int Read() { return 1; }
int Write() { return 2; }
int Admin() { return 4; }

int Grant(int flags, int bit) { return flags | bit; }
int Revoke(int flags, int bit) { return flags & ~bit; }
bool Has(int flags, int bit) { return (flags & bit) != 0; }
int Toggle(int flags, int bit) { return flags ^ bit; }

Overflow is not an error here, and the sign survives a right shift:

int Smallest() { return 1 << 31; }        // -2147483648, not an overflow
int NoOpShift() { return 1 << 32; }       // 1 — the count masks to five bits
int Halve() { return -8 >> 1; }           // -4 — the sign is preserved

Both of these are refused:

double d = 2.5;
int bad = d & 255;        // REFUSED — a double has no bit pattern

bool a = true, b = false;
bool also = a & b;        // REFUSED — use `&&`; C#'s bool `&` is not implemented

See also#

Related

Every type, in one list

The complete vocabulary of built-in types — the scalars you can store, the collections, the two callable spellings…

long

A 64-bit whole number, exact to its full range — ids, sequence numbers, row versions, byte offsets. It holds the same…

Constant expressions

Some places take a value that must be known at compile time — an attribute argument, a config setting, a workflow…