-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfxp.mjs
179 lines (155 loc) · 5.34 KB
/
fxp.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/*
* Simple library for calculations with fixed-point numbers. Each number has a scale in the number of bits.
* Calculations are done with the same scale, and the result has the same scale. Mixing scales is not supported.
*/
// const FP_BITS = 32
// const FP_BASE = Math.pow(2,FP_BITS)
// const FP_MASK = FP_BASE - 1
const ASSERTIONS = false
export class FxP {
/**
* @param {bigint} bigInt
* @param {number} scale
* @param {bigint} bigScale optional bigScale, must be the same as scale!
*/
constructor(bigInt, scale, bigScale = 0n) {
if (ASSERTIONS && typeof bigInt !== 'bigint') throw new Error(`intValue must be a bigint but is a ${typeof bigInt}`)
if (ASSERTIONS && typeof scale !== 'number') throw new Error(`scale must be a number but is a ${typeof bigint}`)
if (ASSERTIONS && bigScale && typeof bigScale !== 'bigint') throw new Error(`bigScale must be a bigint but is a ${typeof bigScale}`)
this.bigInt = bigInt
this.scale = scale
this.bigScale = bigScale || BigInt(scale)
}
add(other) {
if (ASSERTIONS && this.scale !== other.scale) throw new Error('Scales must be equal')
return new FxP(this.bigInt + other.bigInt, this.scale)
}
subtract(other) {
if (ASSERTIONS && this.scale !== other.scale) throw new Error('Scales must be equal')
return new FxP(this.bigInt - other.bigInt, this.scale)
}
multiply(other) {
if (ASSERTIONS && this.scale !== other.scale) throw new Error('Scales must be equal')
return new FxP((this.bigInt * other.bigInt) >> this.bigScale, this.scale, this.bigScale)
}
divide(other) {
if (ASSERTIONS && this.scale !== other.scale) throw new Error('Scales must be equal')
return new FxP((this.bigInt << this.bigScale) / other.bigInt, this.scale, this.bigScale)
}
min(other) {
if (ASSERTIONS && this.scale !== other.scale) throw new Error('Scales must be equal')
return this.bigInt < other.bigInt ? this : other
}
max(other) {
if (ASSERTIONS && this.scale !== other.scale) throw new Error('Scales must be equal')
return this.bigInt > other.bigInt ? this : other
}
leq(other) {
if (ASSERTIONS && this.scale !== other.scale) throw new Error('Scales must be equal')
return this.bigInt <= other.bigInt
}
/**
* Returns approximate number of bits of the integer value. Works for very large numbers, not for very small
*
* @param {BigInt} value
* @returns {number}
*/
bits() {
const n = this.bigInt >> this.bigScale
return n.toString(2).length - (n < 0 ? 2 : 1);
// return bits(this.bigInt >> this.bigScale)
}
withScale(scale) {
const diff = scale - this.scale
if (diff === 0) return this
if (diff > 0) {
return new FxP(this.bigInt << BigInt(diff), scale, this.bigScale + BigInt(diff))
} else {
return new FxP(this.bigInt >> BigInt(-diff), scale, this.bigScale + BigInt(diff))
}
}
/**
* Converts the fixed-point number to a number. Not that the value may be out of the range of a number.
* @returns {number}
*/
toNumber() {
return toNumber(this.bigInt, this.scale)
}
bigIntValue() {
return this.bigInt >> this.bigScale
}
toString() {
return `${this.bigInt} / 2^${this.scale} (${this.toNumber()})`
}
toJSON() {
return {
bigInt: this.bigInt.toString(),
scale: this.scale
}
}
}
export function fromNumber(value, scale = 60) {
if (Number.isInteger(value)) {
return fromIntNumber(value, scale)
}
let prescale = 0
const exp = exponent(value)
if (exp < 53) {
prescale = Math.min(1023, Math.min(scale, 53 - exp))
}
let sValue = Math.round(value * 2 ** prescale);
const bigScale = BigInt(scale)
const scaledValue = BigInt(sValue) << BigInt(scale - prescale)
return new FxP(scaledValue, scale, bigScale)
}
export function fromIntNumber(value, scale = 60) {
const bigScale = BigInt(scale)
return new FxP(BigInt(value) << bigScale, scale, bigScale)
}
export function toNumber(bigInt, scale) {
let exp = -scale
const size = bits(bigInt)
if (size > 512) {
const preScale = size - 512
const n = Number(bigInt >> BigInt(preScale))
return n * 2 ** (exp + preScale)
}
return Number(bigInt) * 2 ** exp
}
/**
* Calculates the 2-base exponent of the given number. Deals with 0 and negative numbers.
* @param {number} number
* @returns {number}
*/
function exponent(number) {
return (number === 0) ? 0 : Math.floor(Math.log2(Math.abs(number)))
}
/**
* Returns approximate number of bits of the given value.
* @param {BigInt} value
* @returns {number}
*/
function bits(value) {
return value > 0 ? ilog2(value) : ilog2(-value)
}
/**
* https://stackoverflow.com/questions/55355184/optimized-integer-logarithm-base2-for-bigint
* @param {BigInt} value
* @returns {number}
*/
function ilog2(value) {
let result = 0n, i, v
for (i = 1n; value >> (1n << i); i <<= 1n) {
}
while (value > 1n) {
v = 1n << --i
if (value >> v) {
result += v
value >>= v
}
}
return Number(result)
}
export function fromJSON(json) {
return new FxP(BigInt(json.bigInt), json.scale)
}