Skip to content

Number

Number is an Object that wraps a single primitive: any of xtc’s sized integers (i8/u8/i16/u16/i32/u32/i64/u64) or a float/double. A typed collection stores primitives in it (an Array<i32> keeps its elements as Numbers), and it lets a heterogeneous collection hold mixed values.

#import "Foundation.xc" // or #import "Number.xc"

A Number has two storage kinds, integer and float, and one is canonical at a time. The setters (setI8, setFloat …) set the kind, the predicates isInt / isFloat report it, and the getters read the value back in whatever type you ask for.

Two slots, widest of their kind. Integers are held bit-preserving in an i64 slot, floats in an IEEE-754 double slot. Every narrow accessor (asI8, asU16, asFloat …) is a cast from one of the two. The wide slots are what make Array<i64> and Array<double> possible.

Cross-kind conversion is lazy and cached. Each Number tracks a two-bit valid bitmap. Reading a float out of an int Number (or an int out of a float one) converts on the first call, stores the result in the inactive slot, and sets its valid bit. A Number compared against many values of the other kind pays the conversion cost once. Any setter clears the other bit, so a stale conversion never survives a new value.

Bit-preserving casts. setU32(0xFFFFFFFF).asI32() returns -1 and setI32(-1).asU32() returns 0xFFFFFFFF, matching plain (i32)/(u32) casts. As a result, withU32(0xFFFFFFFF).asFloat() converts from the signed view (-1.0), not 4294967295.0. Set the kind with withFloat if you need the unsigned magnitude.

  • Comparable: compare orders two Numbers (cross-kind via float promotion), so numbers can be sorted and used as keys.
  • Hashable: hash makes a Number a Map / Set key, and it agrees with equals: Int(42) and Float(42.0) hash the same.

Every Number* is also an Object* and fits anywhere one is expected, such as a collection slot or a dictionary value.

Creating (inferred kind) · with

Creating (pinned kind) · withI8 / withU8 · withI16 / withU16 · withI32 / withU32 · withI64 / withU64 · withFloat / withDouble · init

Setting (inferred kind) · set

Setting (pinned kind) · setI8 / setU8 · setI16 / setU16 · setI32 / setU32 · setI64 / setU64 · setFloat / setDouble

Reading (by destination) · value

Reading (pinned type) · asI8 / asU8 · asI16 / asU16 · asI32 / asU32 · asI64 / asU64 · asFloat / asDouble

Predicates · isInt / isFloat

Protocol methods · description · equals · compare · hash


static Number* with(i8 v) static Number* with(u8 v)
static Number* with(i16 v) static Number* with(u16 v)
static Number* with(i32 v) static Number* with(u32 v)
static Number* with(i64 v) static Number* with(u64 v)
static Number* with(float v) static Number* with(double v)

Overloaded on the argument’s type: the compiler picks the storage kind from v. Use it when the literal’s natural type is the one you want; Number.with(50000) boxes a u16. To force a wider or differently-signed slot, use the pinned withXxx factory instead.

↑ Topics

Each factory sets the storage kind regardless of the argument’s natural type. Use them when you need a wider or differently-signed slot than the natural fit.

static Number* withI8(i8 v)
static Number* withU8(u8 v)

A Number holding an 8-bit signed / unsigned integer.

static Number* withI16(i16 v)
static Number* withU16(u16 v)

A Number holding a 16-bit signed / unsigned integer.

static Number* withI32(i32 v)
static Number* withU32(u32 v)

A Number holding a 32-bit signed / unsigned integer.

static Number* withI64(i64 v)
static Number* withU64(u64 v)

A Number holding a 64-bit signed / unsigned integer. Needs -DENABLE_64BIT=1 on xt6502 (see Availability).

static Number* withFloat(float v)
static Number* withDouble(double v)

A Number holding a floating-point value (both stored in the double slot).

void init(void)

The default initializer: an integer Number holding 0. Prefer new Number() plus a setter, or the with… factories; you rarely call init directly.

↑ Topics

void set(i8 v) void set(u8 v)
void set(i16 v) void set(u16 v)
void set(i32 v) void set(u32 v)
void set(i64 v) void set(u64 v)
void set(float v) void set(double v)

