Skip to content

Commit b1bf660

Browse files
committed
feat: add more methods to PrecisionNumber
1 parent bf35ca7 commit b1bf660

2 files changed

Lines changed: 288 additions & 16 deletions

File tree

src/classes/precision-number.ts

Lines changed: 169 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ export type PrecisionNumberValue = Decimal.Value | PrecisionNumber | { toString:
1010
*
1111
* `PrecisionNumber` stores values as `Decimal`, rounds every mutating operation to
1212
* the configured decimal places, and exposes both mutable and immutable arithmetic
13-
* methods. Mutable methods (`plus`, `minus`, `times`, `dividedBy`, `negate`,
14-
* `absoluteValue`) update the current instance and return `this`; immutable methods
15-
* prefixed with `to` return a new `PrecisionNumber` with the same precision settings.
13+
* methods. Mutable methods (`plus`, `minus`, `times`, `dividedBy`, `modulo`,
14+
* `pow`, `clamp`, `ceil`, `floor`, `negate`, `absoluteValue`) update the current
15+
* instance and return `this`; immutable methods prefixed with `to` return a new
16+
* `PrecisionNumber` with the same precision settings.
1617
*
1718
* @example
1819
* ```typescript
@@ -70,8 +71,26 @@ export class PrecisionNumber {
7071
return decimal.toDecimalPlaces(this.#decimalPlaces, this.#rounding);
7172
}
7273

74+
#valueToString(value: PrecisionNumberValue) {
75+
return value.toString().trim();
76+
}
77+
7378
// Public getters
7479

80+
/**
81+
* Decimal places retained by mutating operations and default formatting.
82+
*/
83+
get decimalPlaces() {
84+
return this.#decimalPlaces;
85+
}
86+
87+
/**
88+
* Decimal.js rounding mode used by mutating operations and default formatting.
89+
*/
90+
get rounding() {
91+
return this.#rounding;
92+
}
93+
7594
/**
7695
* Fixed-decimal string using the instance precision and rounding mode.
7796
*/
@@ -108,6 +127,43 @@ export class PrecisionNumber {
108127
return this;
109128
}
110129

130+
/**
131+
* Rounds the current value up to the nearest integer in place.
132+
*
133+
* The stored value still uses this instance's configured decimal places for output.
134+
*
135+
* @returns {this} The current instance for chaining
136+
*/
137+
ceil() {
138+
this.#decimal = this.#decimalToFixedDecimal(this.#decimal.ceil());
139+
return this;
140+
}
141+
142+
/**
143+
* Restricts the current value to the inclusive range `[min, max]` in place.
144+
*
145+
* @param {PrecisionNumberValue} min - Lower bound
146+
* @param {PrecisionNumberValue} max - Upper bound
147+
*
148+
* @returns {this} The current instance for chaining
149+
*
150+
* @throws {Error} If `min` is greater than `max`
151+
*/
152+
clamp(min: PrecisionNumberValue, max: PrecisionNumberValue) {
153+
const minDecimal = new Decimal(this.#valueToString(min));
154+
const maxDecimal = new Decimal(this.#valueToString(max));
155+
if (minDecimal.gt(maxDecimal)) throw new Error('Invalid clamp range: min cannot be greater than max');
156+
this.#decimal = this.#decimalToFixedDecimal(Decimal.min(Decimal.max(this.#decimal, minDecimal), maxDecimal));
157+
return this;
158+
}
159+
160+
/**
161+
* Returns a new instance with the same value, decimal places, and rounding mode.
162+
*/
163+
clone() {
164+
return new PrecisionNumber(this.#decimal, this.#decimalPlaces, this.#rounding);
165+
}
166+
111167
/**
112168
* Divides the current value by another value in place.
113169
*
@@ -116,7 +172,7 @@ export class PrecisionNumber {
116172
* @returns {this} The current instance for chaining
117173
*/
118174
dividedBy(value: PrecisionNumberValue) {
119-
this.#decimal = this.#decimalToFixedDecimal(this.#decimal.dividedBy(value.toString().trim()));
175+
this.#decimal = this.#decimalToFixedDecimal(this.#decimal.dividedBy(this.#valueToString(value)));
120176
return this;
121177
}
122178

@@ -128,21 +184,33 @@ export class PrecisionNumber {
128184
* @returns {boolean} `true` when both numeric values are equal
129185
*/
130186
equals(value: PrecisionNumberValue) {
131-
return this.#decimal.equals(value.toString().trim());
187+
return this.#decimal.equals(this.#valueToString(value));
188+
}
189+
190+
/**
191+
* Rounds the current value down to the nearest integer in place.
192+
*
193+
* The stored value still uses this instance's configured decimal places for output.
194+
*
195+
* @returns {this} The current instance for chaining
196+
*/
197+
floor() {
198+
this.#decimal = this.#decimalToFixedDecimal(this.#decimal.floor());
199+
return this;
132200
}
133201

134202
/**
135203
* Checks whether the current value is greater than another value.
136204
*/
137205
gt(value: PrecisionNumberValue) {
138-
return this.#decimal.gt(value.toString().trim());
206+
return this.#decimal.gt(this.#valueToString(value));
139207
}
140208

141209
/**
142210
* Checks whether the current value is greater than or equal to another value.
143211
*/
144212
gte(value: PrecisionNumberValue) {
145-
return this.#decimal.gte(value.toString().trim());
213+
return this.#decimal.gte(this.#valueToString(value));
146214
}
147215

148216
/**
@@ -191,21 +259,33 @@ export class PrecisionNumber {
191259
* Checks whether the current value is less than another value.
192260
*/
193261
lt(value: PrecisionNumberValue) {
194-
return this.#decimal.lt(value.toString().trim());
262+
return this.#decimal.lt(this.#valueToString(value));
195263
}
196264

197265
/**
198266
* Checks whether the current value is less than or equal to another value.
199267
*/
200268
lte(value: PrecisionNumberValue) {
201-
return this.#decimal.lte(value.toString().trim());
269+
return this.#decimal.lte(this.#valueToString(value));
202270
}
203271

204272
/**
205273
* Subtracts another value from the current value in place.
206274
*/
207275
minus(value: PrecisionNumberValue) {
208-
this.#decimal = this.#decimalToFixedDecimal(this.#decimal.minus(value.toString().trim()));
276+
this.#decimal = this.#decimalToFixedDecimal(this.#decimal.minus(this.#valueToString(value)));
277+
return this;
278+
}
279+
280+
/**
281+
* Replaces the current value with the remainder after division by another value.
282+
*
283+
* @param {PrecisionNumberValue} value - Divisor used to compute the remainder
284+
*
285+
* @returns {this} The current instance for chaining
286+
*/
287+
modulo(value: PrecisionNumberValue) {
288+
this.#decimal = this.#decimalToFixedDecimal(this.#decimal.modulo(this.#valueToString(value)));
209289
return this;
210290
}
211291

@@ -221,15 +301,27 @@ export class PrecisionNumber {
221301
* Adds another value to the current value in place.
222302
*/
223303
plus(value: PrecisionNumberValue) {
224-
this.#decimal = this.#decimalToFixedDecimal(this.#decimal.plus(value.toString().trim()));
304+
this.#decimal = this.#decimalToFixedDecimal(this.#decimal.plus(this.#valueToString(value)));
305+
return this;
306+
}
307+
308+
/**
309+
* Raises the current value to an exponent in place.
310+
*
311+
* @param {PrecisionNumberValue} value - Exponent
312+
*
313+
* @returns {this} The current instance for chaining
314+
*/
315+
pow(value: PrecisionNumberValue) {
316+
this.#decimal = this.#decimalToFixedDecimal(this.#decimal.pow(this.#valueToString(value)));
225317
return this;
226318
}
227319

228320
/**
229321
* Multiplies the current value by another value in place.
230322
*/
231323
times(value: PrecisionNumberValue) {
232-
this.#decimal = this.#decimalToFixedDecimal(this.#decimal.times(value.toString().trim()));
324+
this.#decimal = this.#decimalToFixedDecimal(this.#decimal.times(this.#valueToString(value)));
233325
return this;
234326
}
235327

@@ -240,17 +332,43 @@ export class PrecisionNumber {
240332
return new PrecisionNumber(this.#decimal.absoluteValue(), this.#decimalPlaces, this.#rounding);
241333
}
242334

335+
/**
336+
* Returns a new instance rounded up to the nearest integer.
337+
*/
338+
toCeil() {
339+
return new PrecisionNumber(this.#decimal.ceil(), this.#decimalPlaces, this.#rounding);
340+
}
341+
342+
/**
343+
* Returns a new instance restricted to the inclusive range `[min, max]`.
344+
*
345+
* @param {PrecisionNumberValue} min - Lower bound
346+
* @param {PrecisionNumberValue} max - Upper bound
347+
*
348+
* @throws {Error} If `min` is greater than `max`
349+
*/
350+
toClamped(min: PrecisionNumberValue, max: PrecisionNumberValue) {
351+
return this.clone().clamp(min, max);
352+
}
353+
243354
/**
244355
* Returns a new instance divided by another value.
245356
*/
246357
toDividedBy(value: PrecisionNumberValue) {
247358
return new PrecisionNumber(
248-
this.#decimal.dividedBy(value.toString().trim()),
359+
this.#decimal.dividedBy(this.#valueToString(value)),
249360
this.#decimalPlaces,
250361
this.#rounding,
251362
);
252363
}
253364

365+
/**
366+
* Returns a new instance rounded down to the nearest integer.
367+
*/
368+
toFloor() {
369+
return new PrecisionNumber(this.#decimal.floor(), this.#decimalPlaces, this.#rounding);
370+
}
371+
254372
/**
255373
* Serializes to the fixed-decimal string.
256374
*/
@@ -262,7 +380,22 @@ export class PrecisionNumber {
262380
* Returns a new instance with another value subtracted.
263381
*/
264382
toMinus(value: PrecisionNumberValue) {
265-
return new PrecisionNumber(this.#decimal.minus(value.toString().trim()), this.#decimalPlaces, this.#rounding);
383+
return new PrecisionNumber(
384+
this.#decimal.minus(this.#valueToString(value)),
385+
this.#decimalPlaces,
386+
this.#rounding,
387+
);
388+
}
389+
390+
/**
391+
* Returns a new instance with the remainder after division by another value.
392+
*/
393+
toModulo(value: PrecisionNumberValue) {
394+
return new PrecisionNumber(
395+
this.#decimal.modulo(this.#valueToString(value)),
396+
this.#decimalPlaces,
397+
this.#rounding,
398+
);
266399
}
267400

268401
/**
@@ -276,7 +409,23 @@ export class PrecisionNumber {
276409
* Returns a new instance with another value added.
277410
*/
278411
toPlus(value: PrecisionNumberValue) {
279-
return new PrecisionNumber(this.#decimal.plus(value.toString().trim()), this.#decimalPlaces, this.#rounding);
412+
return new PrecisionNumber(this.#decimal.plus(this.#valueToString(value)), this.#decimalPlaces, this.#rounding);
413+
}
414+
415+
/**
416+
* Returns a new instance raised to an exponent.
417+
*/
418+
toPow(value: PrecisionNumberValue) {
419+
return new PrecisionNumber(this.#decimal.pow(this.#valueToString(value)), this.#decimalPlaces, this.#rounding);
420+
}
421+
422+
/**
423+
* Converts the current value to a JavaScript number.
424+
*
425+
* Use `toString`/`value` when preserving decimal precision is more important than native-number ergonomics.
426+
*/
427+
toNumber() {
428+
return this.#decimal.toNumber();
280429
}
281430

282431
/**
@@ -297,6 +446,10 @@ export class PrecisionNumber {
297446
* Returns a new instance multiplied by another value.
298447
*/
299448
toTimes(value: PrecisionNumberValue) {
300-
return new PrecisionNumber(this.#decimal.times(value.toString().trim()), this.#decimalPlaces, this.#rounding);
449+
return new PrecisionNumber(
450+
this.#decimal.times(this.#valueToString(value)),
451+
this.#decimalPlaces,
452+
this.#rounding,
453+
);
301454
}
302455
}

0 commit comments

Comments
 (0)