This repository has been archived by the owner on Jun 11, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #35 from LiskHQ/feature/q96-number-system
Implement Q96 number format - Closes #20
- Loading branch information
Showing
6 changed files
with
257 additions
and
4 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
/* | ||
* Copyright © 2022 Lisk Foundation | ||
* | ||
* See the LICENSE file at the top-level directory of this distribution | ||
* for licensing information. | ||
* | ||
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, | ||
* no part of this software, including this file, may be copied, modified, | ||
* propagated, or distributed except according to the terms contained in the | ||
* LICENSE file. | ||
* | ||
* Removal or modification of this copyright notice is prohibited. | ||
*/ | ||
|
||
import { | ||
toBufferBE | ||
} from 'bigint-buffer'; | ||
import { | ||
Q96 | ||
} from '../types'; | ||
import { | ||
MAX_NUM_BYTES_Q96 | ||
} from '../constants'; | ||
import { | ||
ONE, | ||
TWO, | ||
N_96 | ||
} from "./mathConstants"; | ||
|
||
|
||
//Fixed Point Arithmetic | ||
export const numberToQ96 = (r: bigint): Q96 => { | ||
const _exp: bigint = TWO ** N_96; | ||
const _r = BigInt(r); | ||
const num: bigint = _r * _exp; | ||
|
||
return num; | ||
} | ||
|
||
export const roundDownQ96 = (a: Q96): bigint => a >> N_96 | ||
|
||
export const roundUpQ96 = (a: Q96): bigint => { | ||
const _x = ONE << N_96; | ||
const _y = a % _x; | ||
|
||
const _z = Number(_y); | ||
|
||
if (_z === 0) { | ||
return a >> N_96; | ||
} | ||
|
||
const _r = a >> N_96; | ||
return _r + ONE; | ||
|
||
} | ||
|
||
|
||
// Arithmetic | ||
export const addQ96 = (a: Q96, b: Q96): Q96 => a + b | ||
|
||
export const subQ96 = (a: Q96, b: Q96): Q96 => { | ||
if (a >= b) { | ||
return a - b; | ||
} | ||
|
||
throw new Error ("Result can't be negative"); | ||
} | ||
|
||
export const mulQ96 = (a: Q96, b: Q96): Q96 => { | ||
const _x: bigint = a * b; | ||
return _x >> N_96; | ||
} | ||
|
||
export const divQ96 = (a: Q96, b: Q96): Q96 => { | ||
const _x: Q96 = a << N_96; | ||
return _x / b; | ||
} | ||
|
||
export const mulDivQ96 = (a: Q96, b: Q96, c: Q96): bigint => { | ||
const _x: bigint = a * b; | ||
const _y: bigint = _x << N_96; | ||
const _z: bigint = _y / c; | ||
|
||
return roundDownQ96(_z); | ||
} | ||
|
||
export const mulDivRoundUpQ96 = (a: Q96, b: Q96, c: Q96): bigint => { | ||
const _x: bigint = a * b; | ||
const _y: bigint = _x << N_96; | ||
const _z: bigint = _y / c; | ||
|
||
return roundUpQ96(_z) | ||
} | ||
|
||
export const q96ToInt = (a: Q96): bigint => roundDownQ96(a) | ||
|
||
export const q96ToIntRoundUp = (a: Q96): bigint => roundUpQ96(a) | ||
|
||
export const invQ96 = (a: Q96): Q96 => { | ||
const _x: bigint = ONE << N_96; | ||
return divQ96(_x, a); | ||
} | ||
|
||
|
||
// Q96 Encoding and Decoding | ||
export const bytesToQ96 = (numberBytes: Buffer): Q96 => { | ||
if (numberBytes.length > MAX_NUM_BYTES_Q96) { | ||
throw new Error(); | ||
} | ||
|
||
const _hex: string[] = []; | ||
|
||
for (let i = 0; i < numberBytes.length; i++) { | ||
let current = numberBytes[i] < 0 ? numberBytes[i] + 256 : numberBytes[i]; | ||
_hex.push((current >>> 4).toString(16)); | ||
_hex.push((current & 0xF).toString(16)); | ||
} | ||
|
||
const _hex_bi = _hex.join(""); | ||
|
||
//return big-endian decoding of bytes | ||
return BigInt(`0x${_hex_bi}`); | ||
} | ||
|
||
export const q96ToBytes = (numberQ96: Q96): Buffer => { | ||
|
||
const _hex: string = numberQ96.toString(16); | ||
let _byteArr: number[] = []; | ||
|
||
for (let c = 0; c < _hex.length; c += 2) { | ||
_byteArr.push(parseInt(_hex.substring(c, 2), 16)); | ||
} | ||
|
||
if (_byteArr.length > MAX_NUM_BYTES_Q96) { | ||
throw new Error("Overflow when serializing a Q96 number") | ||
} | ||
|
||
// return result padded to length NUM_BYTES_Q96 with zero bytes | ||
return toBufferBE(BigInt(`0x${_hex}`), MAX_NUM_BYTES_Q96) //big-endian encoding of numberQ96 as integer | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { | ||
addQ96, | ||
divQ96, | ||
numberToQ96, | ||
mulQ96, | ||
subQ96, | ||
invQ96, | ||
bytesToQ96, | ||
q96ToBytes, | ||
q96ToInt, | ||
mulDivQ96, | ||
q96ToIntRoundUp | ||
} from '../../../../src/app/modules/dex/utils/q96'; | ||
|
||
import { | ||
Q96 | ||
} from '../../../../src/app/modules/dex/types'; | ||
|
||
import { | ||
MAX_SQRT_RATIO, | ||
MAX_UINT_64 | ||
} from '../../../../src/app/modules/dex/constants'; | ||
|
||
describe('DexQ96Module', () => { | ||
describe('constructor', () => { | ||
const testValueMax: Q96 = numberToQ96(MAX_SQRT_RATIO); | ||
const testValueMaxUint: Q96 = numberToQ96(BigInt(MAX_UINT_64)); | ||
const testValue2: Q96 = numberToQ96(BigInt(2)); | ||
|
||
it('add', async () => { | ||
expect(q96ToInt(addQ96(testValueMaxUint, testValue2))).toBe(BigInt("18446744073709551617")); | ||
}); | ||
|
||
it('sub', async () => { | ||
expect(q96ToInt(subQ96(testValueMax, testValue2))).toBe(BigInt("1461446703529909599612049957420313862569572983182")); | ||
}); | ||
|
||
it('mul', async () => { | ||
expect(q96ToInt(mulQ96(testValueMaxUint, testValue2))).toBe(BigInt("36893488147419103230")); | ||
}); | ||
|
||
it('div', async () => { | ||
const one = numberToQ96(BigInt(1)); | ||
const div = divQ96(one, testValue2); | ||
expect(q96ToInt(div)).toEqual(BigInt("0")); | ||
expect(q96ToIntRoundUp(div)).toEqual(BigInt("1")); | ||
|
||
const test = mulQ96(testValue2, testValueMaxUint); | ||
expect(divQ96(test, (testValueMaxUint))).toEqual(testValue2); | ||
|
||
const zero = numberToQ96(BigInt(1)); | ||
expect(divQ96(testValue2, zero)).toThrow(); | ||
}); | ||
|
||
it('mulDiv', async () => { | ||
const test = divQ96(testValueMax, testValueMax); | ||
expect(mulDivQ96((test), numberToQ96(BigInt('4')), numberToQ96(BigInt('4')))).toEqual(test); | ||
}); | ||
|
||
it('invQ96', async () => { | ||
const three = numberToQ96(BigInt(3)); // 3 is an odd number, not nicely invertible in binary | ||
expect(q96ToInt(invQ96(invQ96(three)))).toEqual(BigInt("3")); | ||
}); | ||
|
||
it('bytesToQ96 and q96ToBytes', async () => { | ||
expect(bytesToQ96(q96ToBytes(testValueMaxUint))).toBe(testValueMaxUint) | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters