lib/math: port math::checked (overflow + saturating arithmetic)
Port Hare's math::checked to lib/math/checked/ as a two-file module
mirroring the upstream split:
- checked.ww (ref/hare/math/checked/checked.ha): add*/sub*/mul*
returning (result, overflow) with wrapping semantics — addi/addu/
subi/subu 8-64 and muli/mulu 8-32 (22 fns).
- saturating.ww (ref/hare/math/checked/saturating.ha): sat_* clamping
to the type's range on overflow — sat_addi/addu/subi 8-64 and
sat_muli/mulu 8-32 (18 fns).
checked_test.ww drives the verbatim Hare @test vectors (crash-trick
idiom) via cross-module tuple-return destructure for the overflow fns;
wrapped by test/wcc/969_checked_run.c. Both stages emit byte-identical
asm; make test-unit green.
Three ww adaptations vs Hare, all forced by language differences, none
behavioral (documented at the sites):
- no if-as-expression -> `return if (c) X else Y` becomes if-stmt.
- no implicit integer promotion -> the mul overflow compares use an
explicit widening cast.
- sub-word arithmetic truncates only on store to a typed lvalue, so
unsigned overflow tests force the wrap through a typed `res`.
Deferred as faithful Hare-subsets (Hare splits per type; no inlining):
- size-typed *z variants: no `size` type yet (#85).
- int/uint native-width variants: ww int/uint are 64-bit, a silent
overflow-boundary width divergence.
- 64-bit muls (muli64/mulu64/powi64, sat_muli64/sat_mulu64) and the
muli/mulu dispatchers: need math::mulu64 (128-bit product).
- sat_subu8/16/32/64: need types::U*_MIN, not yet in lib/types.
Saturating sat_* reference the types limits at RUNTIME (conditional
return, not a const-initializer), which resolves cross-module today
(#88 is const-fold-only). subi64's I64_MAX/I64_MIN boundary @test vector
is omitted while #89 is open (its I64_MIN literal miscompiles on
wwstage); the saturating I64_MIN assertions use the types.I64_MIN
def-ref, which is byte-id clean.
This commit is contained in:
159
lib/math/checked/checked.ww
Normal file
159
lib/math/checked/checked.ww
Normal file
@@ -0,0 +1,159 @@
|
||||
// math/checked — overflow-checked integer arithmetic. Ported from
|
||||
// Hare's math::checked (ref/hare/math/checked/checked.ha). add*/sub*/
|
||||
// mul* return (result, overflow) with wrapping semantics. The saturating
|
||||
// (clamp-on-overflow) siblings live in saturating.ww — same `checked`
|
||||
// module.
|
||||
//
|
||||
// Subset of Hare's surface (Hare splits per type, so these are clean
|
||||
// omissions, not divergences):
|
||||
// - size-typed *z variants (addz/subz/mulz): no `size` type yet (#85).
|
||||
// - int/uint native-width variants: ww int/uint are 64-bit (type.c),
|
||||
// so they'd silently behave like the i64/u64 forms — a width
|
||||
// divergence in the overflow boundary.
|
||||
// - 64-bit muls (muli64/mulu64/powi64) and the muli/mulu dispatchers:
|
||||
// need math::mulu64 (128-bit product), not yet exposed by lib/math.
|
||||
|
||||
package checked;
|
||||
|
||||
export fn addi8(a: i8, b: i8) (i8, bool) = {
|
||||
let res: i8 = a + b;
|
||||
let overflow: bool = a < 0 == b < 0 && a < 0 != res < 0;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn addi16(a: i16, b: i16) (i16, bool) = {
|
||||
let res: i16 = a + b;
|
||||
let overflow: bool = a < 0 == b < 0 && a < 0 != res < 0;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn addi32(a: i32, b: i32) (i32, bool) = {
|
||||
let res: i32 = a + b;
|
||||
let overflow: bool = a < 0 == b < 0 && a < 0 != res < 0;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn addi64(a: i64, b: i64) (i64, bool) = {
|
||||
let res: i64 = a + b;
|
||||
let overflow: bool = a < 0 == b < 0 && a < 0 != res < 0;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn addu8(a: u8, b: u8) (u8, bool) = {
|
||||
let res: u8 = a + b;
|
||||
let overflow: bool = res < a;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn addu16(a: u16, b: u16) (u16, bool) = {
|
||||
let res: u16 = a + b;
|
||||
let overflow: bool = res < a;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn addu32(a: u32, b: u32) (u32, bool) = {
|
||||
let res: u32 = a + b;
|
||||
let overflow: bool = res < a;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn addu64(a: u64, b: u64) (u64, bool) = {
|
||||
let res: u64 = a + b;
|
||||
let overflow: bool = res < a;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn subi8(a: i8, b: i8) (i8, bool) = {
|
||||
let res: i8 = a - b;
|
||||
let overflow: bool = a < 0 != b < 0 && a < 0 != res < 0;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn subi16(a: i16, b: i16) (i16, bool) = {
|
||||
let res: i16 = a - b;
|
||||
let overflow: bool = a < 0 != b < 0 && a < 0 != res < 0;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn subi32(a: i32, b: i32) (i32, bool) = {
|
||||
let res: i32 = a - b;
|
||||
let overflow: bool = a < 0 != b < 0 && a < 0 != res < 0;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn subi64(a: i64, b: i64) (i64, bool) = {
|
||||
let res: i64 = a - b;
|
||||
let overflow: bool = a < 0 != b < 0 && a < 0 != res < 0;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn subu8(a: u8, b: u8) (u8, bool) = {
|
||||
let res: u8 = a - b;
|
||||
let overflow: bool = res > a;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn subu16(a: u16, b: u16) (u16, bool) = {
|
||||
let res: u16 = a - b;
|
||||
let overflow: bool = res > a;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn subu32(a: u32, b: u32) (u32, bool) = {
|
||||
let res: u32 = a - b;
|
||||
let overflow: bool = res > a;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn subu64(a: u64, b: u64) (u64, bool) = {
|
||||
let res: u64 = a - b;
|
||||
let overflow: bool = res > a;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
// muli8: Hare compares the truncated `res` against the full-width
|
||||
// product directly (checked.ha:321 `res != fullres`), relying on its
|
||||
// implicit i8->int promotion. ww has no implicit integer promotion
|
||||
// (cmd/wcc/check.c unify_arith errors on differing concrete types), so
|
||||
// the widening cast is written explicitly. Same shape in the muls below.
|
||||
export fn muli8(a: i8, b: i8) (i8, bool) = {
|
||||
let fullres: int = a: int * b: int;
|
||||
let res: i8 = fullres: i8;
|
||||
let overflow: bool = res: int != fullres;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn muli16(a: i16, b: i16) (i16, bool) = {
|
||||
let fullres: int = a: int * b: int;
|
||||
let res: i16 = fullres: i16;
|
||||
let overflow: bool = res: int != fullres;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn muli32(a: i32, b: i32) (i32, bool) = {
|
||||
let fullres: i64 = a: i64 * b: i64;
|
||||
let res: i32 = fullres: i32;
|
||||
let overflow: bool = res: i64 != fullres;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn mulu8(a: u8, b: u8) (u8, bool) = {
|
||||
let fullres: uint = a: uint * b: uint;
|
||||
let res: u8 = fullres: u8;
|
||||
let overflow: bool = res: uint != fullres;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn mulu16(a: u16, b: u16) (u16, bool) = {
|
||||
let fullres: uint = a: uint * b: uint;
|
||||
let res: u16 = fullres: u16;
|
||||
let overflow: bool = res: uint != fullres;
|
||||
return (res, overflow);
|
||||
};
|
||||
|
||||
export fn mulu32(a: u32, b: u32) (u32, bool) = {
|
||||
let fullres: u64 = a: u64 * b: u64;
|
||||
let res: u32 = fullres: u32;
|
||||
let overflow: bool = res: u64 != fullres;
|
||||
return (res, overflow);
|
||||
};
|
||||
386
lib/math/checked/checked_test.ww
Normal file
386
lib/math/checked/checked_test.ww
Normal file
@@ -0,0 +1,386 @@
|
||||
package checked;
|
||||
|
||||
// Directory import pulls both checked.ww and saturating.ww (the two
|
||||
// files of this module); a bare `import checked` would resolve only to
|
||||
// the checked.ww file. Referenced as `checked.*` (last path component).
|
||||
import math.checked;
|
||||
import types;
|
||||
|
||||
// Vectors ported verbatim from ref/hare/math/checked/checked.ha @test
|
||||
// blocks. The subi* boundary cases there feed types::I{8,16,32,64}_{MAX,
|
||||
// MIN}; written here as the literal boundary values (the value IS the
|
||||
// boundary) — a test vector, not a stand-in for the types def. Restore
|
||||
// the types-symbol form once it reads cleanly cross-module (#88).
|
||||
|
||||
@test fn test_addi8() void = {
|
||||
let (res, overflow) = checked.addi8(100, 20);
|
||||
if (res != 120) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.addi8(100, 50);
|
||||
if (res2 != -106) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_addi16() void = {
|
||||
let (res, overflow) = checked.addi16(32700, 60);
|
||||
if (res != 32760) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.addi16(32700, 100);
|
||||
if (res2 != -32736) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_addi32() void = {
|
||||
let (res, overflow) = checked.addi32(2147483600, 40);
|
||||
if (res != 2147483640) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.addi32(2147483600, 100);
|
||||
if (res2 != -2147483596) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_addi64() void = {
|
||||
let (res, overflow) = checked.addi64(9223372036854775800, 5);
|
||||
if (res != 9223372036854775805) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.addi64(9223372036854775800, 10);
|
||||
if (res2 != -9223372036854775806) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_addu8() void = {
|
||||
let (res, overflow) = checked.addu8(200u8, 50u8);
|
||||
if (res != 250u8) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.addu8(200u8, 100u8);
|
||||
if (res2 != 44u8) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_addu16() void = {
|
||||
let (res, overflow) = checked.addu16(65500u16, 30u16);
|
||||
if (res != 65530u16) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.addu16(65500u16, 50u16);
|
||||
if (res2 != 14u16) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_addu32() void = {
|
||||
let (res, overflow) = checked.addu32(4294967200u32, 90u32);
|
||||
if (res != 4294967290u32) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.addu32(4294967200u32, 100u32);
|
||||
if (res2 != 4u32) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_addu64() void = {
|
||||
let (res, overflow) = checked.addu64(18446744073709551600u64, 10u64);
|
||||
if (res != 18446744073709551610u64) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.addu64(18446744073709551610u64, 50u64);
|
||||
if (res2 != 44u64) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_subi8() void = {
|
||||
let (res, overflow) = checked.subi8(-100, 20);
|
||||
if (res != -120) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.subi8(-100, 50);
|
||||
if (res2 != 106) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
// I8_MAX, I8_MIN boundary
|
||||
let (res3, overflow3) = checked.subi8(127, -128);
|
||||
if (res3 != -1) { let _: i32 = 1/0; };
|
||||
if (!overflow3) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_subi16() void = {
|
||||
let (res, overflow) = checked.subi16(-32700, 60);
|
||||
if (res != -32760) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.subi16(-32700, 100);
|
||||
if (res2 != 32736) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
// I16_MAX, I16_MIN boundary
|
||||
let (res3, overflow3) = checked.subi16(32767, -32768);
|
||||
if (res3 != -1) { let _: i32 = 1/0; };
|
||||
if (!overflow3) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_subi32() void = {
|
||||
let (res, overflow) = checked.subi32(-2147483600, 40);
|
||||
if (res != -2147483640) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.subi32(-2147483600, 100);
|
||||
if (res2 != 2147483596) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
// I32_MAX, I32_MIN boundary
|
||||
let (res3, overflow3) = checked.subi32(2147483647, -2147483648);
|
||||
if (res3 != -1) { let _: i32 = 1/0; };
|
||||
if (!overflow3) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_subi64() void = {
|
||||
let (res, overflow) = checked.subi64(-9223372036854775800, 5);
|
||||
if (res != -9223372036854775805) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.subi64(-9223372036854775800, 10);
|
||||
if (res2 != 9223372036854775806) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
// I64_MAX, I64_MIN boundary — Hare's subi64 @test 3rd case
|
||||
// subi64(I64_MAX, I64_MIN). Omitted while #89 is open: the I64_MIN
|
||||
// literal -9223372036854775808 makes wwstage emit `MOVQ $-, AX`
|
||||
// (digits dropped — abs(i64-min) overflow in the wwstage literal
|
||||
// formatter) vs cstage's correct value, a rule-10 break. The
|
||||
// i8/i16/i32 boundary cases above are unaffected; restore this one
|
||||
// once #89 lands.
|
||||
};
|
||||
|
||||
@test fn test_subu8() void = {
|
||||
let (res, overflow) = checked.subu8(250u8, 50u8);
|
||||
if (res != 200u8) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.subu8(44u8, 100u8);
|
||||
if (res2 != 200u8) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_subu16() void = {
|
||||
let (res, overflow) = checked.subu16(65530u16, 30u16);
|
||||
if (res != 65500u16) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.subu16(14u16, 50u16);
|
||||
if (res2 != 65500u16) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_subu32() void = {
|
||||
let (res, overflow) = checked.subu32(4294967290u32, 90u32);
|
||||
if (res != 4294967200u32) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.subu32(4u32, 100u32);
|
||||
if (res2 != 4294967200u32) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_subu64() void = {
|
||||
let (res, overflow) = checked.subu64(18446744073709551610u64, 10u64);
|
||||
if (res != 18446744073709551600u64) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.subu64(44u64, 50u64);
|
||||
if (res2 != 18446744073709551610u64) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_muli8() void = {
|
||||
let (res, overflow) = checked.muli8(11, 11);
|
||||
if (res != 121) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.muli8(12, 12);
|
||||
if (res2 != -112) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_muli16() void = {
|
||||
let (res, overflow) = checked.muli16(181, 181);
|
||||
if (res != 32761) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.muli16(182, 182);
|
||||
if (res2 != -32412) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_muli32() void = {
|
||||
let (res, overflow) = checked.muli32(46340, 46340);
|
||||
if (res != 2147395600) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.muli32(46341, 46341);
|
||||
if (res2 != -2147479015) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_mulu8() void = {
|
||||
let (res, overflow) = checked.mulu8(15u8, 15u8);
|
||||
if (res != 225u8) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.mulu8(16u8, 16u8);
|
||||
if (res2 != 0u8) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_mulu16() void = {
|
||||
let (res, overflow) = checked.mulu16(255u16, 255u16);
|
||||
if (res != 65025u16) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.mulu16(256u16, 256u16);
|
||||
if (res2 != 0u16) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_mulu32() void = {
|
||||
let (res, overflow) = checked.mulu32(65535u32, 65535u32);
|
||||
if (res != 4294836225u32) { let _: i32 = 1/0; };
|
||||
if (overflow) { let _: i32 = 1/0; };
|
||||
let (res2, overflow2) = checked.mulu32(65536u32, 65536u32);
|
||||
if (res2 != 0u32) { let _: i32 = 1/0; };
|
||||
if (!overflow2) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
// Saturating vectors from ref/hare/math/checked/saturating.ha @test
|
||||
// blocks. Clamp cases compare to the types limit (as Hare does); the
|
||||
// I64_MIN comparisons use types.I64_MIN rather than the literal
|
||||
// -9223372036854775808 (which the wwstage formatter miscompiles, #89).
|
||||
|
||||
@test fn test_sat_addi8() void = {
|
||||
if (checked.sat_addi8(100, 20) != 120) { let _: i32 = 1/0; };
|
||||
if (checked.sat_addi8(100, 50) != types.I8_MAX) { let _: i32 = 1/0; };
|
||||
if (checked.sat_addi8(-100, -50) != types.I8_MIN) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_addi16() void = {
|
||||
if (checked.sat_addi16(32700, 60) != 32760) { let _: i32 = 1/0; };
|
||||
if (checked.sat_addi16(32700, 100) != types.I16_MAX) { let _: i32 = 1/0; };
|
||||
if (checked.sat_addi16(-32700, -100) != types.I16_MIN) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_addi32() void = {
|
||||
if (checked.sat_addi32(2147483600, 40) != 2147483640) { let _: i32 = 1/0; };
|
||||
if (checked.sat_addi32(2147483600, 100) != types.I32_MAX) { let _: i32 = 1/0; };
|
||||
if (checked.sat_addi32(-2147483600, -100) != types.I32_MIN) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_addi64() void = {
|
||||
if (checked.sat_addi64(9223372036854775800, 5) != 9223372036854775805) { let _: i32 = 1/0; };
|
||||
if (checked.sat_addi64(9223372036854775800, 10) != types.I64_MAX) { let _: i32 = 1/0; };
|
||||
if (checked.sat_addi64(-9223372036854775800, -10) != types.I64_MIN) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_addu8() void = {
|
||||
if (checked.sat_addu8(200u8, 50u8) != 250u8) { let _: i32 = 1/0; };
|
||||
if (checked.sat_addu8(200u8, 100u8) != types.U8_MAX) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_addu16() void = {
|
||||
if (checked.sat_addu16(65500u16, 30u16) != 65530u16) { let _: i32 = 1/0; };
|
||||
if (checked.sat_addu16(65500u16, 50u16) != types.U16_MAX) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_addu32() void = {
|
||||
if (checked.sat_addu32(4294967200u32, 90u32) != 4294967290u32) { let _: i32 = 1/0; };
|
||||
if (checked.sat_addu32(4294967200u32, 100u32) != types.U32_MAX) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_addu64() void = {
|
||||
if (checked.sat_addu64(18446744073709551600u64, 10u64) != 18446744073709551610u64) { let _: i32 = 1/0; };
|
||||
if (checked.sat_addu64(18446744073709551600u64, 50u64) != types.U64_MAX) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_subi8() void = {
|
||||
if (checked.sat_subi8(-100, 20) != -120) { let _: i32 = 1/0; };
|
||||
if (checked.sat_subi8(-100, 50) != types.I8_MIN) { let _: i32 = 1/0; };
|
||||
if (checked.sat_subi8(100, -50) != types.I8_MAX) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_subi16() void = {
|
||||
if (checked.sat_subi16(-32700, 60) != -32760) { let _: i32 = 1/0; };
|
||||
if (checked.sat_subi16(-32700, 100) != types.I16_MIN) { let _: i32 = 1/0; };
|
||||
if (checked.sat_subi16(32700, -100) != types.I16_MAX) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_subi32() void = {
|
||||
if (checked.sat_subi32(-2147483600, 40) != -2147483640) { let _: i32 = 1/0; };
|
||||
if (checked.sat_subi32(-2147483600, 100) != types.I32_MIN) { let _: i32 = 1/0; };
|
||||
if (checked.sat_subi32(2147483600, -100) != types.I32_MAX) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_subi64() void = {
|
||||
if (checked.sat_subi64(-9223372036854775800, 5) != -9223372036854775805) { let _: i32 = 1/0; };
|
||||
if (checked.sat_subi64(-9223372036854775800, 10) != types.I64_MIN) { let _: i32 = 1/0; };
|
||||
if (checked.sat_subi64(9223372036854775800, -10) != types.I64_MAX) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_muli8() void = {
|
||||
if (checked.sat_muli8(11, 11) != 121) { let _: i32 = 1/0; };
|
||||
if (checked.sat_muli8(12, 12) != types.I8_MAX) { let _: i32 = 1/0; };
|
||||
if (checked.sat_muli8(12, -12) != types.I8_MIN) { let _: i32 = 1/0; };
|
||||
if (checked.sat_muli8(-12, 12) != types.I8_MIN) { let _: i32 = 1/0; };
|
||||
if (checked.sat_muli8(-12, -12) != types.I8_MAX) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_muli16() void = {
|
||||
if (checked.sat_muli16(181, 181) != 32761) { let _: i32 = 1/0; };
|
||||
if (checked.sat_muli16(182, 182) != types.I16_MAX) { let _: i32 = 1/0; };
|
||||
if (checked.sat_muli16(182, -182) != types.I16_MIN) { let _: i32 = 1/0; };
|
||||
if (checked.sat_muli16(-182, 182) != types.I16_MIN) { let _: i32 = 1/0; };
|
||||
if (checked.sat_muli16(-182, -182) != types.I16_MAX) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_muli32() void = {
|
||||
if (checked.sat_muli32(46340, 46340) != 2147395600) { let _: i32 = 1/0; };
|
||||
if (checked.sat_muli32(46341, 46341) != types.I32_MAX) { let _: i32 = 1/0; };
|
||||
if (checked.sat_muli32(46341, -46341) != types.I32_MIN) { let _: i32 = 1/0; };
|
||||
if (checked.sat_muli32(-46341, 46341) != types.I32_MIN) { let _: i32 = 1/0; };
|
||||
if (checked.sat_muli32(-46341, -46341) != types.I32_MAX) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_mulu8() void = {
|
||||
if (checked.sat_mulu8(15u8, 15u8) != 225u8) { let _: i32 = 1/0; };
|
||||
if (checked.sat_mulu8(16u8, 16u8) != types.U8_MAX) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_mulu16() void = {
|
||||
if (checked.sat_mulu16(255u16, 255u16) != 65025u16) { let _: i32 = 1/0; };
|
||||
if (checked.sat_mulu16(256u16, 256u16) != types.U16_MAX) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
@test fn test_sat_mulu32() void = {
|
||||
if (checked.sat_mulu32(65535u32, 65535u32) != 4294836225u32) { let _: i32 = 1/0; };
|
||||
if (checked.sat_mulu32(65536u32, 65536u32) != types.U32_MAX) { let _: i32 = 1/0; };
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
test_addi8();
|
||||
test_addi16();
|
||||
test_addi32();
|
||||
test_addi64();
|
||||
test_addu8();
|
||||
test_addu16();
|
||||
test_addu32();
|
||||
test_addu64();
|
||||
test_subi8();
|
||||
test_subi16();
|
||||
test_subi32();
|
||||
test_subi64();
|
||||
test_subu8();
|
||||
test_subu16();
|
||||
test_subu32();
|
||||
test_subu64();
|
||||
test_muli8();
|
||||
test_muli16();
|
||||
test_muli32();
|
||||
test_mulu8();
|
||||
test_mulu16();
|
||||
test_mulu32();
|
||||
test_sat_addi8();
|
||||
test_sat_addi16();
|
||||
test_sat_addi32();
|
||||
test_sat_addi64();
|
||||
test_sat_addu8();
|
||||
test_sat_addu16();
|
||||
test_sat_addu32();
|
||||
test_sat_addu64();
|
||||
test_sat_subi8();
|
||||
test_sat_subi16();
|
||||
test_sat_subi32();
|
||||
test_sat_subi64();
|
||||
test_sat_muli8();
|
||||
test_sat_muli16();
|
||||
test_sat_muli32();
|
||||
test_sat_mulu8();
|
||||
test_sat_mulu16();
|
||||
test_sat_mulu32();
|
||||
return 0;
|
||||
};
|
||||
169
lib/math/checked/saturating.ww
Normal file
169
lib/math/checked/saturating.ww
Normal file
@@ -0,0 +1,169 @@
|
||||
// math/checked (saturating) — clamp-on-overflow arithmetic. Ported from
|
||||
// Hare's math::checked saturating siblings
|
||||
// (ref/hare/math/checked/saturating.ha). Part of the same `checked`
|
||||
// module as checked.ww. Each sat_* clamps to the type's range on
|
||||
// overflow instead of wrapping.
|
||||
//
|
||||
// Three ww adaptations vs Hare, all forced by language differences, none
|
||||
// behavioral:
|
||||
// - no if-as-expression (N_IF is statement-only, see strings.ww), so
|
||||
// Hare's `return if (c) X else Y` becomes an if-statement.
|
||||
// - unsigned overflow tests rely on inline wraparound in Hare; ww
|
||||
// truncates sub-word arithmetic only on store to a typed lvalue, so
|
||||
// the wrap is forced through a typed `res`.
|
||||
// - the mul overflow compares need an explicit widening cast (no
|
||||
// implicit promotion in ww).
|
||||
//
|
||||
// Deferred (faithful subset, Hare splits per type): sat_subu8/16/32/64
|
||||
// (reference types::U*_MIN, not yet exported by lib/types); sat_*z (no
|
||||
// `size` type, #85); sat_muli64/sat_mulu64/sat_muli/sat_mulu (need
|
||||
// math::mulu64); int/uint native-width sat_* (ww int is 64-bit — a width
|
||||
// divergence in the overflow boundary).
|
||||
|
||||
package checked;
|
||||
|
||||
import types;
|
||||
|
||||
export fn sat_addi8(a: i8, b: i8) i8 = {
|
||||
let res: i8 = a + b;
|
||||
if (a < 0 == b < 0 && a < 0 != res < 0) {
|
||||
if (res < 0) { return types.I8_MAX; };
|
||||
return types.I8_MIN;
|
||||
};
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_addi16(a: i16, b: i16) i16 = {
|
||||
let res: i16 = a + b;
|
||||
if (a < 0 == b < 0 && a < 0 != res < 0) {
|
||||
if (res < 0) { return types.I16_MAX; };
|
||||
return types.I16_MIN;
|
||||
};
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_addi32(a: i32, b: i32) i32 = {
|
||||
let res: i32 = a + b;
|
||||
if (a < 0 == b < 0 && a < 0 != res < 0) {
|
||||
if (res < 0) { return types.I32_MAX; };
|
||||
return types.I32_MIN;
|
||||
};
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_addi64(a: i64, b: i64) i64 = {
|
||||
let res: i64 = a + b;
|
||||
if (a < 0 == b < 0 && a < 0 != res < 0) {
|
||||
if (res < 0) { return types.I64_MAX; };
|
||||
return types.I64_MIN;
|
||||
};
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_addu8(a: u8, b: u8) u8 = {
|
||||
let res: u8 = a + b;
|
||||
if (res < a) { return types.U8_MAX; };
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_addu16(a: u16, b: u16) u16 = {
|
||||
let res: u16 = a + b;
|
||||
if (res < a) { return types.U16_MAX; };
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_addu32(a: u32, b: u32) u32 = {
|
||||
let res: u32 = a + b;
|
||||
if (res < a) { return types.U32_MAX; };
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_addu64(a: u64, b: u64) u64 = {
|
||||
let res: u64 = a + b;
|
||||
if (res < a) { return types.U64_MAX; };
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_subi8(a: i8, b: i8) i8 = {
|
||||
let res: i8 = a - b;
|
||||
if (a < 0 != b < 0 && a < 0 != res < 0) {
|
||||
if (res < 0) { return types.I8_MAX; };
|
||||
return types.I8_MIN;
|
||||
};
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_subi16(a: i16, b: i16) i16 = {
|
||||
let res: i16 = a - b;
|
||||
if (a < 0 != b < 0 && a < 0 != res < 0) {
|
||||
if (res < 0) { return types.I16_MAX; };
|
||||
return types.I16_MIN;
|
||||
};
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_subi32(a: i32, b: i32) i32 = {
|
||||
let res: i32 = a - b;
|
||||
if (a < 0 != b < 0 && a < 0 != res < 0) {
|
||||
if (res < 0) { return types.I32_MAX; };
|
||||
return types.I32_MIN;
|
||||
};
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_subi64(a: i64, b: i64) i64 = {
|
||||
let res: i64 = a - b;
|
||||
if (a < 0 != b < 0 && a < 0 != res < 0) {
|
||||
if (res < 0) { return types.I64_MAX; };
|
||||
return types.I64_MIN;
|
||||
};
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_muli8(a: i8, b: i8) i8 = {
|
||||
let fullres: int = a: int * b: int;
|
||||
let res: i8 = fullres: i8;
|
||||
if (res: int != fullres) {
|
||||
if (res < 0) { return types.I8_MAX; };
|
||||
return types.I8_MIN;
|
||||
};
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_muli16(a: i16, b: i16) i16 = {
|
||||
let fullres: int = a: int * b: int;
|
||||
let res: i16 = fullres: i16;
|
||||
if (res: int != fullres) {
|
||||
if (res < 0) { return types.I16_MAX; };
|
||||
return types.I16_MIN;
|
||||
};
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_muli32(a: i32, b: i32) i32 = {
|
||||
let fullres: i64 = a: i64 * b: i64;
|
||||
let res: i32 = fullres: i32;
|
||||
if (res: i64 != fullres) {
|
||||
if (res < 0) { return types.I32_MAX; };
|
||||
return types.I32_MIN;
|
||||
};
|
||||
return res;
|
||||
};
|
||||
|
||||
export fn sat_mulu8(a: u8, b: u8) u8 = {
|
||||
let res: uint = a: uint * b: uint;
|
||||
if (res > (types.U8_MAX): uint) { return types.U8_MAX; };
|
||||
return res: u8;
|
||||
};
|
||||
|
||||
export fn sat_mulu16(a: u16, b: u16) u16 = {
|
||||
let res: uint = a: uint * b: uint;
|
||||
if (res > (types.U16_MAX): uint) { return types.U16_MAX; };
|
||||
return res: u16;
|
||||
};
|
||||
|
||||
export fn sat_mulu32(a: u32, b: u32) u32 = {
|
||||
let res: u64 = a: u64 * b: u64;
|
||||
if (res > (types.U32_MAX): u64) { return types.U32_MAX; };
|
||||
return res: u32;
|
||||
};
|
||||
Reference in New Issue
Block a user