hanki

int

stdlib/int.hk: inherent assoc fns on int.

Associated functions on a built-in type live in an impl <Type> block alongside the type's inherent methods. Reading an int from text is FromString (below) and no inherent fn, which lets generic code reach it through the bound; _parse is the runtime seam it is built on.

impl int

_parse

def _parse(s: string) -> Option<int>

The runtime seam under [FromString]: the raw yes-or-no parse, with no room to say why. parse below is the surface, and turns the None into a reason.

to_i64

def to_i64(self) -> i64

Narrow to i64, truncating (modular: the low 64 bits at the target width), total: matches the fixed-width narrowing family (HANKI.md §3). The in-band sentinels saturate (-inf -> i64 MIN, +inf/undefined -> i64 MAX).

7.to_i64()    => 7i64
(-7).to_i64() => -7i64

to_rational

def to_rational(self) -> rational

Widen to the rational tier (exact; self/1; sentinels pass through).

6.to_rational() / 3.to_rational() => 2.to_rational()

to_decimal

def to_decimal(self) -> decimal

Widen to the decimal tier (exact, scale 0; sentinels pass through).

3.to_decimal() + 4.to_decimal() => 7.to_decimal()

to_f64

def to_f64(self) -> f64

Widen to f64, rounded to nearest, lossy beyond 2^53 (f64 has a 53-bit mantissa). Sentinels map to IEEE: inf/-inf become the float infinities, undefined becomes NaN.

42.to_f64()    => 42.0f64
(-42).to_f64() => -42.0f64

todecimalscaled

def to_decimal_scaled(self, scale: int) -> decimal

This value as the mantissa of a decimal scaled by 10^-scale: the decimal equal to self * 10^(-scale) (a negative scale scales up). The fractional-decimal constructor decimal otherwise lacks, since its / widens to rational.

scale is the same int that decimal.scale answers and decimal.from_parts accepts, and this takes from_parts's exact contract: undefined when scale is not one any decimal can hold (non-finite, or past i64).

12345.to_decimal_scaled(2) => 1234500.to_decimal_scaled(4)

to_i8

def to_i8(self) -> i8

Narrow to i8, truncating (modular, low 8 bits): i64.to_i8 reached without the detour through to_i64 a caller had to write.

This is the tier values arrive in: every unsuffixed integer literal is an int, and so is every count the index surface reports, so int is what a caller has where a fixed width is wanted. It is self.to_i64().to_i8() and no more: the low bits of a number are the low bits of its low 64 bits, and the two agree for every finite value, and a sentinel saturates in to_i64 as it always did.

127.to_i8()  => 127i8
200.to_i8()  => -56i8
(-1).to_i8() => -1i8

to_i16

def to_i16(self) -> i16

Narrow to i16, truncating (modular, low 16 bits). See to_i8.

32767.to_i16() => 32767i16
32768.to_i16() => -32768i16

to_i32

def to_i32(self) -> i32

Narrow to i32, truncating (modular, low 32 bits). See to_i8.

2147483647.to_i32() => 2147483647i32
2147483648.to_i32() => -2147483648i32

to_u64

def to_u64(self) -> u64

Reinterpret the low 64 bits as u64: the same bits, total. See to_i8.

9.to_u64()    => 9u64
(-1).to_u64() => 18446744073709551615u64

to_u32

def to_u32(self) -> u32

Narrow to u32, truncating (modular, low 32 bits). See to_i8.

4294967295.to_u32() => 4294967295u32
4294967296.to_u32() => 0u32
(-1).to_u32()       => 4294967295u32

to_u16

def to_u16(self) -> u16

Narrow to u16, truncating (modular, low 16 bits). See to_i8.

65535.to_u16() => 65535u16
65536.to_u16() => 0u16

to_u8

def to_u8(self) -> u8

Narrow to u8, truncating (modular, low 8 bits). See to_i8.

255.to_u8() => 255u8
256.to_u8() => 0u8

_fits?

def _fits?(self, low: int, high: int) -> bool

Whether self lies in the inclusive range [low, high], the shared test under the try_to_ family. Every fixed width's bounds are representable here, and the comparison therefore happens in this tier and nothing is narrowed before the answer is known. The in-band sentinels fail it through the tier's total order (-inf sits below every finite bound, inf and undefined above every one), which is the reading the twins want: no width covers an infinity.

trytoi8

def try_to_i8(self) -> Option<i8>

Narrow to i8, or None where the value does not fit: the checked twin of to_i8 (HANKI.md §3).