The mutating counterpart of with: overloaded on the argument’s type, it stores a new value in an existing Number, picking the kind from v.

↑ Topics

Each setter sets the canonical kind, writes its slot, and updates the valid bitmap, so the next cross-kind read converts afresh rather than returning a stale conversion.

void setI8(i8 v)
void setU8(u8 v)

Store an 8-bit integer, pinning the int kind.

void setI16(i16 v)
void setU16(u16 v)

Store a 16-bit integer.

void setI32(i32 v)
void setU32(u32 v)

Store a 32-bit integer.

void setI64(i64 v)
void setU64(u64 v)

Store a 64-bit integer.

void setFloat(float v)
void setDouble(double v)

Store a floating-point value, pinning the float kind.

↑ Topics

i8 value(void) u8 value(void)
i16 value(void) u16 value(void)
i32 value(void) u32 value(void)
i64 value(void) u64 value(void)
float value(void) double value(void)

Overloaded on return type: value() picks its type from the expected-type context (assignment LHS, variable declaration, call argument), the same mechanism Math.rand() uses to pick i16 or float. i32 v = n.value(); reads the int view; float f = n.value(); reads the float view. Each narrow overload goes through the cache-aware conversion points, so kind conversion happens at most once.

↑ Topics

Explicit return type. asI64 and asDouble are the cache-aware conversion points. Every other getter delegates to them with a bit-preserving cast, so a stale slot is converted at most once.

i8 asI8(void)
u8 asU8(void)

The value truncated to 8 bits, signed / unsigned.

i16 asI16(void)
u16 asU16(void)

The value truncated to 16 bits.

i32 asI32(void)
u32 asU32(void)

The value truncated to 32 bits.

i64 asI64(void)
u64 asU64(void)

The full 64-bit integer view. asI64 is a conversion point: on a float Number it converts via the (i64)double cast (truncate toward zero, saturate to 0 on overflow), caches the result, and returns the cached value on later calls.

float asFloat(void)
double asDouble(void)

The floating-point view. asDouble is a conversion point: on an int Number it converts via (double)i64 (exact to 2^53, losing precision beyond), caches the result, and returns the cached value on later calls. asFloat narrows that to binary32.

↑ Topics

bool isInt(void)
bool isFloat(void)

Which kind is canonical: the one a setter last set. One of the two is always true.

↑ Topics

The Object / Comparable / Hashable hooks that give Number value semantics.

String* description(void)

The %@ hook. An int renders exactly (String.withI64); a float renders to six decimal places (String.withFloat’s default, matching C’s printf %f).

bool equals(Number* other)
bool equals(Object* other)

Value equality. Same-kind compares stay bit-exact; cross-kind promotes both sides to float, so Number.withI16(42) equals Number.withFloat(42.0), and a non-integer float never equals any integer Number. The Object* overload is the Comparable slot heterogeneous containers use; it returns false against a non-Number.

i8 compare(Number* other)
i8 compare(Object* other)

Ordering: <0 / 0 / >0 (the C / NSComparisonResult convention). The kind rules match equals, so Int(42) and Float(42.0) compare equal here too. Comparing against a non-Number returns 0 (“these sort equally”), because there is no meaningful order between a Number and another kind of object. Pass a comparator if you need an order across kinds.

u32 hash(void) // u8 on the xt6502 build

Scrambles the 32-bit integer view. Cross-kind equals promotes to float, and Int(42) and Float(42.0) both asI32() to 42, so they hash the same, as the Hashable contract requires. Float(42.5) also folds to 42 and collides with Int(42), but probe-chain equality keeps lookups correct.

↑ Topics

#import "Stdio.xc"
#import "Foundation.xc"
i32 main(void)
{
Number* n = Number.withI16((i16)-42);
if (n.isInt()) Stdio.printf("%d\n", n.asI16()); // -42
Number* f = Number.withFloat(3.25);
Stdio.printf("%s\n", f.description().cString()); // 3.250000
// Cross-kind equality: promotes to float.
Number* a = Number.with((i16)42);
Number* b = Number.withFloat(42.0);
Stdio.printf("%d\n", (i16)a.equals(b)); // 1 (true)
return 0;
}