examples/widths/
Come convertire numeri tra tipi con dimensioni diverse e gestire i valori che non rientrano nel tipo scelto.
- 185 righe
Le conversioni verso tipi interi restituiscono un valore oppure un errore se il numero è fuori intervallo. Quelle in virgola mobile possono arrotondare il risultato. Il programma verifica i limiti dei tipi, gli errori di conversione e la precisione persa con l’arrotondamento.
Eseguilo
true
true
false
false
200
true
true
true
true
true
2
-2
0
false
true
200
-1
2.5
exit 0 · 18 righe
Il programma
# Numbers changing width, and what the change is allowed to cost — the
# program for the seven conversions the corpus never called.
#
# Corpus and regression material (CLAUDE.md §9), and the gap was measured
# rather than guessed. Over every `.hero` in `examples/`:
#
# to_i8 to_i16 to_i32 to_u8 to_u16 to_f32 ZERO uses
# to_u32 to_u64 to_f64 4, 4 and 5 uses
# the types u16 u64 i8 i16 declared ZERO
#
# Five of those six had no use in `selfhost/` either. So of the twelve
# conversions `spec:62-68` gives, more than half had no program behind them at
# all — in exactly the department where a mistake is easiest to make and
# hardest to see, because a wrong width is a right-looking number.
#
# WHAT THE SPEC PROMISES, AND WHICH HALF THIS PROGRAM CAN PIN:
#
# - no implicit conversions, widths included, so `a + b` needs one type
# - the NAME says whether it can fail: `to_str`, `to_f32` and `to_f64`
# cannot and give a value; `to_i8` … `to_u64` give a `T?`
# - `to_i64` takes a float too, truncating TOWARD ZERO
# - nothing fails to fit a float: too large is `inf`, and `to_f32` rounds
# - a literal takes the type its context asks for; overflow ABORTS at every
# width
#
# The last clause is the one a corpus program must NOT exercise. An abort ends
# the process, so it belongs in `tests/golden/run/`, where the harness can
# expect one — and it lives there already. Everything above it is here.
# One byte, and the value that will not fit in one.
constant BYTE_MAX: i64
255
constant TOO_BIG_FOR_A_BYTE: i64
256
# A signed byte's range, which is the asymmetry people get wrong: -128 fits
# and +128 does not.
constant I8_MIN: i64
0 - 128
constant I8_MAX: i64
127
# Whether a value survives the trip down to a byte and back up again. A
# conversion that CAN fail answers a `T?`, so this is the shape every caller
# of `to_u8` has to write.
function fits_a_byte(value: i64) -> bool
return !value.to_u8().is_err()
function through_a_byte(value: i64) -> i64?
small = value.to_u8()?
# Widening back is infallible in mathematics and fallible in this
# language, because the NAME carries the promise and `to_i64` keeps one
# name for every argument. `.must()` is what a caller writes when the
# argument makes the failure unreachable — and `spec` §1.4 calls that
# redundancy spent where an error cannot occur.
return ok(small.to_i64().must())
# The float side. `to_f64` cannot fail, so it needs no `?`; `to_i64` of a
# float truncates toward zero, which is NOT the same as rounding and not the
# same as flooring for a negative number.
function truncated(value: f64) -> i64
return value.to_i64().must()
function main()
# A conversion that fits, one that does not, and the boundary between
# them — printed rather than only asserted, so `main.expected` carries
# the shape of the answer.
print(fits_a_byte(0))
print(fits_a_byte(BYTE_MAX))
print(fits_a_byte(TOO_BIG_FOR_A_BYTE))
print(fits_a_byte(0 - 1))
print(through_a_byte(200).must())
print(through_a_byte(TOO_BIG_FOR_A_BYTE).is_err())
# The signed byte's asymmetry, which is the one line of this program a
# reader is most likely to have wrong in their head.
print(!I8_MIN.to_i8().is_err())
print(!I8_MAX.to_i8().is_err())
print((0 - 129).to_i8().is_err())
print(128.to_i8().is_err())
# Truncation toward zero, on both signs. A language that floored would
# print -3 on the second line.
print(truncated(2.7))
print(truncated(0.0 - 2.7))
print(truncated(0.0))
# `to_f32` rounds rather than failing, so a value with more precision
# than a `f32` holds comes back changed and not refused.
wide = 16777217.0
narrow = wide.to_f32()
print(narrow.to_f64() == wide)
# And nothing fails to fit a float: the largest `i64` becomes a float
# that no longer equals it.
biggest = 9223372036854775807
print(biggest.to_f64().to_i64().is_err())
# `to_str` cannot fail either, at any width, which is why every message
# in this file could be built without a single `.must()`.
b: u8 = 200
print(b.to_str())
print((0 - 1).to_str())
print(2.5.to_str())
test "a byte holds 0 through 255 and nothing else"
assert fits_a_byte(0)
assert fits_a_byte(1)
assert fits_a_byte(254)
assert fits_a_byte(BYTE_MAX)
assert !fits_a_byte(TOO_BIG_FOR_A_BYTE)
assert !fits_a_byte(0 - 1)
assert !fits_a_byte(1000000)
test "a signed byte is asymmetric, and that is the whole point of the case"
assert !I8_MIN.to_i8().is_err()
assert !I8_MAX.to_i8().is_err()
assert (0 - 129).to_i8().is_err()
assert 128.to_i8().is_err()
test "every signed width refuses one past its own top"
assert !32767.to_i16().is_err()
assert 32768.to_i16().is_err()
assert !2147483647.to_i32().is_err()
assert 2147483648.to_i32().is_err()
test "every unsigned width refuses a negative and one past its top"
assert !65535.to_u16().is_err()
assert 65536.to_u16().is_err()
assert (0 - 1).to_u16().is_err()
assert !4294967295.to_u32().is_err()
assert 4294967296.to_u32().is_err()
assert (0 - 1).to_u32().is_err()
assert !0.to_u64().is_err()
assert (0 - 1).to_u64().is_err()
test "a round trip through a byte is the identity where it fits at all"
for one in [0, 1, 127, 128, 254, 255]
assert through_a_byte(one).must() == one
test "a round trip refuses rather than wrapping"
# The property that separates this language from C: 256 does not become 0.
assert through_a_byte(256).is_err()
assert through_a_byte(257).is_err()
assert through_a_byte(0 - 1).is_err()
test "to_i64 of a float truncates toward zero, on both signs"
assert truncated(2.9) == 2
assert truncated(2.1) == 2
assert truncated(0.0 - 2.9) == 0 - 2
assert truncated(0.0 - 2.1) == 0 - 2
assert truncated(0.0) == 0
# Not flooring: a language that floored would answer -3 here.
assert truncated(0.0 - 2.9) != 0 - 3
test "to_f32 rounds rather than refusing"
# 16777217 is the first integer an f32 cannot hold: it has 24 bits of
# mantissa, so 2^24 + 1 comes back as 2^24.
wide = 16777217.0
assert wide.to_f32().to_f64() != wide
assert wide.to_f32().to_f64() == 16777216.0
# And a value inside the range survives exactly.
assert 1.5.to_f32().to_f64() == 1.5
assert 0.0.to_f32().to_f64() == 0.0
test "to_str and to_f64 cannot fail, so they hand back a value"
b: u8 = 200
assert b.to_str() == "200"
assert (0 - 1).to_str() == "-1"
assert 255.to_f64() == 255.0
assert (0 - 1).to_f64() == 0.0 - 1.0
test "a literal takes the type its context asks for"
# `200` is a `u8` here because the cell says so, and an `i64` two lines
# down because nothing else says otherwise.
small: u8 = 200
plain = 200
assert small.to_i64().must() == plain
assert small.to_str() == plain.to_str()
impronta sha256 146221d4f518 · 2 file · index.json
Successivo: palette. Tutti gli esempi. ← Precedente: floats
Eseguine uno: i quattro comandi. Impara il linguaggio, oppure leggi la specifica completa in un solo file.