Unlike the bare family this is not self.to_i64().try_to_i8(). That composition is unsound: an int past i64's range has already wrapped by the time the check runs, and (2.pow(70).to_int() + 5).to_i64().try_to_i8() would answer Some(5i8) for a value that never fit, the wrapping the twin exists to detect. The bounds test therefore runs against the full-precision value.

127.try_to_i8()    => Some(127i8)
128.try_to_i8()    => None
(-129).try_to_i8() => None

trytoi16

def try_to_i16(self) -> Option<i16>

Narrow to i16, or None when the value doesn't fit. See try_to_i8.

32767.try_to_i16() => Some(32767i16)
32768.try_to_i16() => None

trytoi32

def try_to_i32(self) -> Option<i32>

Narrow to i32, or None when the value doesn't fit. See try_to_i8.

2147483647.try_to_i32() => Some(2147483647i32)
2147483648.try_to_i32() => None

trytoi64

def try_to_i64(self) -> Option<i64>

Narrow to i64, or None when the value doesn't fit. See try_to_i8. The checked twin of to_i64, and the one crossing where the bare form's saturating sentinels are most likely to surprise: None says the value never had a 64-bit reading, sentinels included.

9223372036854775807.try_to_i64() => Some(9223372036854775807i64)
9223372036854775808.try_to_i64() => None

trytou8

def try_to_u8(self) -> Option<u8>

Narrow to u8, or None when the value doesn't fit. See try_to_i8. A negative value fits no unsigned width, and it answers None in place of reinterpreting its bits.

255.try_to_u8()  => Some(255u8)
256.try_to_u8()  => None
(-1).try_to_u8() => None

trytou16

def try_to_u16(self) -> Option<u16>

Narrow to u16, or None when the value doesn't fit. See try_to_u8.

65535.try_to_u16() => Some(65535u16)
65536.try_to_u16() => None

trytou32

def try_to_u32(self) -> Option<u32>

Narrow to u32, or None when the value doesn't fit. See try_to_u8.

4294967295.try_to_u32() => Some(4294967295u32)
4294967296.try_to_u32() => None

trytou64

def try_to_u64(self) -> Option<u64>

Narrow to u64, or None when the value doesn't fit. See try_to_u8. The checked twin of to_u64, which reinterprets the low 64 bits and so turns -1 into u64's maximum; this one rejects it.

18446744073709551615.try_to_u64() => Some(18446744073709551615u64)
18446744073709551616.try_to_u64() => None
(-1).try_to_u64()                 => None

abs

def abs(self) -> int

The absolute value. int is arbitrary-precision, and every magnitude is representable, with no wrap case.

(-5).abs() => 5
5.abs()    => 5
0.abs()    => 0

pow

def pow(self, exp: int) -> rational

self raised to exp, exact for every exponent, and it therefore answers in rational and not in int. A negative exponent is a reciprocal, and 2.pow(-1) is 1/2; answering 0 there would be the unannounced loss of value this tier exists to prevent. The common case adds a crossing back: 10.pow(3).to_int().

0.pow(exp) for a negative exp divides by zero and so lands on the tier's own p/0 sentinel, and never on a failure.

10.pow(3)   => 1000.to_rational()
2.pow(-1)   => 1 / 2
(-2).pow(3) => (0 - 8).to_rational()
7.pow(0)    => 1.to_rational()
0.pow(-1)   => 1 / 0

upto

def upto(self, hi: int) -> Range

The inclusive range [self, hi] as a half-open Range: 1.upto(3) iterates 1, 2, 3. The inclusive counterpart to the half-open Range.new; empty when hi < self.

1.upto(3).length  => 3
1.upto(3).fold(0, |acc, i| acc + i) => 6
3.upto(1).length  => 0

times!

def times!(self, f: (int) -> () [e]) -> () [e]

Runs the effectful f self times, with the index 0 … self-1. A zero or negative count runs f no times. @no-doctest: effectful iteration returning unit; no value to assert

impl FromString<int>

parse

def parse(s: string) -> Result<int, ParseError>

Parses a base-10 int. It is arbitrary precision, and the only way to fail is for the text not to denote a whole number at all; there is no range to fall outside of. Surrounding whitespace is trimmed. Underscore separators belong to the literal grammar, for reading source; text being parsed is data, and no numeric parse accepts one.

int.parse("42")    => Ok(42)
int.parse("-1")    => Ok(-1)
int.parse("1_000") => Err(ParseError(input="1_000", reason="not a base-10 integer"))
int.parse("nope")  => Err(ParseError(input="nope", reason="not a base-10 integer"))