@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 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
@@ -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"}
@@ -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"
@@ -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
+ }