@rinxun/numeric-calculator 1.0.7-alpha.1 → 1.0.8

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/README.md CHANGED
@@ -64,6 +64,7 @@ The instance supports chain operations.
64
64
  - `minus(operands: IOperand[]) => instance`
65
65
  - `times(operands: IOperand[]) => instance`
66
66
  - `divide(operands: IOperand[]) => instance`
67
+ - `round(fractionDigits?: number) => number`
67
68
  - `toPrecision(precision?: number) => number`
68
69
  - `toFixed(fractionDigits?: number) => string`
69
70
 
@@ -141,6 +142,26 @@ const result = new NC(-1000).divide(100, '20', 2).toPrecision(); // -1000/100/20
141
142
 
142
143
 
143
144
 
145
+ <h4>inst.round(fractionDigits?: number, options?: { snap?: boolean })</h4>
146
+
147
+ ``` typescript
148
+ const result = new NC().round(3); // 0
149
+ const result = new NC(5.255).round(2); // 5.26
150
+ const result = new NC(7.65).times(16.5).round(2); // => 7.65 * 16.5 = 126.225 ≈ 126.23
151
+ const result = new NC(26.05).times(16.5).round(2); // => 26.05 * 16.5 = 429.825 ≈ 429.83
152
+ ```
153
+
154
+ `options.snap` (default `false`) makes rounding tolerant to binary floating-point representation dust. After plain float arithmetic upstream, a true `95.015` can arrive as `95.01499999999942`, and exact-decimal rounding of the *represented* value then rounds down, disagreeing with the true value. With `snap`, the amplified value is nudged by a few ULPs relative to its own magnitude before `Math.round`, so only values sitting exactly on a rounding boundary are affected — everything beyond a few ULPs of the boundary keeps its exact-decimal result, and there is no magic number to tune:
155
+
156
+ ``` typescript
157
+ const dust = 2959.9649999999997; // true value 2959.965, distorted by float subtraction upstream
158
+ const result = new NC(dust).round(2); // 2959.96 — exact decimal of the represented value
159
+ const result = new NC(dust).round(2, { snap: true }); // 2959.97 — matches the true half-up value
160
+ const result = new NC(719.6446).round(2, { snap: true }); // 719.64 — values beyond a few ULPs of the boundary are untouched
161
+ ```
162
+
163
+
164
+
144
165
  <h4>inst.toPrecision(precision?: number)</h4>
145
166
 
146
167
  ```typescript
@@ -175,4 +196,4 @@ const result = new NC(10).plus(new NC().times(2, 4), '6').divide(new NC(12).minu
175
196
 
176
197
  <h2>Changelog</h2>
177
198
 
178
- If you have recently updated, please read the [changelog](https://github.com/rinxun/numeric-calculator/releases) for details of what has changed.
199
+ If you have recently updated, please read the [changelog](https://github.com/rinxun/numeric-calculator/releases) for details of what has changed.
package/index.d.ts CHANGED
@@ -3,6 +3,17 @@ declare type IConfig = {
3
3
  fractionDigits?: number;
4
4
  enableCheckBoundary?: boolean;
5
5
  };
6
+ declare type IRoundOptions = {
7
+ /**
8
+ * @description when true, nudges the amplified value by a few ULPs relative
9
+ * to its own magnitude before rounding, so that binary floating-point
10
+ * representation dust does not flip values sitting exactly on a rounding
11
+ * boundary. Use it when the input may come from plain float arithmetic
12
+ * upstream, e.g. a true 95.015 arriving as 95.01499999999942. Defaults to
13
+ * false, which keeps the exact-decimal behavior of the represented value.
14
+ */
15
+ snap?: boolean;
16
+ };
6
17
  declare type IOperand = Calculator | number | string;
7
18
  /**
8
19
  * @description Calculate addition, subtaction, multiplication, division precisely, for tackling the precision issue
@@ -134,8 +145,9 @@ declare class Calculator {
134
145
  /**
135
146
  *
136
147
  * @param {number} fractionDigits default to 2, should be in the range 0 - 20
148
+ * @param {IRoundOptions} options rounding options, see {@link IRoundOptions.snap}
137
149
  * @returns {number} rounded result
138
150
  */
139
- round(fractionDigits?: number): number;
151
+ round(fractionDigits?: number, options?: IRoundOptions): number;
140
152
  }
141
153
  export default Calculator;
package/index.js CHANGED
@@ -323,13 +323,21 @@ var Calculator = /** @class */ (function () {
323
323
  /**
324
324
  *
325
325
  * @param {number} fractionDigits default to 2, should be in the range 0 - 20
326
+ * @param {IRoundOptions} options rounding options, see {@link IRoundOptions.snap}
326
327
  * @returns {number} rounded result
327
328
  */
328
- Calculator.prototype.round = function (fractionDigits) {
329
+ Calculator.prototype.round = function (fractionDigits, options) {
329
330
  var magnification = Math.pow(10, fractionDigits || this._fractionDigits || 2);
330
331
  var amplifyingResult = this.processTimes(this._VALUE || 0, magnification);
331
332
  this.checkBoundary(amplifyingResult);
332
- return this.processDivide(Math.round(amplifyingResult), magnification);
333
+ // Dust observed in real pipelines reaches a few dozen ULPs (each float op
334
+ // distorts by at most 0.5 ULP), so 64x headroom. This stays safely below
335
+ // half an amplified unit at any magnitude (it scales with the value), and
336
+ // assumes inputs are meaningful well beyond ULP precision.
337
+ var tolerance = (options === null || options === void 0 ? void 0 : options.snap)
338
+ ? Math.abs(amplifyingResult) * Number.EPSILON * 64
339
+ : 0;
340
+ return this.processDivide(Math.round(amplifyingResult + tolerance), magnification);
333
341
  };
334
342
  return Calculator;
335
343
  }());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rinxun/numeric-calculator",
3
- "version": "1.0.7-alpha.1",
3
+ "version": "1.0.8",
4
4
  "description": "Calculate addition, subtraction, multiplication, division precisely, for tackling the precision issue",
5
5
  "keywords": [
6
6
  "long",