wangzhibo
2026-05-09 53418655c061ac26f66058253f39200aced1ab1d
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/**
 * @copyright (c) 2016, Philipp Thürwächter & Pattrick Hüper
 * @copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
 * @license BSD-3-Clause (see LICENSE in the root directory of this source tree)
 */
import { ArithmeticException } from './errors';
 
export const MAX_SAFE_INTEGER = 9007199254740991;
export const MIN_SAFE_INTEGER = -9007199254740991;
 
/**
 * Math helper with static function for integer operations
 */
export class MathUtil {
    /**
     *
     * @param {number} x
     * @param {number} y
     * @returns {number}
     */
    static intDiv(x, y) {
        let r = x/y;
        r = MathUtil.roundDown(r);
        return MathUtil.safeZero(r);
    }
 
    /**
     *
     * @param {number} x
     * @param {number} y
     * @returns {number}
     */
    static intMod(x, y) {
        let r = x - MathUtil.intDiv(x, y) * y;
        r = MathUtil.roundDown(r);
        return MathUtil.safeZero(r);
    }
 
    /**
     *
     * @param {number} r
     * @returns {number}
     */
    static roundDown(r){
        if (r < 0) {
            return Math.ceil(r);
        } else {
            return Math.floor(r);
        }
    }
 
    /**
     *
     * @param {number} x
     * @param {number} y
     * @returns {number}
     */
    static floorDiv(x, y){
        const r = Math.floor(x / y);
        return MathUtil.safeZero(r);
    }
 
    /**
     *
     * @param {number} x
     * @param {number} y
     * @returns {number}
     */
    static floorMod(x, y){
        const r = x - MathUtil.floorDiv(x, y) * y;
        return MathUtil.safeZero(r);
    }
 
    /**
     *
     * @param {number} x
     * @param {number} y
     * @returns {number}
     */
    static safeAdd(x, y) {
        MathUtil.verifyInt(x);
        MathUtil.verifyInt(y);
        if (x === 0) {
            return MathUtil.safeZero(y);
        }
        if (y === 0) {
            return MathUtil.safeZero(x);
        }
        const r = MathUtil.safeToInt(x + y);
        if (r === x || r === y) {
            throw new ArithmeticException('Invalid addition beyond MAX_SAFE_INTEGER!');
        }
        return r;
    }
 
    /**
     *
     * @param {number} x
     * @param {number} y
     * @returns {number}
     */
    static safeSubtract(x, y) {
        MathUtil.verifyInt(x);
        MathUtil.verifyInt(y);
        if (x === 0 && y === 0) {
            return 0;
        } else if (x === 0) {
            return MathUtil.safeZero(-1 * y);
        } else if (y === 0) {
            return MathUtil.safeZero(x);
        }
        return MathUtil.safeToInt(x - y);
    }
 
    /**
     *
     * @param {number} x
     * @param {number} y
     * @returns {number}
     */
    static safeMultiply(x, y) {
        MathUtil.verifyInt(x);
        MathUtil.verifyInt(y);
        if (x === 1) {
            return MathUtil.safeZero(y);
        }
        if (y === 1) {
            return MathUtil.safeZero(x);
        }
        if (x === 0 || y === 0) {
            return 0;
        }
        const r = MathUtil.safeToInt(x * y);
        if (r / y !== x || (x === MIN_SAFE_INTEGER && y === -1) || (y === MIN_SAFE_INTEGER && x === -1)) {
            throw new ArithmeticException(`Multiplication overflows: ${x} * ${y}`);
        }
        return r;
    }
 
    /**
     *
     * @param {number} value
     * @returns {number}
     */
    static parseInt(value) {
        const r = parseInt(value);
        return MathUtil.safeToInt(r);
    }
 
    /**
     *
     * @param {number} value
     * @returns {number}
     */
    static safeToInt(value) {
        MathUtil.verifyInt(value);
        return MathUtil.safeZero(value);
    }
 
    /**
     *
     * @param {number} value
     */
    static verifyInt(value){
        if (value == null) {
            throw new ArithmeticException(`Invalid value: '${value}', using null or undefined as argument`);
        }
        if (isNaN(value)) {
            throw new ArithmeticException('Invalid int value, using NaN as argument');
        }
        if (Number.isInteger) {
            if (!Number.isInteger(Number(value))) {
                throw new ArithmeticException(`Invalid value: '${value}' is a float`);
            }
        } else if ((value % 1) !== 0) { // IE11 does not support Number.isInteger
            throw new ArithmeticException(`Invalid value: '${value}' is a float`);
        }
        if (value > MAX_SAFE_INTEGER || value < MIN_SAFE_INTEGER) {
            throw new ArithmeticException(`Calculation overflows an int: ${value}`);
        }
    }
 
    /**
     * convert -0 to 0 and int as string to a number ( '1' -> 1 )
     *
     * @param {number} value
     * @returns {number}
     */
    static safeZero(value){
        return value === 0 ? 0 : +value;
    }
 
    /**
     * Compares two Numbers.
     *
     * @param {number} a  the first value
     * @param {number} b  the second value
     * @return {number} the result
     */
    static compareNumbers(a, b) {
        if (a < b) {
            return -1;
        }
        if (a > b) {
            return 1;
        }
        return 0;
    }
 
    // convert to small integer for v8 optimisation
    static smi(int) {
        return ((int >>> 1) & 0x40000000) | (int & 0xBFFFFFFF);
    }
 
    // calculate 32 bit hash of a number and convert to SMI
    static hash(number) {
        if (number !== number || number === Infinity) {
            return 0;
        }
        let result = number;
        while (number > 0xFFFFFFFF) {
            number /= 0xFFFFFFFF;
            result ^= number;
        }
        return MathUtil.smi(result);
    }
 
    // default hashCode calculation for a number sequence as mentioned by Joshua Bloch
    static hashCode(...numbers) {
        let result = 17;
        for (const n of numbers) {
            result = (result << 5) - result + MathUtil.hash(n);
        }
        return MathUtil.hash(result);
    }
}
 
MathUtil.MAX_SAFE_INTEGER = MAX_SAFE_INTEGER;
MathUtil.MIN_SAFE_INTEGER = MIN_SAFE_INTEGER;