@obinexusmk2/hypernum 0.1.0 → 0.1.1
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/LICENSE +21 -21
- package/README.md +355 -256
- package/dist/index.cjs +4 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -7
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +2 -2
- package/dist/index.umd.js.map +1 -1
- package/dist/types/{config → src/config}/config-loader.d.ts +0 -0
- package/dist/types/src/config/config-loader.d.ts.map +1 -0
- package/dist/types/{config → src/config}/config-parser.d.ts +0 -0
- package/dist/types/src/config/config-parser.d.ts.map +1 -0
- package/dist/types/{config → src/config}/config-resolver.d.ts +0 -0
- package/dist/types/src/config/config-resolver.d.ts.map +1 -0
- package/dist/types/{config → src/config}/config-source.d.ts +0 -0
- package/dist/types/src/config/config-source.d.ts.map +1 -0
- package/dist/types/{config → src/config}/index.d.ts +0 -0
- package/dist/types/src/config/index.d.ts.map +1 -0
- package/dist/types/{core → src/core}/common.d.ts +0 -0
- package/dist/types/src/core/common.d.ts.map +1 -0
- package/dist/types/{core → src/core}/config.d.ts +0 -0
- package/dist/types/src/core/config.d.ts.map +1 -0
- package/dist/types/{core → src/core}/constants.d.ts +0 -0
- package/dist/types/src/core/constants.d.ts.map +1 -0
- package/dist/types/{core → src/core}/errors.d.ts +0 -0
- package/dist/types/src/core/errors.d.ts.map +1 -0
- package/dist/types/{core → src/core}/hypernum.d.ts +0 -0
- package/dist/types/src/core/hypernum.d.ts.map +1 -0
- package/dist/types/{core → src/core}/index.d.ts +0 -0
- package/dist/types/src/core/index.d.ts.map +1 -0
- package/dist/types/{index.d.ts → src/index.d.ts} +1 -1
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/{operations → src/operations}/arithmetic.d.ts +0 -0
- package/dist/types/src/operations/arithmetic.d.ts.map +1 -0
- package/dist/types/{operations → src/operations}/bitwise.d.ts +0 -0
- package/dist/types/src/operations/bitwise.d.ts.map +1 -0
- package/dist/types/{operations → src/operations}/comparison.d.ts +0 -0
- package/dist/types/src/operations/comparison.d.ts.map +1 -0
- package/dist/types/{operations → src/operations}/conversion.d.ts +0 -0
- package/dist/types/src/operations/conversion.d.ts.map +1 -0
- package/dist/types/{operations → src/operations}/factorial.d.ts +0 -0
- package/dist/types/src/operations/factorial.d.ts.map +1 -0
- package/dist/types/{operations → src/operations}/index.d.ts +0 -0
- package/dist/types/src/operations/index.d.ts.map +1 -0
- package/dist/types/{operations → src/operations}/power.d.ts +0 -0
- package/dist/types/src/operations/power.d.ts.map +1 -0
- package/dist/types/{storage → src/storage}/Heap.d.ts +0 -0
- package/dist/types/src/storage/Heap.d.ts.map +1 -0
- package/dist/types/{storage → src/storage}/index.d.ts +0 -0
- package/dist/types/src/storage/index.d.ts.map +1 -0
- package/dist/types/{structures → src/structures}/ackermann.d.ts +0 -0
- package/dist/types/src/structures/ackermann.d.ts.map +1 -0
- package/dist/types/{structures → src/structures}/big-array.d.ts +0 -0
- package/dist/types/src/structures/big-array.d.ts.map +1 -0
- package/dist/types/{structures → src/structures}/index.d.ts +0 -0
- package/dist/types/src/structures/index.d.ts.map +1 -0
- package/dist/types/{structures → src/structures}/number-tree.d.ts +0 -0
- package/dist/types/src/structures/number-tree.d.ts.map +1 -0
- package/dist/types/{structures → src/structures}/power-tower.d.ts +0 -0
- package/dist/types/src/structures/power-tower.d.ts.map +1 -0
- package/dist/types/{utils → src/utils}/formatting.d.ts +0 -0
- package/dist/types/src/utils/formatting.d.ts.map +1 -0
- package/dist/types/{utils → src/utils}/index.d.ts +0 -0
- package/dist/types/src/utils/index.d.ts.map +1 -0
- package/dist/types/{utils → src/utils}/parser.d.ts +0 -0
- package/dist/types/src/utils/parser.d.ts.map +1 -0
- package/dist/types/{utils → src/utils}/precision.d.ts +0 -0
- package/dist/types/src/utils/precision.d.ts.map +1 -0
- package/dist/types/{utils → src/utils}/validation.d.ts +0 -0
- package/dist/types/src/utils/validation.d.ts.map +1 -0
- package/package.json +169 -164
- package/rollup.config.js +163 -161
- package/src/cli/hypernum.js +272 -0
- package/src/config/config-loader.ts +0 -0
- package/src/config/config-parser.ts +160 -160
- package/src/config/config-resolver.ts +0 -0
- package/src/config/config-source.ts +0 -0
- package/src/config/index.ts +0 -0
- package/src/core/common.ts +184 -184
- package/src/core/config.ts +392 -392
- package/src/core/constants.ts +101 -101
- package/src/core/errors.ts +202 -202
- package/src/core/hypernum.ts +240 -240
- package/src/core/index.ts +4 -4
- package/src/index.ts +179 -182
- package/src/operations/arithmetic.ts +332 -332
- package/src/operations/bitwise.ts +366 -366
- package/src/operations/comparison.ts +271 -271
- package/src/operations/conversion.ts +399 -399
- package/src/operations/factorial.ts +278 -278
- package/src/operations/index.ts +4 -4
- package/src/operations/power.ts +315 -315
- package/src/storage/Heap.ts +237 -237
- package/src/storage/index.ts +0 -0
- package/src/structures/ackermann.ts +232 -232
- package/src/structures/big-array.ts +305 -305
- package/src/structures/index.ts +3 -3
- package/src/structures/number-tree.ts +403 -403
- package/src/structures/power-tower.ts +277 -277
- package/src/types/common.d.ts +356 -356
- package/src/types/core.d.ts +160 -160
- package/src/types/index.d.ts +1 -1
- package/src/utils/formatting.ts +245 -245
- package/src/utils/index.ts +4 -4
- package/src/utils/parser.ts +244 -244
- package/src/utils/precision.ts +216 -216
- package/src/utils/validation.ts +182 -182
- package/tsconfig.json +83 -83
- package/dist/types/config/config-loader.d.ts.map +0 -1
- package/dist/types/config/config-parser.d.ts.map +0 -1
- package/dist/types/config/config-resolver.d.ts.map +0 -1
- package/dist/types/config/config-source.d.ts.map +0 -1
- package/dist/types/config/index.d.ts.map +0 -1
- package/dist/types/core/common.d.ts.map +0 -1
- package/dist/types/core/config.d.ts.map +0 -1
- package/dist/types/core/constants.d.ts.map +0 -1
- package/dist/types/core/errors.d.ts.map +0 -1
- package/dist/types/core/hypernum.d.ts.map +0 -1
- package/dist/types/core/index.d.ts.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/operations/arithmetic.d.ts.map +0 -1
- package/dist/types/operations/bitwise.d.ts.map +0 -1
- package/dist/types/operations/comparison.d.ts.map +0 -1
- package/dist/types/operations/conversion.d.ts.map +0 -1
- package/dist/types/operations/factorial.d.ts.map +0 -1
- package/dist/types/operations/index.d.ts.map +0 -1
- package/dist/types/operations/power.d.ts.map +0 -1
- package/dist/types/storage/Heap.d.ts.map +0 -1
- package/dist/types/storage/index.d.ts.map +0 -1
- package/dist/types/structures/ackermann.d.ts.map +0 -1
- package/dist/types/structures/big-array.d.ts.map +0 -1
- package/dist/types/structures/index.d.ts.map +0 -1
- package/dist/types/structures/number-tree.d.ts.map +0 -1
- package/dist/types/structures/power-tower.d.ts.map +0 -1
- package/dist/types/utils/formatting.d.ts.map +0 -1
- package/dist/types/utils/index.d.ts.map +0 -1
- package/dist/types/utils/parser.d.ts.map +0 -1
- package/dist/types/utils/precision.d.ts.map +0 -1
- package/dist/types/utils/validation.d.ts.map +0 -1
|
@@ -1,400 +1,400 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conversion operations module for Hypernum library
|
|
3
|
-
* Provides functions for converting numbers between different formats and bases
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
toBigInt,
|
|
8
|
-
ValidationError,
|
|
9
|
-
} from '../utils/validation';
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
RoundingMode,
|
|
13
|
-
|
|
14
|
-
} from '../utils/precision';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Options for conversion operations
|
|
18
|
-
*/
|
|
19
|
-
export interface ConversionOptions {
|
|
20
|
-
/** Precision for decimal operations */
|
|
21
|
-
precision?: number;
|
|
22
|
-
/** Rounding mode for decimal operations */
|
|
23
|
-
roundingMode?: RoundingMode;
|
|
24
|
-
/** Whether to use uppercase for hex/base-N output */
|
|
25
|
-
uppercase?: boolean;
|
|
26
|
-
/** Whether to add prefix for base-N output (0x, 0b, etc.) */
|
|
27
|
-
prefix?: boolean;
|
|
28
|
-
/** Minimum number of digits (pad with zeros) */
|
|
29
|
-
minDigits?: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const DEFAULT_OPTIONS: Required<ConversionOptions> = {
|
|
33
|
-
precision: 0,
|
|
34
|
-
roundingMode: RoundingMode.HALF_EVEN,
|
|
35
|
-
uppercase: false,
|
|
36
|
-
prefix: false,
|
|
37
|
-
minDigits: 1
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Converts number to binary string representation
|
|
42
|
-
*/
|
|
43
|
-
export function toBinary(
|
|
44
|
-
value: bigint | string | number,
|
|
45
|
-
options: ConversionOptions = {}
|
|
46
|
-
): string {
|
|
47
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
48
|
-
const bigValue = toBigInt(value);
|
|
49
|
-
|
|
50
|
-
let binary = bigValue.toString(2);
|
|
51
|
-
|
|
52
|
-
// Pad with zeros if needed
|
|
53
|
-
while (binary.length < opts.minDigits) {
|
|
54
|
-
binary = '0' + binary;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return opts.prefix ? '0b' + binary : binary;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Converts number to octal string representation
|
|
62
|
-
*/
|
|
63
|
-
export function toOctal(
|
|
64
|
-
value: bigint | string | number,
|
|
65
|
-
options: ConversionOptions = {}
|
|
66
|
-
): string {
|
|
67
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
68
|
-
const bigValue = toBigInt(value);
|
|
69
|
-
|
|
70
|
-
let octal = bigValue.toString(8);
|
|
71
|
-
|
|
72
|
-
while (octal.length < opts.minDigits) {
|
|
73
|
-
octal = '0' + octal;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return opts.prefix ? '0o' + octal : octal;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Converts number to hexadecimal string representation
|
|
81
|
-
*/
|
|
82
|
-
export function toHexadecimal(
|
|
83
|
-
value: bigint | string | number,
|
|
84
|
-
options: ConversionOptions = {}
|
|
85
|
-
): string {
|
|
86
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
87
|
-
const bigValue = toBigInt(value);
|
|
88
|
-
|
|
89
|
-
let hex = bigValue.toString(16);
|
|
90
|
-
|
|
91
|
-
if (opts.uppercase) {
|
|
92
|
-
hex = hex.toUpperCase();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
while (hex.length < opts.minDigits) {
|
|
96
|
-
hex = '0' + hex;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return opts.prefix ? '0x' + hex : hex;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Converts number to string in specified base
|
|
104
|
-
*/
|
|
105
|
-
export function toBase(
|
|
106
|
-
value: bigint | string | number,
|
|
107
|
-
base: number,
|
|
108
|
-
options: ConversionOptions = {}
|
|
109
|
-
): string {
|
|
110
|
-
if (base < 2 || base > 36) {
|
|
111
|
-
throw new ValidationError('Base must be between 2 and 36');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
115
|
-
const bigValue = toBigInt(value);
|
|
116
|
-
|
|
117
|
-
let result = bigValue.toString(base);
|
|
118
|
-
|
|
119
|
-
if (opts.uppercase) {
|
|
120
|
-
result = result.toUpperCase();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
while (result.length < opts.minDigits) {
|
|
124
|
-
result = '0' + result;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return result;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Converts string from specified base to bigint
|
|
132
|
-
*/
|
|
133
|
-
export function fromBase(
|
|
134
|
-
value: string,
|
|
135
|
-
base: number
|
|
136
|
-
): bigint {
|
|
137
|
-
if (base < 2 || base > 36) {
|
|
138
|
-
throw new ValidationError('Base must be between 2 and 36');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Remove base prefixes if present
|
|
142
|
-
const cleanValue = value.toLowerCase()
|
|
143
|
-
.replace(/^0x/, '') // hex
|
|
144
|
-
.replace(/^0b/, '') // binary
|
|
145
|
-
.replace(/^0o/, ''); // octal
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
return BigInt(`${base}n${cleanValue}`);
|
|
149
|
-
} catch (error) {
|
|
150
|
-
throw new ValidationError(`Invalid number format for base ${base}: ${value}`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Converts decimal string to fraction representation
|
|
156
|
-
*/
|
|
157
|
-
export function toFraction(
|
|
158
|
-
value: string,
|
|
159
|
-
): [bigint, bigint] {
|
|
160
|
-
|
|
161
|
-
// Split into integer and decimal parts
|
|
162
|
-
const [intPart, decPart = ''] = value.split('.');
|
|
163
|
-
|
|
164
|
-
if (!decPart) {
|
|
165
|
-
return [toBigInt(intPart), 1n];
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Convert decimal to fraction
|
|
169
|
-
const numerator = toBigInt(intPart + decPart);
|
|
170
|
-
const denominator = 10n ** BigInt(decPart.length);
|
|
171
|
-
|
|
172
|
-
// Simplify fraction
|
|
173
|
-
const gcd = calculateGCD(numerator, denominator);
|
|
174
|
-
|
|
175
|
-
return [numerator / gcd, denominator / gcd];
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Converts fraction to decimal string with specified precision
|
|
180
|
-
*/
|
|
181
|
-
export function fromFraction(
|
|
182
|
-
numerator: bigint | string | number,
|
|
183
|
-
denominator: bigint | string | number,
|
|
184
|
-
options: ConversionOptions = {}
|
|
185
|
-
): string {
|
|
186
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
187
|
-
const bigNumerator = toBigInt(numerator);
|
|
188
|
-
const bigDenominator = toBigInt(denominator);
|
|
189
|
-
|
|
190
|
-
if (bigDenominator === 0n) {
|
|
191
|
-
throw new ValidationError('Denominator cannot be zero');
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const quotient = bigNumerator / bigDenominator;
|
|
195
|
-
const remainder = bigNumerator % bigDenominator;
|
|
196
|
-
|
|
197
|
-
if (remainder === 0n || opts.precision === 0) {
|
|
198
|
-
return quotient.toString();
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Calculate decimal part
|
|
202
|
-
const scaleFactor = 10n ** BigInt(opts.precision);
|
|
203
|
-
const scaledRemainder = (remainder * scaleFactor) / bigDenominator;
|
|
204
|
-
|
|
205
|
-
return `${quotient}.${scaledRemainder.toString().padStart(opts.precision, '0')}`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Converts scientific notation to decimal string
|
|
210
|
-
*/
|
|
211
|
-
export function fromScientific(
|
|
212
|
-
value: string,
|
|
213
|
-
): string {
|
|
214
|
-
|
|
215
|
-
// Parse scientific notation format
|
|
216
|
-
const match = value.match(/^(-?\d+\.?\d*)[eE]([+-]?\d+)$/);
|
|
217
|
-
if (!match) {
|
|
218
|
-
throw new ValidationError('Invalid scientific notation format');
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const [, significand, exponent] = match;
|
|
222
|
-
const exp = parseInt(exponent || '0', 10);
|
|
223
|
-
|
|
224
|
-
// Convert to regular decimal
|
|
225
|
-
if (exp >= 0) {
|
|
226
|
-
if (significand === undefined) {
|
|
227
|
-
throw new ValidationError('Invalid scientific notation format');
|
|
228
|
-
}
|
|
229
|
-
return (BigInt(significand.replace('.', '')) * (10n ** BigInt(exp))).toString();
|
|
230
|
-
} else {
|
|
231
|
-
const absExp = Math.abs(exp);
|
|
232
|
-
if (significand === undefined) {
|
|
233
|
-
throw new ValidationError('Invalid scientific notation format');
|
|
234
|
-
}
|
|
235
|
-
const scaledValue = BigInt(significand.replace('.', ''));
|
|
236
|
-
return (scaledValue / (10n ** BigInt(absExp))).toString();
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Converts decimal to scientific notation
|
|
242
|
-
*/
|
|
243
|
-
export function toScientific(
|
|
244
|
-
value: bigint | string | number,
|
|
245
|
-
options: ConversionOptions = {}
|
|
246
|
-
): string {
|
|
247
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
248
|
-
const bigValue = toBigInt(value);
|
|
249
|
-
|
|
250
|
-
if (bigValue === 0n) {
|
|
251
|
-
return '0e0';
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const str = bigValue.toString();
|
|
255
|
-
const firstDigit = str[0] === '-' ? str[1] : str[0];
|
|
256
|
-
const exponent = str.length - (str[0] === '-' ? 2 : 1);
|
|
257
|
-
|
|
258
|
-
let result = firstDigit;
|
|
259
|
-
if (str.length > 1) {
|
|
260
|
-
const restDigits = str.slice(str[0] === '-' ? 2 : 1);
|
|
261
|
-
if (opts.precision > 0) {
|
|
262
|
-
result += '.' + restDigits.slice(0, opts.precision);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (str[0] === '-') {
|
|
267
|
-
result = '-' + result;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return `${result}e${exponent}`;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Calculates Greatest Common Divisor (helper function)
|
|
275
|
-
*/
|
|
276
|
-
function calculateGCD(a: bigint, b: bigint): bigint {
|
|
277
|
-
a = a < 0n ? -a : a;
|
|
278
|
-
b = b < 0n ? -b : b;
|
|
279
|
-
|
|
280
|
-
while (b !== 0n) {
|
|
281
|
-
const temp = b;
|
|
282
|
-
b = a % b;
|
|
283
|
-
a = temp;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return a;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Converts Roman numeral to number
|
|
292
|
-
*/
|
|
293
|
-
export function fromRoman(value: string): bigint {
|
|
294
|
-
const romanValues = new Map<string, number>([
|
|
295
|
-
['I', 1],
|
|
296
|
-
['V', 5],
|
|
297
|
-
['X', 10],
|
|
298
|
-
['L', 50],
|
|
299
|
-
['C', 100],
|
|
300
|
-
['D', 500],
|
|
301
|
-
['M', 1000]
|
|
302
|
-
]);
|
|
303
|
-
|
|
304
|
-
let result = 0;
|
|
305
|
-
let prevValue = 0;
|
|
306
|
-
|
|
307
|
-
// Process from right to left
|
|
308
|
-
for (let i = value.length - 1; i >= 0; i--) {
|
|
309
|
-
const char = value[i]?.toUpperCase() ?? '';
|
|
310
|
-
const current = romanValues.get(char);
|
|
311
|
-
|
|
312
|
-
if (current === undefined) {
|
|
313
|
-
throw new ValidationError(`Invalid Roman numeral character: ${char}`);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (current >= prevValue) {
|
|
317
|
-
result += current;
|
|
318
|
-
} else {
|
|
319
|
-
result -= current;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
prevValue = current;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
return BigInt(result);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Converts number to Roman numeral
|
|
330
|
-
*/
|
|
331
|
-
export function toRoman(
|
|
332
|
-
value: bigint | string | number,
|
|
333
|
-
options: ConversionOptions = {}
|
|
334
|
-
): string {
|
|
335
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
336
|
-
const num = Number(toBigInt(value));
|
|
337
|
-
|
|
338
|
-
if (num <= 0 || num > 3999) {
|
|
339
|
-
throw new ValidationError('Number must be between 1 and 3999 for Roman numerals');
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Define symbol pairs with proper typing
|
|
343
|
-
type RomanPair = [string, string];
|
|
344
|
-
type RomanSingle = [string];
|
|
345
|
-
type RomanSymbol = RomanPair | RomanSingle;
|
|
346
|
-
|
|
347
|
-
const romanSymbols: RomanSymbol[] = [
|
|
348
|
-
['I', 'V'], // ones
|
|
349
|
-
['X', 'L'], // tens
|
|
350
|
-
['C', 'D'], // hundreds
|
|
351
|
-
['M'] // thousands
|
|
352
|
-
];
|
|
353
|
-
|
|
354
|
-
let result = '';
|
|
355
|
-
let position = 0;
|
|
356
|
-
let remaining = num;
|
|
357
|
-
|
|
358
|
-
while (remaining > 0) {
|
|
359
|
-
const digit = remaining % 10;
|
|
360
|
-
const symbols = romanSymbols[position];
|
|
361
|
-
|
|
362
|
-
if (!symbols) {
|
|
363
|
-
break; // Safety check for position overflow
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const unit = symbols[0];
|
|
367
|
-
const five = symbols[1] ?? '';
|
|
368
|
-
const next = position < 3 ? romanSymbols[position + 1]?.[0] ?? '' : '';
|
|
369
|
-
|
|
370
|
-
let digitStr = '';
|
|
371
|
-
if (digit === 9 && next) {
|
|
372
|
-
digitStr = unit + next;
|
|
373
|
-
} else if (digit >= 5 && five) {
|
|
374
|
-
digitStr = five + unit.repeat(digit - 5);
|
|
375
|
-
} else if (digit === 4 && five) {
|
|
376
|
-
digitStr = unit + five;
|
|
377
|
-
} else {
|
|
378
|
-
digitStr = unit.repeat(digit);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
result = digitStr + result;
|
|
382
|
-
remaining = Math.floor(remaining / 10);
|
|
383
|
-
position++;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return opts.uppercase ? result : result.toLowerCase();
|
|
387
|
-
}
|
|
388
|
-
export default {
|
|
389
|
-
toBinary,
|
|
390
|
-
toOctal,
|
|
391
|
-
toHexadecimal,
|
|
392
|
-
toBase,
|
|
393
|
-
fromBase,
|
|
394
|
-
toFraction,
|
|
395
|
-
fromFraction,
|
|
396
|
-
fromScientific,
|
|
397
|
-
toScientific,
|
|
398
|
-
fromRoman,
|
|
399
|
-
toRoman
|
|
1
|
+
/**
|
|
2
|
+
* Conversion operations module for Hypernum library
|
|
3
|
+
* Provides functions for converting numbers between different formats and bases
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
toBigInt,
|
|
8
|
+
ValidationError,
|
|
9
|
+
} from '../utils/validation';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
RoundingMode,
|
|
13
|
+
|
|
14
|
+
} from '../utils/precision';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Options for conversion operations
|
|
18
|
+
*/
|
|
19
|
+
export interface ConversionOptions {
|
|
20
|
+
/** Precision for decimal operations */
|
|
21
|
+
precision?: number;
|
|
22
|
+
/** Rounding mode for decimal operations */
|
|
23
|
+
roundingMode?: RoundingMode;
|
|
24
|
+
/** Whether to use uppercase for hex/base-N output */
|
|
25
|
+
uppercase?: boolean;
|
|
26
|
+
/** Whether to add prefix for base-N output (0x, 0b, etc.) */
|
|
27
|
+
prefix?: boolean;
|
|
28
|
+
/** Minimum number of digits (pad with zeros) */
|
|
29
|
+
minDigits?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const DEFAULT_OPTIONS: Required<ConversionOptions> = {
|
|
33
|
+
precision: 0,
|
|
34
|
+
roundingMode: RoundingMode.HALF_EVEN,
|
|
35
|
+
uppercase: false,
|
|
36
|
+
prefix: false,
|
|
37
|
+
minDigits: 1
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Converts number to binary string representation
|
|
42
|
+
*/
|
|
43
|
+
export function toBinary(
|
|
44
|
+
value: bigint | string | number,
|
|
45
|
+
options: ConversionOptions = {}
|
|
46
|
+
): string {
|
|
47
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
48
|
+
const bigValue = toBigInt(value);
|
|
49
|
+
|
|
50
|
+
let binary = bigValue.toString(2);
|
|
51
|
+
|
|
52
|
+
// Pad with zeros if needed
|
|
53
|
+
while (binary.length < opts.minDigits) {
|
|
54
|
+
binary = '0' + binary;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return opts.prefix ? '0b' + binary : binary;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Converts number to octal string representation
|
|
62
|
+
*/
|
|
63
|
+
export function toOctal(
|
|
64
|
+
value: bigint | string | number,
|
|
65
|
+
options: ConversionOptions = {}
|
|
66
|
+
): string {
|
|
67
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
68
|
+
const bigValue = toBigInt(value);
|
|
69
|
+
|
|
70
|
+
let octal = bigValue.toString(8);
|
|
71
|
+
|
|
72
|
+
while (octal.length < opts.minDigits) {
|
|
73
|
+
octal = '0' + octal;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return opts.prefix ? '0o' + octal : octal;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Converts number to hexadecimal string representation
|
|
81
|
+
*/
|
|
82
|
+
export function toHexadecimal(
|
|
83
|
+
value: bigint | string | number,
|
|
84
|
+
options: ConversionOptions = {}
|
|
85
|
+
): string {
|
|
86
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
87
|
+
const bigValue = toBigInt(value);
|
|
88
|
+
|
|
89
|
+
let hex = bigValue.toString(16);
|
|
90
|
+
|
|
91
|
+
if (opts.uppercase) {
|
|
92
|
+
hex = hex.toUpperCase();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
while (hex.length < opts.minDigits) {
|
|
96
|
+
hex = '0' + hex;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return opts.prefix ? '0x' + hex : hex;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Converts number to string in specified base
|
|
104
|
+
*/
|
|
105
|
+
export function toBase(
|
|
106
|
+
value: bigint | string | number,
|
|
107
|
+
base: number,
|
|
108
|
+
options: ConversionOptions = {}
|
|
109
|
+
): string {
|
|
110
|
+
if (base < 2 || base > 36) {
|
|
111
|
+
throw new ValidationError('Base must be between 2 and 36');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
115
|
+
const bigValue = toBigInt(value);
|
|
116
|
+
|
|
117
|
+
let result = bigValue.toString(base);
|
|
118
|
+
|
|
119
|
+
if (opts.uppercase) {
|
|
120
|
+
result = result.toUpperCase();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
while (result.length < opts.minDigits) {
|
|
124
|
+
result = '0' + result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Converts string from specified base to bigint
|
|
132
|
+
*/
|
|
133
|
+
export function fromBase(
|
|
134
|
+
value: string,
|
|
135
|
+
base: number
|
|
136
|
+
): bigint {
|
|
137
|
+
if (base < 2 || base > 36) {
|
|
138
|
+
throw new ValidationError('Base must be between 2 and 36');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Remove base prefixes if present
|
|
142
|
+
const cleanValue = value.toLowerCase()
|
|
143
|
+
.replace(/^0x/, '') // hex
|
|
144
|
+
.replace(/^0b/, '') // binary
|
|
145
|
+
.replace(/^0o/, ''); // octal
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
return BigInt(`${base}n${cleanValue}`);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
throw new ValidationError(`Invalid number format for base ${base}: ${value}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Converts decimal string to fraction representation
|
|
156
|
+
*/
|
|
157
|
+
export function toFraction(
|
|
158
|
+
value: string,
|
|
159
|
+
): [bigint, bigint] {
|
|
160
|
+
|
|
161
|
+
// Split into integer and decimal parts
|
|
162
|
+
const [intPart, decPart = ''] = value.split('.');
|
|
163
|
+
|
|
164
|
+
if (!decPart) {
|
|
165
|
+
return [toBigInt(intPart), 1n];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Convert decimal to fraction
|
|
169
|
+
const numerator = toBigInt(intPart + decPart);
|
|
170
|
+
const denominator = 10n ** BigInt(decPart.length);
|
|
171
|
+
|
|
172
|
+
// Simplify fraction
|
|
173
|
+
const gcd = calculateGCD(numerator, denominator);
|
|
174
|
+
|
|
175
|
+
return [numerator / gcd, denominator / gcd];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Converts fraction to decimal string with specified precision
|
|
180
|
+
*/
|
|
181
|
+
export function fromFraction(
|
|
182
|
+
numerator: bigint | string | number,
|
|
183
|
+
denominator: bigint | string | number,
|
|
184
|
+
options: ConversionOptions = {}
|
|
185
|
+
): string {
|
|
186
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
187
|
+
const bigNumerator = toBigInt(numerator);
|
|
188
|
+
const bigDenominator = toBigInt(denominator);
|
|
189
|
+
|
|
190
|
+
if (bigDenominator === 0n) {
|
|
191
|
+
throw new ValidationError('Denominator cannot be zero');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const quotient = bigNumerator / bigDenominator;
|
|
195
|
+
const remainder = bigNumerator % bigDenominator;
|
|
196
|
+
|
|
197
|
+
if (remainder === 0n || opts.precision === 0) {
|
|
198
|
+
return quotient.toString();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Calculate decimal part
|
|
202
|
+
const scaleFactor = 10n ** BigInt(opts.precision);
|
|
203
|
+
const scaledRemainder = (remainder * scaleFactor) / bigDenominator;
|
|
204
|
+
|
|
205
|
+
return `${quotient}.${scaledRemainder.toString().padStart(opts.precision, '0')}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Converts scientific notation to decimal string
|
|
210
|
+
*/
|
|
211
|
+
export function fromScientific(
|
|
212
|
+
value: string,
|
|
213
|
+
): string {
|
|
214
|
+
|
|
215
|
+
// Parse scientific notation format
|
|
216
|
+
const match = value.match(/^(-?\d+\.?\d*)[eE]([+-]?\d+)$/);
|
|
217
|
+
if (!match) {
|
|
218
|
+
throw new ValidationError('Invalid scientific notation format');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const [, significand, exponent] = match;
|
|
222
|
+
const exp = parseInt(exponent || '0', 10);
|
|
223
|
+
|
|
224
|
+
// Convert to regular decimal
|
|
225
|
+
if (exp >= 0) {
|
|
226
|
+
if (significand === undefined) {
|
|
227
|
+
throw new ValidationError('Invalid scientific notation format');
|
|
228
|
+
}
|
|
229
|
+
return (BigInt(significand.replace('.', '')) * (10n ** BigInt(exp))).toString();
|
|
230
|
+
} else {
|
|
231
|
+
const absExp = Math.abs(exp);
|
|
232
|
+
if (significand === undefined) {
|
|
233
|
+
throw new ValidationError('Invalid scientific notation format');
|
|
234
|
+
}
|
|
235
|
+
const scaledValue = BigInt(significand.replace('.', ''));
|
|
236
|
+
return (scaledValue / (10n ** BigInt(absExp))).toString();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Converts decimal to scientific notation
|
|
242
|
+
*/
|
|
243
|
+
export function toScientific(
|
|
244
|
+
value: bigint | string | number,
|
|
245
|
+
options: ConversionOptions = {}
|
|
246
|
+
): string {
|
|
247
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
248
|
+
const bigValue = toBigInt(value);
|
|
249
|
+
|
|
250
|
+
if (bigValue === 0n) {
|
|
251
|
+
return '0e0';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const str = bigValue.toString();
|
|
255
|
+
const firstDigit = str[0] === '-' ? str[1] : str[0];
|
|
256
|
+
const exponent = str.length - (str[0] === '-' ? 2 : 1);
|
|
257
|
+
|
|
258
|
+
let result = firstDigit;
|
|
259
|
+
if (str.length > 1) {
|
|
260
|
+
const restDigits = str.slice(str[0] === '-' ? 2 : 1);
|
|
261
|
+
if (opts.precision > 0) {
|
|
262
|
+
result += '.' + restDigits.slice(0, opts.precision);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (str[0] === '-') {
|
|
267
|
+
result = '-' + result;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return `${result}e${exponent}`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Calculates Greatest Common Divisor (helper function)
|
|
275
|
+
*/
|
|
276
|
+
function calculateGCD(a: bigint, b: bigint): bigint {
|
|
277
|
+
a = a < 0n ? -a : a;
|
|
278
|
+
b = b < 0n ? -b : b;
|
|
279
|
+
|
|
280
|
+
while (b !== 0n) {
|
|
281
|
+
const temp = b;
|
|
282
|
+
b = a % b;
|
|
283
|
+
a = temp;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return a;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Converts Roman numeral to number
|
|
292
|
+
*/
|
|
293
|
+
export function fromRoman(value: string): bigint {
|
|
294
|
+
const romanValues = new Map<string, number>([
|
|
295
|
+
['I', 1],
|
|
296
|
+
['V', 5],
|
|
297
|
+
['X', 10],
|
|
298
|
+
['L', 50],
|
|
299
|
+
['C', 100],
|
|
300
|
+
['D', 500],
|
|
301
|
+
['M', 1000]
|
|
302
|
+
]);
|
|
303
|
+
|
|
304
|
+
let result = 0;
|
|
305
|
+
let prevValue = 0;
|
|
306
|
+
|
|
307
|
+
// Process from right to left
|
|
308
|
+
for (let i = value.length - 1; i >= 0; i--) {
|
|
309
|
+
const char = value[i]?.toUpperCase() ?? '';
|
|
310
|
+
const current = romanValues.get(char);
|
|
311
|
+
|
|
312
|
+
if (current === undefined) {
|
|
313
|
+
throw new ValidationError(`Invalid Roman numeral character: ${char}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (current >= prevValue) {
|
|
317
|
+
result += current;
|
|
318
|
+
} else {
|
|
319
|
+
result -= current;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
prevValue = current;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return BigInt(result);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Converts number to Roman numeral
|
|
330
|
+
*/
|
|
331
|
+
export function toRoman(
|
|
332
|
+
value: bigint | string | number,
|
|
333
|
+
options: ConversionOptions = {}
|
|
334
|
+
): string {
|
|
335
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
336
|
+
const num = Number(toBigInt(value));
|
|
337
|
+
|
|
338
|
+
if (num <= 0 || num > 3999) {
|
|
339
|
+
throw new ValidationError('Number must be between 1 and 3999 for Roman numerals');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Define symbol pairs with proper typing
|
|
343
|
+
type RomanPair = [string, string];
|
|
344
|
+
type RomanSingle = [string];
|
|
345
|
+
type RomanSymbol = RomanPair | RomanSingle;
|
|
346
|
+
|
|
347
|
+
const romanSymbols: RomanSymbol[] = [
|
|
348
|
+
['I', 'V'], // ones
|
|
349
|
+
['X', 'L'], // tens
|
|
350
|
+
['C', 'D'], // hundreds
|
|
351
|
+
['M'] // thousands
|
|
352
|
+
];
|
|
353
|
+
|
|
354
|
+
let result = '';
|
|
355
|
+
let position = 0;
|
|
356
|
+
let remaining = num;
|
|
357
|
+
|
|
358
|
+
while (remaining > 0) {
|
|
359
|
+
const digit = remaining % 10;
|
|
360
|
+
const symbols = romanSymbols[position];
|
|
361
|
+
|
|
362
|
+
if (!symbols) {
|
|
363
|
+
break; // Safety check for position overflow
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const unit = symbols[0];
|
|
367
|
+
const five = symbols[1] ?? '';
|
|
368
|
+
const next = position < 3 ? romanSymbols[position + 1]?.[0] ?? '' : '';
|
|
369
|
+
|
|
370
|
+
let digitStr = '';
|
|
371
|
+
if (digit === 9 && next) {
|
|
372
|
+
digitStr = unit + next;
|
|
373
|
+
} else if (digit >= 5 && five) {
|
|
374
|
+
digitStr = five + unit.repeat(digit - 5);
|
|
375
|
+
} else if (digit === 4 && five) {
|
|
376
|
+
digitStr = unit + five;
|
|
377
|
+
} else {
|
|
378
|
+
digitStr = unit.repeat(digit);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
result = digitStr + result;
|
|
382
|
+
remaining = Math.floor(remaining / 10);
|
|
383
|
+
position++;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return opts.uppercase ? result : result.toLowerCase();
|
|
387
|
+
}
|
|
388
|
+
export default {
|
|
389
|
+
toBinary,
|
|
390
|
+
toOctal,
|
|
391
|
+
toHexadecimal,
|
|
392
|
+
toBase,
|
|
393
|
+
fromBase,
|
|
394
|
+
toFraction,
|
|
395
|
+
fromFraction,
|
|
396
|
+
fromScientific,
|
|
397
|
+
toScientific,
|
|
398
|
+
fromRoman,
|
|
399
|
+
toRoman
|
|
400
400
|
};
|