@exponent-labs/precise-number 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +18 -0
- package/build/index.js.map +1 -0
- package/build/pnum.d.ts +21 -0
- package/build/pnum.js +69 -0
- package/build/pnum.js.map +1 -0
- package/jest.config.ts +10 -0
- package/package.json +24 -0
- package/src/index.ts +1 -0
- package/src/pnum.test.ts +32 -0
- package/src/pnum.ts +74 -0
- package/tsconfig.json +17 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
## [0.0.3](https://github.com/exponent-finance/exponent-core/compare/@exponent-labs/precise-number@0.0.2...@exponent-labs/precise-number@0.0.3) (2025-03-03)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @exponent-labs/precise-number
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.0.2](https://github.com/exponent-finance/exponent-core/compare/@exponent-labs/precise-number@0.0.1...@exponent-labs/precise-number@0.0.2) (2025-03-03)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package @exponent-labs/precise-number
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./pnum";
|
package/build/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./pnum"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,yCAAsB"}
|
package/build/pnum.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import BN from "bn.js";
|
|
2
|
+
export type PNumRaw = [BN, BN, BN, BN];
|
|
3
|
+
export declare const DECIMAL_PLACES = 12;
|
|
4
|
+
export declare const ONE: number;
|
|
5
|
+
export declare class PreciseNumber {
|
|
6
|
+
value: PNumRaw;
|
|
7
|
+
valueString: string;
|
|
8
|
+
constructor(val: string);
|
|
9
|
+
/** Serialize for Solana Anchor deserialization
|
|
10
|
+
* The PreciseNumber type is a tuple struct `PreciseNumber([u64; 4])` in Rust
|
|
11
|
+
*/
|
|
12
|
+
serialize(): {
|
|
13
|
+
0: PNumRaw;
|
|
14
|
+
};
|
|
15
|
+
anchorify(): {
|
|
16
|
+
0: PNumRaw;
|
|
17
|
+
};
|
|
18
|
+
static fromRaw(raw: BN[]): PreciseNumber;
|
|
19
|
+
}
|
|
20
|
+
export declare function stringToU256(str: string, decimalPlaces?: number): PNumRaw;
|
|
21
|
+
export declare function u256ToString(bnArray: BN[], decimalPlaces?: number): string;
|
package/build/pnum.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.u256ToString = exports.stringToU256 = exports.PreciseNumber = exports.ONE = exports.DECIMAL_PLACES = void 0;
|
|
7
|
+
const bn_js_1 = __importDefault(require("bn.js"));
|
|
8
|
+
const decimal_js_1 = __importDefault(require("decimal.js"));
|
|
9
|
+
exports.DECIMAL_PLACES = 12;
|
|
10
|
+
exports.ONE = 10 ** exports.DECIMAL_PLACES;
|
|
11
|
+
class PreciseNumber {
|
|
12
|
+
value;
|
|
13
|
+
valueString;
|
|
14
|
+
constructor(val) {
|
|
15
|
+
this.valueString = val;
|
|
16
|
+
this.value = stringToU256(val);
|
|
17
|
+
}
|
|
18
|
+
/** Serialize for Solana Anchor deserialization
|
|
19
|
+
* The PreciseNumber type is a tuple struct `PreciseNumber([u64; 4])` in Rust
|
|
20
|
+
*/
|
|
21
|
+
serialize() {
|
|
22
|
+
return { 0: this.value };
|
|
23
|
+
}
|
|
24
|
+
anchorify() {
|
|
25
|
+
return this.serialize();
|
|
26
|
+
}
|
|
27
|
+
static fromRaw(raw) {
|
|
28
|
+
if (raw.length !== 4) {
|
|
29
|
+
throw new Error("Invalid input: raw must be an array of 4 BNs");
|
|
30
|
+
}
|
|
31
|
+
const n = u256ToString(raw);
|
|
32
|
+
return new PreciseNumber(n);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.PreciseNumber = PreciseNumber;
|
|
36
|
+
function stringToU256(str, decimalPlaces = exports.DECIMAL_PLACES) {
|
|
37
|
+
try {
|
|
38
|
+
// 1. Parse as Decimal for precision
|
|
39
|
+
const decimalValue = new decimal_js_1.default(str);
|
|
40
|
+
// 2. Scale to integer representation
|
|
41
|
+
const scaledValue = decimalValue.times(new decimal_js_1.default(10).pow(decimalPlaces));
|
|
42
|
+
const integer = BigInt(scaledValue.round().toHexadecimal());
|
|
43
|
+
const u64Words = [];
|
|
44
|
+
for (let i = 0; i < 4; i++) {
|
|
45
|
+
const word = (integer >> BigInt(i * 64)) & BigInt("0xFFFFFFFFFFFFFFFF");
|
|
46
|
+
u64Words.push(new bn_js_1.default(word.toString()));
|
|
47
|
+
}
|
|
48
|
+
return u64Words;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
throw new Error(`Invalid input: ${error}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.stringToU256 = stringToU256;
|
|
55
|
+
function u256ToString(bnArray, decimalPlaces = exports.DECIMAL_PLACES) {
|
|
56
|
+
if (bnArray.length !== 4) {
|
|
57
|
+
throw new Error("Invalid input: bnArray must be an array of 4 BNs");
|
|
58
|
+
}
|
|
59
|
+
// join words
|
|
60
|
+
const integer = bnArray[0].add(bnArray[1].shln(64)).add(bnArray[2].shln(128)).add(bnArray[3].shln(192));
|
|
61
|
+
// 2. Convert BN to Decimal
|
|
62
|
+
const decimalValue = new decimal_js_1.default(integer.toString());
|
|
63
|
+
// 3. Scale back to original fixed-point value
|
|
64
|
+
const originalValue = decimalValue.div(new decimal_js_1.default(10).pow(decimalPlaces));
|
|
65
|
+
// 4. Return the string representation
|
|
66
|
+
return originalValue.toString();
|
|
67
|
+
}
|
|
68
|
+
exports.u256ToString = u256ToString;
|
|
69
|
+
//# sourceMappingURL=pnum.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pnum.js","sourceRoot":"","sources":["../src/pnum.ts"],"names":[],"mappings":"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"}
|
package/jest.config.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// jest.config.ts
|
|
2
|
+
import type { Config } from "@jest/types"
|
|
3
|
+
|
|
4
|
+
const config: Config.InitialOptions = {
|
|
5
|
+
preset: "ts-jest",
|
|
6
|
+
testEnvironment: "node",
|
|
7
|
+
testMatch: ["**/*.test.ts"], // Adjust the pattern if your test files have a different naming convention
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default config
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@exponent-labs/precise-number",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"main": "build/index.js",
|
|
5
|
+
"types": "build/index.d.ts",
|
|
6
|
+
"license": "AGPL-3.0",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc --build",
|
|
9
|
+
"test": "jest"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"bn.js": "5.2.1",
|
|
13
|
+
"decimal.js": "10.4.3"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/bn.js": "^5.1.0",
|
|
17
|
+
"@types/jest": "^29.5.12",
|
|
18
|
+
"jest": "^29.7.0",
|
|
19
|
+
"ts-jest": "^29.1.2",
|
|
20
|
+
"ts-node": "10.9.2",
|
|
21
|
+
"typescript": "5.4.5"
|
|
22
|
+
},
|
|
23
|
+
"gitHead": "209b8847e9a0fadb5b5ec96b9b47f0ace4a3bf9d"
|
|
24
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./pnum"
|
package/src/pnum.test.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import BN from "bn.js"
|
|
2
|
+
import { ONE, stringToU256, u256ToString } from "./pnum"
|
|
3
|
+
|
|
4
|
+
describe("precise-number", () => {
|
|
5
|
+
it("handles 0", () => {
|
|
6
|
+
const pn = stringToU256("0")
|
|
7
|
+
expect(pn[0].isZero()).toBeTruthy()
|
|
8
|
+
expect(pn[1].isZero()).toBeTruthy()
|
|
9
|
+
expect(pn[2].isZero()).toBeTruthy()
|
|
10
|
+
expect(pn[3].isZero()).toBeTruthy()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it("handles 1", () => {
|
|
14
|
+
const pn = stringToU256("1")
|
|
15
|
+
expect(pn[0].toNumber()).toEqual(ONE)
|
|
16
|
+
expect(pn[1].isZero()).toBeTruthy()
|
|
17
|
+
expect(pn[2].isZero()).toBeTruthy()
|
|
18
|
+
expect(pn[3].isZero()).toBeTruthy()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it("handles multiple words", () => {
|
|
22
|
+
const val = "1000000000"
|
|
23
|
+
const pn = stringToU256(val)
|
|
24
|
+
const target = new BN(val).mul(new BN(ONE))
|
|
25
|
+
const pnVal = pn[0].add(pn[1].shln(64)).add(pn[2].shln(128)).add(pn[3].shln(192))
|
|
26
|
+
expect(pnVal.eq(target)).toBeTruthy()
|
|
27
|
+
|
|
28
|
+
// Check that it goes back to the original value
|
|
29
|
+
const s = u256ToString(pn)
|
|
30
|
+
expect(s).toEqual(val)
|
|
31
|
+
})
|
|
32
|
+
})
|
package/src/pnum.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import BN from "bn.js"
|
|
2
|
+
import Decimal from "decimal.js"
|
|
3
|
+
|
|
4
|
+
export type PNumRaw = [BN, BN, BN, BN]
|
|
5
|
+
export const DECIMAL_PLACES = 12
|
|
6
|
+
|
|
7
|
+
export const ONE = 10 ** DECIMAL_PLACES
|
|
8
|
+
|
|
9
|
+
export class PreciseNumber {
|
|
10
|
+
public value: PNumRaw
|
|
11
|
+
public valueString: string
|
|
12
|
+
|
|
13
|
+
constructor(val: string) {
|
|
14
|
+
this.valueString = val
|
|
15
|
+
this.value = stringToU256(val)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Serialize for Solana Anchor deserialization
|
|
19
|
+
* The PreciseNumber type is a tuple struct `PreciseNumber([u64; 4])` in Rust
|
|
20
|
+
*/
|
|
21
|
+
serialize() {
|
|
22
|
+
return { 0: this.value }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
anchorify() {
|
|
26
|
+
return this.serialize()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static fromRaw(raw: BN[]): PreciseNumber {
|
|
30
|
+
if (raw.length !== 4) {
|
|
31
|
+
throw new Error("Invalid input: raw must be an array of 4 BNs")
|
|
32
|
+
}
|
|
33
|
+
const n = u256ToString(raw)
|
|
34
|
+
return new PreciseNumber(n)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function stringToU256(str: string, decimalPlaces = DECIMAL_PLACES): PNumRaw {
|
|
39
|
+
try {
|
|
40
|
+
// 1. Parse as Decimal for precision
|
|
41
|
+
const decimalValue = new Decimal(str)
|
|
42
|
+
|
|
43
|
+
// 2. Scale to integer representation
|
|
44
|
+
const scaledValue = decimalValue.times(new Decimal(10).pow(decimalPlaces))
|
|
45
|
+
const integer = BigInt(scaledValue.round().toHexadecimal())
|
|
46
|
+
const u64Words: BN[] = []
|
|
47
|
+
for (let i = 0; i < 4; i++) {
|
|
48
|
+
const word = (integer >> BigInt(i * 64)) & BigInt("0xFFFFFFFFFFFFFFFF")
|
|
49
|
+
u64Words.push(new BN(word.toString()))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return u64Words as PNumRaw
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw new Error(`Invalid input: ${error}`)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function u256ToString(bnArray: BN[], decimalPlaces = DECIMAL_PLACES): string {
|
|
59
|
+
if (bnArray.length !== 4) {
|
|
60
|
+
throw new Error("Invalid input: bnArray must be an array of 4 BNs")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// join words
|
|
64
|
+
const integer = bnArray[0].add(bnArray[1].shln(64)).add(bnArray[2].shln(128)).add(bnArray[3].shln(192))
|
|
65
|
+
|
|
66
|
+
// 2. Convert BN to Decimal
|
|
67
|
+
const decimalValue = new Decimal(integer.toString())
|
|
68
|
+
|
|
69
|
+
// 3. Scale back to original fixed-point value
|
|
70
|
+
const originalValue = decimalValue.div(new Decimal(10).pow(decimalPlaces))
|
|
71
|
+
|
|
72
|
+
// 4. Return the string representation
|
|
73
|
+
return originalValue.toString()
|
|
74
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"rootDir": "src",
|
|
4
|
+
"sourceMap": true,
|
|
5
|
+
"incremental": true /* Save .tsbuildinfo files to allow for incremental compilation of projects. */,
|
|
6
|
+
"composite": true /* Enable constraints that allow a TypeScript project to be used with project references. */,
|
|
7
|
+
"target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
|
8
|
+
"module": "CommonJS" /* Specify what module code is generated. */,
|
|
9
|
+
"declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
|
|
10
|
+
"outDir": "./build" /* Specify an output folder for all emitted files. */,
|
|
11
|
+
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
|
12
|
+
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
|
13
|
+
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"],
|
|
16
|
+
"exclude": ["build", "node_modules", "**/*.test.ts"]
|
|
17
|
+
}
|