Skip to content

Commit 1eb2600

Browse files
Add safe u256 add and sub (#87)
1 parent 3fa87a0 commit 1eb2600

File tree

2 files changed

+310
-1
lines changed

2 files changed

+310
-1
lines changed

assembly/__tests__/safe_u256.spec.ts

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { u256Safe } from "../integer/safe/u256";
2+
3+
describe("Basic Operations", () => {
4+
describe("ADD", () => {
5+
it("Should add [1, 0, 0, 0] and [max, 0, 0, 0]", () => {
6+
var a = u256Safe.One;
7+
var b = new u256Safe(u64.MAX_VALUE, 0, 0, 0);
8+
var r = new u256Safe(0, 1, 0, 0);
9+
expect(a + b).toStrictEqual(r);
10+
expect(b + a).toStrictEqual(r);
11+
});
12+
it("Should add [1, 0, 0, 0] and [max, max, 0, 0]", () => {
13+
var a = u256Safe.One;
14+
var b = new u256Safe(u64.MAX_VALUE, u64.MAX_VALUE, 0, 0);
15+
var r = new u256Safe(0, 0, 1, 0);
16+
expect(a + b).toStrictEqual(r);
17+
expect(b + a).toStrictEqual(r);
18+
});
19+
it("Should add [1, 0, 0, 0] and [max, max, max, 0]", () => {
20+
var a = u256Safe.One;
21+
var b = new u256Safe(u64.MAX_VALUE, u64.MAX_VALUE, u64.MAX_VALUE, 0);
22+
var r = new u256Safe(0, 0, 0, 1);
23+
expect(a + b).toStrictEqual(r);
24+
expect(b + a).toStrictEqual(r);
25+
});
26+
it("Should add [1, 1, 1, 1] and [max - 1, max - 1, max - 1, max - 1]", () => {
27+
const one: u64 = 1;
28+
const pre = u64.MAX_VALUE - 1;
29+
var a = new u256Safe(one, one, one, one);
30+
var b = new u256Safe(pre, pre, pre, pre);
31+
var r = u256Safe.Max;
32+
expect(a + b).toStrictEqual(r);
33+
expect(b + a).toStrictEqual(r);
34+
});
35+
it("Should test carry and lo2 equality", () => {
36+
var a = new u256Safe(u64.MAX_VALUE, 1, 0, 0);
37+
var b = new u256Safe(1, 0, 0, 0);
38+
var r = new u256Safe(0, 2, 0, 0);
39+
expect(a + b).toStrictEqual(r);
40+
});
41+
it("Should add two numbers 1", () => {
42+
var a = u256Safe.from(23489);
43+
var b = u256Safe.from(1234);
44+
var r = u256Safe.from(24723);
45+
expect(a + b).toStrictEqual(r);
46+
expect(b + a).toStrictEqual(r);
47+
});
48+
it("Should add two numbers 2", () => {
49+
var a = u256Safe.from(43545453452452452);
50+
var b = u256Safe.from(1);
51+
var r = u256Safe.from(43545453452452453);
52+
expect(a + b).toStrictEqual(r);
53+
expect(b + a).toStrictEqual(r);
54+
});
55+
it("Should add two numbers 3", () => {
56+
var a = new u256Safe(6064648183073788001, 18412591705276258226, 18446744073709551615, 9223372036854775807);
57+
var b = new u256Safe(3061651127733543934, 42442096056813094);
58+
var r = new u256Safe(9126299310807331935, 8289727623519704, 0, 9223372036854775808);
59+
expect(a + b).toStrictEqual(r);
60+
expect(b + a).toStrictEqual(r);
61+
});
62+
});
63+
describe("SUB", () => {
64+
it("Should sub one minus one", () => {
65+
var a = u256Safe.One;
66+
var b = a;
67+
var r = u256Safe.Zero;
68+
expect(a - b).toStrictEqual(r);
69+
});
70+
it("Should sub [2, 2, 2, 2] and [1, 1, 1, 1]", () => {
71+
var a = new u256Safe(2, 2, 2, 2);
72+
var b = new u256Safe(1, 1, 1, 1);
73+
var r = b;
74+
expect(a - b).toStrictEqual(r);
75+
});
76+
it("Should sub [max, max, max, max] and [1, 2, 3, 4]", () => {
77+
const max = u64.MAX_VALUE;
78+
var a = u256Safe.Max;
79+
var b = new u256Safe(1, 2, 3, 4);
80+
var r = new u256Safe(max - 1, max - 2, max - 3, max - 4);
81+
expect(a - b).toStrictEqual(r);
82+
});
83+
it("Should sub [max, max, max, max] and [1, 0, 0, 0]", () => {
84+
const max = u64.MAX_VALUE;
85+
var a = u256Safe.Max;
86+
var b = u256Safe.One;
87+
var r = new u256Safe(max - 1, max, max, max);
88+
expect(a - b).toStrictEqual(r);
89+
});
90+
it("Should sub two numbers 1", () => {
91+
var a = u256Safe.from(23489);
92+
var b = u256Safe.from(1234);
93+
var r = u256Safe.from(22255);
94+
expect(a - b).toStrictEqual(r);
95+
});
96+
it("Should sub two numbers 2", () => {
97+
var a = u256Safe.from(43545453452452453);
98+
var b = u256Safe.from(1);
99+
var r = u256Safe.from(43545453452452452);
100+
expect(a - b).toStrictEqual(r);
101+
});
102+
it("Should sub two numbers 3", () => {
103+
var a = new u256Safe(6064648183073788001, 18412591705276258226, 18446744073709551615, 9223372036854775807);
104+
var b = new u256Safe(3061651127733543934, 42442096056813094);
105+
var r = new u256Safe(3002997055340244067, 18370149609219445132, 18446744073709551615, 9223372036854775807);
106+
expect(a - b).toStrictEqual(r);
107+
});
108+
});
109+
});
110+
111+
describe("Overflow Underflow Throwable", () => {
112+
describe("ADD", () => {
113+
it("Should throw when add two numbers 1", () => {
114+
expect(() => {
115+
var a = u256Safe.One;
116+
var b = u256Safe.Max;
117+
!(a + b);
118+
}).toThrow();
119+
});
120+
121+
it("Should throw when add two numbers 2", () => {
122+
expect(() => {
123+
var a = u256Safe.Max;
124+
var b = u256Safe.One;
125+
!(a + b);
126+
}).toThrow();
127+
});
128+
129+
it("Should throw when add two numbers 3", () => {
130+
expect(() => {
131+
var a = u256Safe.from(-2);
132+
var b = new u256Safe(2);
133+
!(a + b);
134+
}).toThrow();
135+
});
136+
});
137+
/* -------------------------------------------------------------------------- */
138+
/* SUB OVERFLOW */
139+
/* -------------------------------------------------------------------------- */
140+
describe("SUB", () => {
141+
it("Should throw when subtract two numbers 1", () => {
142+
expect(() => {
143+
var a = u256Safe.Zero;
144+
var b = u256Safe.Max;
145+
!(a - b);
146+
}).toThrow();
147+
});
148+
149+
it("Should throw when subtract two numbers 2", () => {
150+
expect(() => {
151+
var a = u256Safe.from(-2);
152+
var b = u256Safe.Max;
153+
!(a - b);
154+
}).toThrow();
155+
});
156+
157+
it("Should throw when subtract two numbers 3", () => {
158+
expect(() => {
159+
var a = u256Safe.Zero;
160+
var b = u256Safe.One;
161+
!(a - b);
162+
}).toThrow();
163+
});
164+
});
165+
});

assembly/integer/safe/u256.ts

+145-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,151 @@
1+
import { u128Safe as u128 } from './u128';
2+
import { u128 as U128 } from '../u128';
13
import { u256 as U256 } from '../u256';
24

35
class u256 extends U256 {
4-
// TODO override add, sub, inc, dec, mul, div, rem operators
6+
@inline static get Zero(): u256 {
7+
return new u256();
8+
}
9+
@inline static get One(): u256 {
10+
return new u256(1);
11+
}
12+
@inline static get Min(): u256 {
13+
return new u256();
14+
}
15+
@inline static get Max(): u256 {
16+
return new u256(-1, -1, -1, -1);
17+
}
18+
19+
@inline
20+
static fromU256(value: u256): u256 {
21+
return changetype<u256>(U256.fromU256(value));
22+
}
23+
24+
@inline
25+
static fromU128(value: u128): u256 {
26+
return changetype<u256>(U256.fromU128(value));
27+
}
28+
29+
@inline
30+
static fromI64(value: i64): u256 {
31+
return changetype<u256>(U256.fromI64(value));
32+
}
33+
34+
@inline
35+
static fromU64(value: u64): u256 {
36+
return changetype<u256>(U256.fromU64(value));
37+
}
38+
39+
@inline
40+
static fromF64(value: f64): u256 {
41+
return changetype<u256>(U256.fromF64(value));
42+
}
43+
44+
@inline
45+
static fromF32(value: f32): u256 {
46+
return changetype<u256>(U256.fromF32(value));
47+
}
48+
49+
@inline
50+
static fromI32(value: i32): u256 {
51+
return changetype<u256>(U256.fromI32(value));
52+
}
53+
54+
@inline
55+
static fromU32(value: u32): u256 {
56+
return changetype<u256>(U256.fromU32(value));
57+
}
58+
59+
@inline
60+
static fromBytes<T>(array: T, bigEndian: bool = false): u256 {
61+
return changetype<u256>(U256.fromBytes<T>(array, bigEndian));
62+
}
63+
64+
@inline
65+
static fromBytesLE(array: u8[]): u256 {
66+
return changetype<u256>(U256.fromBytesLE(array));
67+
}
68+
69+
@inline
70+
static fromBytesBE(array: u8[]): u256 {
71+
return changetype<u256>(U256.fromBytesBE(array));
72+
}
73+
74+
@inline
75+
static fromUint8ArrayLE(array: Uint8Array): u256 {
76+
return changetype<u256>(U256.fromUint8ArrayLE(array));
77+
}
78+
79+
@inline
80+
static fromUint8ArrayBE(array: Uint8Array): u256 {
81+
return changetype<u256>(U256.fromUint8ArrayBE(array));
82+
}
83+
84+
@inline
85+
static from<T>(value: T): u256 {
86+
if (value instanceof bool) return u256.fromU64(<u64>value);
87+
else if (value instanceof i8) return u256.fromI64(<i64>value);
88+
else if (value instanceof u8) return u256.fromU64(<u64>value);
89+
else if (value instanceof i16) return u256.fromI64(<i64>value);
90+
else if (value instanceof u16) return u256.fromU64(<u64>value);
91+
else if (value instanceof i32) return u256.fromI64(<i64>value);
92+
else if (value instanceof u32) return u256.fromU64(<u64>value);
93+
else if (value instanceof i64) return u256.fromI64(<i64>value);
94+
else if (value instanceof u64) return u256.fromU64(<u64>value);
95+
else if (value instanceof f32) return u256.fromF64(<f64>value);
96+
else if (value instanceof f64) return u256.fromF64(<f64>value);
97+
else if (value instanceof u128) return u256.fromU128(<u128>value);
98+
else if (value instanceof U128) return u256.fromU128(<U128>value);
99+
else if (value instanceof U256) return u256.fromU256(<U256>value);
100+
else if (value instanceof u256) return u256.fromU256(<u256>value);
101+
else if (value instanceof u8[]) return u256.fromBytes(<u8[]>value);
102+
else if (value instanceof Uint8Array) return u256.fromBytes(<Uint8Array>value);
103+
else throw new TypeError("Unsupported generic type");
104+
}
105+
106+
@operator("+")
107+
static add(a: u256, b: u256): u256 {
108+
var lo1a = a.lo1,
109+
lo2a = a.lo2,
110+
hi1a = a.hi1,
111+
hi2a = a.hi2;
112+
113+
var lo1b = b.lo1,
114+
lo2b = b.lo2,
115+
hi1b = b.hi1,
116+
hi2b = b.hi2;
117+
118+
// Addition for the lowest segment
119+
var lo1 = lo1a + lo1b;
120+
var cy = u64(lo1 < lo1a); // Detect carry
121+
122+
// Addition for the second lowest segment with carry
123+
var lo2 = lo2a + lo2b + cy;
124+
cy = u64(lo2 < lo2a || (cy == 1 && lo2 == lo2a)); // Update carry
125+
126+
// Addition for the second highest segment with carry
127+
var hi1 = hi1a + hi1b + cy;
128+
cy = u64(hi1 < hi1a || (cy == 1 && hi1 == hi1a)); // Update carry
129+
130+
// Addition for the highest segment with carry
131+
var hi2 = hi2a + hi2b + cy;
132+
133+
// Overflow detection after adding carry to the highest segment
134+
// In a 'safe' implementation, an overflow can be detected if the final carry would exceed the bounds of u256,
135+
// which means an addition that causes the highest segment to overflow.
136+
// However, standard unsigned integer behavior would wrap around, so this step depends on the intended behavior:
137+
if (hi2 < hi2a || (cy == 1 && hi2 == hi2a)) {
138+
throw new RangeError("Overflow during addition");
139+
}
140+
141+
return new u256(lo1, lo2, hi1, hi2);
142+
}
143+
144+
@operator("-")
145+
static sub(a: u256, b: u256): u256 {
146+
if (a < b) throw new RangeError("Underflow during subtraction");
147+
return changetype<u256>(U256.sub(changetype<U256>(a), changetype<U256>(b)));
148+
}
5149
}
6150

7151
export { u256 as u256Safe };

0 commit comments

Comments
 (0)