Skip to content

Commit

Permalink
(feat) Add u256 multiplication (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
peterjah authored Jun 16, 2023
1 parent 86d3236 commit 9767896
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 2 deletions.
91 changes: 91 additions & 0 deletions assembly/__tests__/u256.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { u128 } from '../integer/u128';
import { u256 } from '../integer/u256';

describe("String Conversion", () => {
Expand Down Expand Up @@ -348,4 +349,94 @@ describe("Basic Operations", () => {
var r = new u256(3, 4, 6, 8);
expect(a - b).toStrictEqual(r);
});

it("Should multiply two u256 numbers", () => {
var a = u256.from(3);
var b = u256.from(3);
var r = u256.from(9);

expect(a * b).toStrictEqual(r);
expect(b * a).toStrictEqual(r);
});

it("Should multiply two u256 numbers", () => {
var a = u256.from(43545453452);
var b = u256.from(2353454354);
var r = new u256(10248516654965971928, 5);

expect(a * b).toStrictEqual(r);
expect(b * a).toStrictEqual(r);
});

it("Should multiply two u256 numbers - 2", () => {
var a = u256.from(11);
var b = new u256(0, 2);
var r = new u256(0, 22);

expect(a * b).toStrictEqual(r);
expect(b * a).toStrictEqual(r);
});

it("Should multiply two u256 numbers - 3", () => {
var a = new u256(0, 3);
var b = new u256(0, 0,3);
var r = new u256(0, 0,0,9);

expect(a * b).toStrictEqual(r);
expect(b * a).toStrictEqual(r);
});

it("Should multiply two u256 numbers - 4", () => {
var a = u256.from(new u128(14083847773837265618, 6692605942));
var b = u256.from(new u128(18444665141527514289, 5354084802));
var r = new u256(5659639222556316466, 4474720309748468391, 17386035696907167262, 1);

expect(a * b).toStrictEqual(r);
expect(b * a).toStrictEqual(r);
});

it("Should multiply u256 numbers by 1", () => {
var a = u256.Max;
var b = u256.One;
var r = a;
expect(a * b).toStrictEqual(r);
expect(b * a).toStrictEqual(r);
});

it("Should multiply u256 numbers by 0", () => {
var a = new u256(5656466, 447478468391, 17386907167262, 1);
var b = u256.Zero;
var r = b;
expect(a * b).toStrictEqual(r);
expect(b * a).toStrictEqual(r);
});

it("Should multiply two u256 numbers with overflow", () => {
var a = new u256(0, 0, 1);
expect(a * a).toStrictEqual(u256.Zero);
});

it("Should multiply two u256 numbers with overflow - 2", () => {
var a = new u256(1, 0, 1);
expect(a * a).toStrictEqual(new u256(1, 0, 2));
});

it("Should multiply two u256 numbers with overflow - 3", () => {
var a = new u256(6, 0, 0, 420);
var b = new u256(0, 7, 0, 0);
var r = new u256(0, 42, 0, 0);

expect(a * b).toStrictEqual(r);
expect(b * a).toStrictEqual(r);
});

it("Should multiply two u256 numbers with overflow - 3", () => {
var a = new u256(2, 666, 666, 666);
var b = new u256(0, 0, 0, 3);
var r = new u256(0, 0, 0, 6);

expect(a * b).toStrictEqual(r);
expect(b * a).toStrictEqual(r);
});

});
67 changes: 65 additions & 2 deletions assembly/globals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { u256 } from './integer';
import { u128 } from './integer/u128';

// used for returning quotient and reminder from __divmod128
Expand All @@ -7,6 +8,8 @@ import { u128 } from './integer/u128';

// used for returning low and high part of __mulq64, __multi3 etc
@lazy export var __res128_hi: u64 = 0;
// used for returning 0 or 1
@lazy export var __carry: u64 = 0;

/**
* Convert 128-bit unsigned integer to 64-bit float
Expand Down Expand Up @@ -75,14 +78,74 @@ export function __umulq64(a: u64, b: u64): u64 {

var uv = u * v;
var w0 = uv & 0xFFFFFFFF;
uv = a * v + (uv >> 32);
uv = a * v + (uv >> 32);
var w1 = uv >> 32;
uv = u * b + (uv & 0xFFFFFFFF);
uv = u * b + (uv & 0xFFFFFFFF);

__res128_hi = a * b + w1 + (uv >> 32);
return (uv << 32) | w0;
}

// __umul64Hop computes (hi * 2^64 + lo) = z + (x * y)
// @ts-ignore: decorator
@inline
export function __umul64Hop(z: u64, x: u64, y: u64): u64 {
var lo = __umulq64(x, y);
lo = __uadd64(lo, z);
var hi = __res128_hi +__carry;
__res128_hi = hi;
return lo
}

// __umul64Step computes (hi * 2^64 + lo) = z + (x * y) + carry.
// @ts-ignore: decorator
@inline
export function __umul64Step(z: u64, x: u64, y: u64, carry: u64): u64 {
var lo = __umulq64(x, y)
lo = __uadd64(lo, carry);
var hi = __uadd64(__res128_hi, 0, __carry);
lo = __uadd64(lo, z);
hi += __carry;
__res128_hi = hi;
return lo
}

// __uadd64 returns the sum with carry of x, y and carry: sum = x + y + carry.
// The carry input must be 0 or 1; otherwise the behavior is undefined.
// The carryOut output is guaranteed to be 0 or 1.
// @ts-ignore: decorator
@inline
export function __uadd64(x: u64, y: u64, carry: u64 = 0): u64 {
var sum = x + y + carry
// // The sum will overflow if both top bits are set (x & y) or if one of them
// // is (x | y), and a carry from the lower place happened. If such a carry
// // happens, the top bit will be 1 + 0 + 1 = 0 (& ~sum).
__carry = ((x & y) | ((x | y) & ~sum)) >>> 63
return sum;

}

// u256 * u256 => u256 implemented from https://github.com/holiman/uint256
// @ts-ignore: decorator
@global
export function __mul256(x0: u64, x1: u64, x2: u64, x3: u64, y0: u64, y1: u64, y2: u64, y3: u64): u256 {
var lo1 = __umulq64(x0, y0);
var res1 = __umul64Hop(__res128_hi, x1, y0);
var res2 = __umul64Hop(__res128_hi, x2, y0);
var res3 = x3 * y0 + __res128_hi;

var lo2 = __umul64Hop(res1, x0, y1);
res2 = __umul64Step(res2, x1, y1, __res128_hi);
res3 += x2 * y1 + __res128_hi;

var hi1 = __umul64Hop(res2, x0, y2);
res3 += x1 * y2 + __res128_hi

var hi2 = __umul64Hop(res3, x0, y3);

return new u256(lo1, lo2, hi1, hi2);
}

// @ts-ignore: decorator
@global
export function __multi3(al: u64, ah: u64, bl: u64, bh: u64): u64 {
Expand Down
32 changes: 32 additions & 0 deletions assembly/integer/u256.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { i128 } from './i128';
import { u128 } from './u128';
import { u256toDecimalString } from "../utils";
import { __mul256 } from '../globals';

@lazy const HEX_CHARS = '0123456789abcdef';

Expand Down Expand Up @@ -141,6 +142,31 @@ export class u256 {
return new u256(<u64>value, mask, mask, mask);
}

/**
* Create 256-bit unsigned integer from generic type T
* @param value
* @returns 256-bit unsigned integer
*/
@inline
static from<T>(value: T): u256 {
if (value instanceof bool) return u256.fromU64(<u64>value);
else if (value instanceof i8) return u256.fromI64(<i64>value);
else if (value instanceof u8) return u256.fromU64(<u64>value);
else if (value instanceof i16) return u256.fromI64(<i64>value);
else if (value instanceof u16) return u256.fromU64(<u64>value);
else if (value instanceof i32) return u256.fromI64(<i64>value);
else if (value instanceof u32) return u256.fromU64(<u64>value);
else if (value instanceof i64) return u256.fromI64(<i64>value);
else if (value instanceof u64) return u256.fromU64(<u64>value);
else if (value instanceof f32) return u256.fromF64(<f64>value);
else if (value instanceof f64) return u256.fromF64(<f64>value);
else if (value instanceof u128) return u256.fromU128(<u128>value);
else if (value instanceof u256) return u256.fromU256(<u256>value);
else if (value instanceof u8[]) return u256.fromBytes(<u8[]>value);
else if (value instanceof Uint8Array) return u256.fromBytes(<Uint8Array>value);
else throw new TypeError("Unsupported generic type");
}

// TODO
// static fromString(str: string): u256

Expand Down Expand Up @@ -431,6 +457,12 @@ export class u256 {
return !u256.lt(a, b);
}

// mul: u256 x u256 = u256
@inline @operator('*')
static mul(a: u256, b: u256): u256 {
return __mul256(a.lo1, a.lo2, a.hi1, a.hi2, b.lo1, b.lo2, b.hi1, b.hi2)
}

@inline
static popcnt(value: u256): i32 {
var count = popcnt(value.lo1);
Expand Down

0 comments on commit 9767896

Please sign in to comment.