@js-draw/math 1.11.1 → 1.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. package/dist/cjs/lib.d.ts +1 -1
  2. package/dist/cjs/lib.js +16 -3
  3. package/dist/cjs/rounding/cleanUpNumber.d.ts +3 -0
  4. package/dist/cjs/rounding/cleanUpNumber.js +35 -0
  5. package/dist/cjs/rounding/constants.d.ts +1 -0
  6. package/dist/cjs/rounding/constants.js +4 -0
  7. package/dist/cjs/rounding/getLenAfterDecimal.d.ts +10 -0
  8. package/dist/cjs/rounding/getLenAfterDecimal.js +30 -0
  9. package/dist/cjs/rounding/lib.d.ts +1 -0
  10. package/dist/cjs/rounding/lib.js +5 -0
  11. package/dist/cjs/{rounding.d.ts → rounding/toRoundedString.d.ts} +1 -3
  12. package/dist/cjs/rounding/toRoundedString.js +54 -0
  13. package/dist/cjs/rounding/toStringOfSamePrecision.d.ts +2 -0
  14. package/dist/cjs/rounding/toStringOfSamePrecision.js +58 -0
  15. package/dist/cjs/rounding/toStringOfSamePrecision.test.d.ts +1 -0
  16. package/dist/cjs/shapes/Path.js +8 -7
  17. package/dist/mjs/lib.d.ts +1 -1
  18. package/dist/mjs/lib.mjs +1 -1
  19. package/dist/mjs/rounding/cleanUpNumber.d.ts +3 -0
  20. package/dist/mjs/rounding/cleanUpNumber.mjs +31 -0
  21. package/dist/mjs/rounding/cleanUpNumber.test.d.ts +1 -0
  22. package/dist/mjs/rounding/constants.d.ts +1 -0
  23. package/dist/mjs/rounding/constants.mjs +1 -0
  24. package/dist/mjs/rounding/getLenAfterDecimal.d.ts +10 -0
  25. package/dist/mjs/rounding/getLenAfterDecimal.mjs +26 -0
  26. package/dist/mjs/rounding/lib.d.ts +1 -0
  27. package/dist/mjs/rounding/lib.mjs +1 -0
  28. package/dist/mjs/{rounding.d.ts → rounding/toRoundedString.d.ts} +1 -3
  29. package/dist/mjs/rounding/toRoundedString.mjs +47 -0
  30. package/dist/mjs/rounding/toRoundedString.test.d.ts +1 -0
  31. package/dist/mjs/rounding/toStringOfSamePrecision.d.ts +2 -0
  32. package/dist/mjs/rounding/toStringOfSamePrecision.mjs +51 -0
  33. package/dist/mjs/rounding/toStringOfSamePrecision.test.d.ts +1 -0
  34. package/dist/mjs/shapes/Path.mjs +2 -1
  35. package/package.json +2 -2
  36. package/src/lib.ts +1 -1
  37. package/src/rounding/cleanUpNumber.test.ts +15 -0
  38. package/src/rounding/cleanUpNumber.ts +38 -0
  39. package/src/rounding/constants.ts +3 -0
  40. package/src/rounding/getLenAfterDecimal.ts +29 -0
  41. package/src/rounding/lib.ts +2 -0
  42. package/src/rounding/toRoundedString.test.ts +32 -0
  43. package/src/rounding/toRoundedString.ts +57 -0
  44. package/src/rounding/toStringOfSamePrecision.test.ts +21 -0
  45. package/src/rounding/toStringOfSamePrecision.ts +63 -0
  46. package/src/shapes/Path.ts +2 -1
  47. package/dist/cjs/rounding.js +0 -146
  48. package/dist/mjs/rounding.mjs +0 -139
  49. package/src/rounding.test.ts +0 -65
  50. package/src/rounding.ts +0 -168
  51. /package/dist/cjs/{rounding.test.d.ts → rounding/cleanUpNumber.test.d.ts} +0 -0
  52. /package/dist/{mjs/rounding.test.d.ts → cjs/rounding/toRoundedString.test.d.ts} +0 -0
package/dist/cjs/lib.d.ts CHANGED
@@ -25,4 +25,4 @@ export { Mat33, Mat33Array } from './Mat33';
25
25
  export { Point2, Vec2 } from './Vec2';
26
26
  export { Vec3 } from './Vec3';
27
27
  export { Color4 } from './Color4';
28
- export { toRoundedString } from './rounding';
28
+ export * from './rounding/lib';
package/dist/cjs/lib.js CHANGED
@@ -17,8 +17,22 @@
17
17
  *
18
18
  * @packageDocumentation
19
19
  */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
32
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
33
+ };
20
34
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.toRoundedString = exports.Color4 = exports.Vec3 = exports.Vec2 = exports.Mat33 = exports.Abstract2DShape = exports.QuadraticBezier = exports.Rect2 = exports.PathCommandType = exports.Path = exports.LineSegment2 = void 0;
35
+ exports.Color4 = exports.Vec3 = exports.Vec2 = exports.Mat33 = exports.Abstract2DShape = exports.QuadraticBezier = exports.Rect2 = exports.PathCommandType = exports.Path = exports.LineSegment2 = void 0;
22
36
  var LineSegment2_1 = require("./shapes/LineSegment2");
23
37
  Object.defineProperty(exports, "LineSegment2", { enumerable: true, get: function () { return LineSegment2_1.LineSegment2; } });
24
38
  var Path_1 = require("./shapes/Path");
@@ -38,7 +52,6 @@ var Vec3_1 = require("./Vec3");
38
52
  Object.defineProperty(exports, "Vec3", { enumerable: true, get: function () { return Vec3_1.Vec3; } });
39
53
  var Color4_1 = require("./Color4");
40
54
  Object.defineProperty(exports, "Color4", { enumerable: true, get: function () { return Color4_1.Color4; } });
41
- var rounding_1 = require("./rounding");
42
- Object.defineProperty(exports, "toRoundedString", { enumerable: true, get: function () { return rounding_1.toRoundedString; } });
55
+ __exportStar(require("./rounding/lib"), exports);
43
56
  // Note: All above exports cannot use `export { default as ... } from "..."` because this
44
57
  // breaks TypeDoc -- TypeDoc otherwise labels any imports of these classes as `default`.
@@ -0,0 +1,3 @@
1
+ /** Cleans up stringified numbers */
2
+ export declare const cleanUpNumber: (text: string) => string;
3
+ export default cleanUpNumber;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cleanUpNumber = void 0;
4
+ /** Cleans up stringified numbers */
5
+ const cleanUpNumber = (text) => {
6
+ // Regular expression substitions can be somewhat expensive. Only do them
7
+ // if necessary.
8
+ if (text.indexOf('e') > 0) {
9
+ // Round to zero.
10
+ if (text.match(/[eE][-]\d{2,}$/)) {
11
+ return '0';
12
+ }
13
+ }
14
+ const lastChar = text.charAt(text.length - 1);
15
+ if (lastChar === '0' || lastChar === '.') {
16
+ // Remove trailing zeroes
17
+ text = text.replace(/([.]\d*[^0]+)0+$/, '$1');
18
+ text = text.replace(/[.]0+$/, '.');
19
+ // Remove trailing period
20
+ text = text.replace(/[.]$/, '');
21
+ }
22
+ const firstChar = text.charAt(0);
23
+ if (firstChar === '0' || firstChar === '-') {
24
+ // Remove unnecessary leading zeroes.
25
+ text = text.replace(/^(0+)[.]/, '.');
26
+ text = text.replace(/^-(0+)[.]/, '-.');
27
+ text = text.replace(/^(-?)0+$/, '$10');
28
+ }
29
+ if (text === '-0') {
30
+ return '0';
31
+ }
32
+ return text;
33
+ };
34
+ exports.cleanUpNumber = cleanUpNumber;
35
+ exports.default = exports.cleanUpNumber;
@@ -0,0 +1 @@
1
+ export declare const numberRegex: RegExp;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.numberRegex = void 0;
4
+ exports.numberRegex = /^([-]?)(\d*)[.](\d+)$/;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Returns the length of `numberAsString` after a decimal point.
3
+ *
4
+ * For example,
5
+ * ```ts
6
+ * getLenAfterDecimal('1.001') // -> 3
7
+ * ```
8
+ */
9
+ export declare const getLenAfterDecimal: (numberAsString: string) => number;
10
+ export default getLenAfterDecimal;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getLenAfterDecimal = void 0;
4
+ const constants_1 = require("./constants");
5
+ /**
6
+ * Returns the length of `numberAsString` after a decimal point.
7
+ *
8
+ * For example,
9
+ * ```ts
10
+ * getLenAfterDecimal('1.001') // -> 3
11
+ * ```
12
+ */
13
+ const getLenAfterDecimal = (numberAsString) => {
14
+ const numberMatch = constants_1.numberRegex.exec(numberAsString);
15
+ if (!numberMatch) {
16
+ // If not a match, either the number is exponential notation (or is something
17
+ // like NaN or Infinity)
18
+ if (numberAsString.search(/[eE]/) !== -1 || /^[a-zA-Z]+$/.exec(numberAsString)) {
19
+ return -1;
20
+ // Or it has no decimal point
21
+ }
22
+ else {
23
+ return 0;
24
+ }
25
+ }
26
+ const afterDecimalLen = numberMatch[3].length;
27
+ return afterDecimalLen;
28
+ };
29
+ exports.getLenAfterDecimal = getLenAfterDecimal;
30
+ exports.default = exports.getLenAfterDecimal;
@@ -0,0 +1 @@
1
+ export { toRoundedString } from './toRoundedString';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toRoundedString = void 0;
4
+ var toRoundedString_1 = require("./toRoundedString");
5
+ Object.defineProperty(exports, "toRoundedString", { enumerable: true, get: function () { return toRoundedString_1.toRoundedString; } });
@@ -1,4 +1,3 @@
1
- export declare const cleanUpNumber: (text: string) => string;
2
1
  /**
3
2
  * Converts `num` to a string, removing trailing digits that were likely caused by
4
3
  * precision errors.
@@ -11,5 +10,4 @@ export declare const cleanUpNumber: (text: string) => string;
11
10
  * ```
12
11
  */
13
12
  export declare const toRoundedString: (num: number) => string;
14
- export declare const getLenAfterDecimal: (numberAsString: string) => number;
15
- export declare const toStringOfSamePrecision: (num: number, ...references: string[]) => string;
13
+ export default toRoundedString;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.toRoundedString = void 0;
7
+ const cleanUpNumber_1 = __importDefault(require("./cleanUpNumber"));
8
+ /**
9
+ * Converts `num` to a string, removing trailing digits that were likely caused by
10
+ * precision errors.
11
+ *
12
+ * @example
13
+ * ```ts,runnable,console
14
+ * import { toRoundedString } from '@js-draw/math';
15
+ *
16
+ * console.log('Rounded: ', toRoundedString(1.000000011));
17
+ * ```
18
+ */
19
+ const toRoundedString = (num) => {
20
+ // Try to remove rounding errors. If the number ends in at least three/four zeroes
21
+ // (or nines) just one or two digits, it's probably a rounding error.
22
+ const fixRoundingUpExp = /^([-]?\d*\.\d{3,})0{4,}\d{1,4}$/;
23
+ const hasRoundingDownExp = /^([-]?)(\d*)\.(\d{3,}9{4,})\d{1,4}$/;
24
+ let text = num.toString(10);
25
+ if (text.indexOf('.') === -1) {
26
+ return text;
27
+ }
28
+ const roundingDownMatch = hasRoundingDownExp.exec(text);
29
+ if (roundingDownMatch) {
30
+ const negativeSign = roundingDownMatch[1];
31
+ const postDecimalString = roundingDownMatch[3];
32
+ const lastDigit = parseInt(postDecimalString.charAt(postDecimalString.length - 1), 10);
33
+ const postDecimal = parseInt(postDecimalString, 10);
34
+ const preDecimal = parseInt(roundingDownMatch[2], 10);
35
+ const origPostDecimalString = roundingDownMatch[3];
36
+ let newPostDecimal = (postDecimal + 10 - lastDigit).toString();
37
+ let carry = 0;
38
+ if (newPostDecimal.length > postDecimal.toString().length) {
39
+ // Left-shift
40
+ newPostDecimal = newPostDecimal.substring(1);
41
+ carry = 1;
42
+ }
43
+ // parseInt(...).toString() removes leading zeroes. Add them back.
44
+ while (newPostDecimal.length < origPostDecimalString.length) {
45
+ newPostDecimal = carry.toString(10) + newPostDecimal;
46
+ carry = 0;
47
+ }
48
+ text = `${negativeSign + (preDecimal + carry).toString()}.${newPostDecimal}`;
49
+ }
50
+ text = text.replace(fixRoundingUpExp, '$1');
51
+ return (0, cleanUpNumber_1.default)(text);
52
+ };
53
+ exports.toRoundedString = toRoundedString;
54
+ exports.default = exports.toRoundedString;
@@ -0,0 +1,2 @@
1
+ export declare const toStringOfSamePrecision: (num: number, ...references: string[]) => string;
2
+ export default toStringOfSamePrecision;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.toStringOfSamePrecision = void 0;
7
+ const cleanUpNumber_1 = __importDefault(require("./cleanUpNumber"));
8
+ const constants_1 = require("./constants");
9
+ const getLenAfterDecimal_1 = __importDefault(require("./getLenAfterDecimal"));
10
+ const toRoundedString_1 = __importDefault(require("./toRoundedString"));
11
+ // [reference] should be a string representation of a base-10 number (no exponential (e.g. 10e10))
12
+ const toStringOfSamePrecision = (num, ...references) => {
13
+ const text = num.toString(10);
14
+ const textMatch = constants_1.numberRegex.exec(text);
15
+ if (!textMatch) {
16
+ return text;
17
+ }
18
+ let decimalPlaces = -1;
19
+ for (const reference of references) {
20
+ decimalPlaces = Math.max((0, getLenAfterDecimal_1.default)(reference), decimalPlaces);
21
+ }
22
+ if (decimalPlaces === -1) {
23
+ return (0, toRoundedString_1.default)(num);
24
+ }
25
+ // Make text's after decimal length match [afterDecimalLen].
26
+ let postDecimal = textMatch[3].substring(0, decimalPlaces);
27
+ let preDecimal = textMatch[2];
28
+ const nextDigit = textMatch[3].charAt(decimalPlaces);
29
+ if (nextDigit !== '') {
30
+ const asNumber = parseInt(nextDigit, 10);
31
+ if (asNumber >= 5) {
32
+ // Don't attempt to parseInt() an empty string.
33
+ if (postDecimal.length > 0) {
34
+ const leadingZeroMatch = /^(0+)(\d*)$/.exec(postDecimal);
35
+ let leadingZeroes = '';
36
+ let postLeading = postDecimal;
37
+ if (leadingZeroMatch) {
38
+ leadingZeroes = leadingZeroMatch[1];
39
+ postLeading = leadingZeroMatch[2];
40
+ }
41
+ postDecimal = (parseInt(postDecimal) + 1).toString();
42
+ // If postDecimal got longer, remove leading zeroes if possible
43
+ if (postDecimal.length > postLeading.length && leadingZeroes.length > 0) {
44
+ leadingZeroes = leadingZeroes.substring(1);
45
+ }
46
+ postDecimal = leadingZeroes + postDecimal;
47
+ }
48
+ if (postDecimal.length === 0 || postDecimal.length > decimalPlaces) {
49
+ preDecimal = (parseInt(preDecimal) + 1).toString();
50
+ postDecimal = postDecimal.substring(1);
51
+ }
52
+ }
53
+ }
54
+ const negativeSign = textMatch[1];
55
+ return (0, cleanUpNumber_1.default)(`${negativeSign}${preDecimal}.${postDecimal}`);
56
+ };
57
+ exports.toStringOfSamePrecision = toStringOfSamePrecision;
58
+ exports.default = exports.toStringOfSamePrecision;
@@ -4,13 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Path = exports.PathCommandType = void 0;
7
- const rounding_1 = require("../rounding");
8
7
  const LineSegment2_1 = __importDefault(require("./LineSegment2"));
9
8
  const Rect2_1 = __importDefault(require("./Rect2"));
10
9
  const Vec2_1 = require("../Vec2");
11
10
  const CubicBezier_1 = __importDefault(require("./CubicBezier"));
12
11
  const QuadraticBezier_1 = __importDefault(require("./QuadraticBezier"));
13
12
  const PointShape2D_1 = __importDefault(require("./PointShape2D"));
13
+ const toRoundedString_1 = __importDefault(require("../rounding/toRoundedString"));
14
+ const toStringOfSamePrecision_1 = __importDefault(require("../rounding/toStringOfSamePrecision"));
14
15
  var PathCommandType;
15
16
  (function (PathCommandType) {
16
17
  PathCommandType[PathCommandType["LineTo"] = 0] = "LineTo";
@@ -591,15 +592,15 @@ class Path {
591
592
  const absoluteCommandParts = [];
592
593
  const relativeCommandParts = [];
593
594
  const makeAbsCommand = !prevPoint || onlyAbsCommands;
594
- const roundedPrevX = prevPoint ? (0, rounding_1.toRoundedString)(prevPoint.x) : '';
595
- const roundedPrevY = prevPoint ? (0, rounding_1.toRoundedString)(prevPoint.y) : '';
595
+ const roundedPrevX = prevPoint ? (0, toRoundedString_1.default)(prevPoint.x) : '';
596
+ const roundedPrevY = prevPoint ? (0, toRoundedString_1.default)(prevPoint.y) : '';
596
597
  for (const point of points) {
597
- const xComponent = (0, rounding_1.toRoundedString)(point.x);
598
- const yComponent = (0, rounding_1.toRoundedString)(point.y);
598
+ const xComponent = (0, toRoundedString_1.default)(point.x);
599
+ const yComponent = (0, toRoundedString_1.default)(point.y);
599
600
  // Relative commands are often shorter as strings than absolute commands.
600
601
  if (!makeAbsCommand) {
601
- const xComponentRelative = (0, rounding_1.toStringOfSamePrecision)(point.x - prevPoint.x, xComponent, roundedPrevX, roundedPrevY);
602
- const yComponentRelative = (0, rounding_1.toStringOfSamePrecision)(point.y - prevPoint.y, yComponent, roundedPrevX, roundedPrevY);
602
+ const xComponentRelative = (0, toStringOfSamePrecision_1.default)(point.x - prevPoint.x, xComponent, roundedPrevX, roundedPrevY);
603
+ const yComponentRelative = (0, toStringOfSamePrecision_1.default)(point.y - prevPoint.y, yComponent, roundedPrevX, roundedPrevY);
603
604
  // No need for an additional separator if it starts with a '-'
604
605
  if (yComponentRelative.charAt(0) === '-') {
605
606
  relativeCommandParts.push(`${xComponentRelative}${yComponentRelative}`);
package/dist/mjs/lib.d.ts CHANGED
@@ -25,4 +25,4 @@ export { Mat33, Mat33Array } from './Mat33';
25
25
  export { Point2, Vec2 } from './Vec2';
26
26
  export { Vec3 } from './Vec3';
27
27
  export { Color4 } from './Color4';
28
- export { toRoundedString } from './rounding';
28
+ export * from './rounding/lib';
package/dist/mjs/lib.mjs CHANGED
@@ -25,6 +25,6 @@ export { Mat33 } from './Mat33.mjs';
25
25
  export { Vec2 } from './Vec2.mjs';
26
26
  export { Vec3 } from './Vec3.mjs';
27
27
  export { Color4 } from './Color4.mjs';
28
- export { toRoundedString } from './rounding.mjs';
28
+ export * from './rounding/lib.mjs';
29
29
  // Note: All above exports cannot use `export { default as ... } from "..."` because this
30
30
  // breaks TypeDoc -- TypeDoc otherwise labels any imports of these classes as `default`.
@@ -0,0 +1,3 @@
1
+ /** Cleans up stringified numbers */
2
+ export declare const cleanUpNumber: (text: string) => string;
3
+ export default cleanUpNumber;
@@ -0,0 +1,31 @@
1
+ /** Cleans up stringified numbers */
2
+ export const cleanUpNumber = (text) => {
3
+ // Regular expression substitions can be somewhat expensive. Only do them
4
+ // if necessary.
5
+ if (text.indexOf('e') > 0) {
6
+ // Round to zero.
7
+ if (text.match(/[eE][-]\d{2,}$/)) {
8
+ return '0';
9
+ }
10
+ }
11
+ const lastChar = text.charAt(text.length - 1);
12
+ if (lastChar === '0' || lastChar === '.') {
13
+ // Remove trailing zeroes
14
+ text = text.replace(/([.]\d*[^0]+)0+$/, '$1');
15
+ text = text.replace(/[.]0+$/, '.');
16
+ // Remove trailing period
17
+ text = text.replace(/[.]$/, '');
18
+ }
19
+ const firstChar = text.charAt(0);
20
+ if (firstChar === '0' || firstChar === '-') {
21
+ // Remove unnecessary leading zeroes.
22
+ text = text.replace(/^(0+)[.]/, '.');
23
+ text = text.replace(/^-(0+)[.]/, '-.');
24
+ text = text.replace(/^(-?)0+$/, '$10');
25
+ }
26
+ if (text === '-0') {
27
+ return '0';
28
+ }
29
+ return text;
30
+ };
31
+ export default cleanUpNumber;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const numberRegex: RegExp;
@@ -0,0 +1 @@
1
+ export const numberRegex = /^([-]?)(\d*)[.](\d+)$/;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Returns the length of `numberAsString` after a decimal point.
3
+ *
4
+ * For example,
5
+ * ```ts
6
+ * getLenAfterDecimal('1.001') // -> 3
7
+ * ```
8
+ */
9
+ export declare const getLenAfterDecimal: (numberAsString: string) => number;
10
+ export default getLenAfterDecimal;
@@ -0,0 +1,26 @@
1
+ import { numberRegex } from './constants.mjs';
2
+ /**
3
+ * Returns the length of `numberAsString` after a decimal point.
4
+ *
5
+ * For example,
6
+ * ```ts
7
+ * getLenAfterDecimal('1.001') // -> 3
8
+ * ```
9
+ */
10
+ export const getLenAfterDecimal = (numberAsString) => {
11
+ const numberMatch = numberRegex.exec(numberAsString);
12
+ if (!numberMatch) {
13
+ // If not a match, either the number is exponential notation (or is something
14
+ // like NaN or Infinity)
15
+ if (numberAsString.search(/[eE]/) !== -1 || /^[a-zA-Z]+$/.exec(numberAsString)) {
16
+ return -1;
17
+ // Or it has no decimal point
18
+ }
19
+ else {
20
+ return 0;
21
+ }
22
+ }
23
+ const afterDecimalLen = numberMatch[3].length;
24
+ return afterDecimalLen;
25
+ };
26
+ export default getLenAfterDecimal;
@@ -0,0 +1 @@
1
+ export { toRoundedString } from './toRoundedString';
@@ -0,0 +1 @@
1
+ export { toRoundedString } from './toRoundedString.mjs';
@@ -1,4 +1,3 @@
1
- export declare const cleanUpNumber: (text: string) => string;
2
1
  /**
3
2
  * Converts `num` to a string, removing trailing digits that were likely caused by
4
3
  * precision errors.
@@ -11,5 +10,4 @@ export declare const cleanUpNumber: (text: string) => string;
11
10
  * ```
12
11
  */
13
12
  export declare const toRoundedString: (num: number) => string;
14
- export declare const getLenAfterDecimal: (numberAsString: string) => number;
15
- export declare const toStringOfSamePrecision: (num: number, ...references: string[]) => string;
13
+ export default toRoundedString;
@@ -0,0 +1,47 @@
1
+ import cleanUpNumber from './cleanUpNumber.mjs';
2
+ /**
3
+ * Converts `num` to a string, removing trailing digits that were likely caused by
4
+ * precision errors.
5
+ *
6
+ * @example
7
+ * ```ts,runnable,console
8
+ * import { toRoundedString } from '@js-draw/math';
9
+ *
10
+ * console.log('Rounded: ', toRoundedString(1.000000011));
11
+ * ```
12
+ */
13
+ export const toRoundedString = (num) => {
14
+ // Try to remove rounding errors. If the number ends in at least three/four zeroes
15
+ // (or nines) just one or two digits, it's probably a rounding error.
16
+ const fixRoundingUpExp = /^([-]?\d*\.\d{3,})0{4,}\d{1,4}$/;
17
+ const hasRoundingDownExp = /^([-]?)(\d*)\.(\d{3,}9{4,})\d{1,4}$/;
18
+ let text = num.toString(10);
19
+ if (text.indexOf('.') === -1) {
20
+ return text;
21
+ }
22
+ const roundingDownMatch = hasRoundingDownExp.exec(text);
23
+ if (roundingDownMatch) {
24
+ const negativeSign = roundingDownMatch[1];
25
+ const postDecimalString = roundingDownMatch[3];
26
+ const lastDigit = parseInt(postDecimalString.charAt(postDecimalString.length - 1), 10);
27
+ const postDecimal = parseInt(postDecimalString, 10);
28
+ const preDecimal = parseInt(roundingDownMatch[2], 10);
29
+ const origPostDecimalString = roundingDownMatch[3];
30
+ let newPostDecimal = (postDecimal + 10 - lastDigit).toString();
31
+ let carry = 0;
32
+ if (newPostDecimal.length > postDecimal.toString().length) {
33
+ // Left-shift
34
+ newPostDecimal = newPostDecimal.substring(1);
35
+ carry = 1;
36
+ }
37
+ // parseInt(...).toString() removes leading zeroes. Add them back.
38
+ while (newPostDecimal.length < origPostDecimalString.length) {
39
+ newPostDecimal = carry.toString(10) + newPostDecimal;
40
+ carry = 0;
41
+ }
42
+ text = `${negativeSign + (preDecimal + carry).toString()}.${newPostDecimal}`;
43
+ }
44
+ text = text.replace(fixRoundingUpExp, '$1');
45
+ return cleanUpNumber(text);
46
+ };
47
+ export default toRoundedString;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare const toStringOfSamePrecision: (num: number, ...references: string[]) => string;
2
+ export default toStringOfSamePrecision;
@@ -0,0 +1,51 @@
1
+ import cleanUpNumber from './cleanUpNumber.mjs';
2
+ import { numberRegex } from './constants.mjs';
3
+ import getLenAfterDecimal from './getLenAfterDecimal.mjs';
4
+ import toRoundedString from './toRoundedString.mjs';
5
+ // [reference] should be a string representation of a base-10 number (no exponential (e.g. 10e10))
6
+ export const toStringOfSamePrecision = (num, ...references) => {
7
+ const text = num.toString(10);
8
+ const textMatch = numberRegex.exec(text);
9
+ if (!textMatch) {
10
+ return text;
11
+ }
12
+ let decimalPlaces = -1;
13
+ for (const reference of references) {
14
+ decimalPlaces = Math.max(getLenAfterDecimal(reference), decimalPlaces);
15
+ }
16
+ if (decimalPlaces === -1) {
17
+ return toRoundedString(num);
18
+ }
19
+ // Make text's after decimal length match [afterDecimalLen].
20
+ let postDecimal = textMatch[3].substring(0, decimalPlaces);
21
+ let preDecimal = textMatch[2];
22
+ const nextDigit = textMatch[3].charAt(decimalPlaces);
23
+ if (nextDigit !== '') {
24
+ const asNumber = parseInt(nextDigit, 10);
25
+ if (asNumber >= 5) {
26
+ // Don't attempt to parseInt() an empty string.
27
+ if (postDecimal.length > 0) {
28
+ const leadingZeroMatch = /^(0+)(\d*)$/.exec(postDecimal);
29
+ let leadingZeroes = '';
30
+ let postLeading = postDecimal;
31
+ if (leadingZeroMatch) {
32
+ leadingZeroes = leadingZeroMatch[1];
33
+ postLeading = leadingZeroMatch[2];
34
+ }
35
+ postDecimal = (parseInt(postDecimal) + 1).toString();
36
+ // If postDecimal got longer, remove leading zeroes if possible
37
+ if (postDecimal.length > postLeading.length && leadingZeroes.length > 0) {
38
+ leadingZeroes = leadingZeroes.substring(1);
39
+ }
40
+ postDecimal = leadingZeroes + postDecimal;
41
+ }
42
+ if (postDecimal.length === 0 || postDecimal.length > decimalPlaces) {
43
+ preDecimal = (parseInt(preDecimal) + 1).toString();
44
+ postDecimal = postDecimal.substring(1);
45
+ }
46
+ }
47
+ }
48
+ const negativeSign = textMatch[1];
49
+ return cleanUpNumber(`${negativeSign}${preDecimal}.${postDecimal}`);
50
+ };
51
+ export default toStringOfSamePrecision;
@@ -1,10 +1,11 @@
1
- import { toRoundedString, toStringOfSamePrecision } from '../rounding.mjs';
2
1
  import LineSegment2 from './LineSegment2.mjs';
3
2
  import Rect2 from './Rect2.mjs';
4
3
  import { Vec2 } from '../Vec2.mjs';
5
4
  import CubicBezier from './CubicBezier.mjs';
6
5
  import QuadraticBezier from './QuadraticBezier.mjs';
7
6
  import PointShape2D from './PointShape2D.mjs';
7
+ import toRoundedString from '../rounding/toRoundedString.mjs';
8
+ import toStringOfSamePrecision from '../rounding/toStringOfSamePrecision.mjs';
8
9
  export var PathCommandType;
9
10
  (function (PathCommandType) {
10
11
  PathCommandType[PathCommandType["LineTo"] = 0] = "LineTo";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@js-draw/math",
3
- "version": "1.11.1",
3
+ "version": "1.16.0",
4
4
  "description": "A math library for js-draw. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -45,5 +45,5 @@
45
45
  "svg",
46
46
  "math"
47
47
  ],
48
- "gitHead": "695cfe01116839842668233a14fa858ad4ae0bac"
48
+ "gitHead": "b0b6d7165d76582e1c197d0f56a10bfe6b46e2bc"
49
49
  }
package/src/lib.ts CHANGED
@@ -36,7 +36,7 @@ export { Mat33, Mat33Array } from './Mat33';
36
36
  export { Point2, Vec2 } from './Vec2';
37
37
  export { Vec3 } from './Vec3';
38
38
  export { Color4 } from './Color4';
39
- export { toRoundedString } from './rounding';
39
+ export * from './rounding/lib';
40
40
 
41
41
 
42
42
  // Note: All above exports cannot use `export { default as ... } from "..."` because this
@@ -0,0 +1,15 @@
1
+ import cleanUpNumber from './cleanUpNumber';
2
+
3
+ it('cleanUpNumber', () => {
4
+ expect(cleanUpNumber('000.0000')).toBe('0');
5
+ expect(cleanUpNumber('-000.0000')).toBe('0');
6
+ expect(cleanUpNumber('0.0000')).toBe('0');
7
+ expect(cleanUpNumber('0.001')).toBe('.001');
8
+ expect(cleanUpNumber('-0.001')).toBe('-.001');
9
+ expect(cleanUpNumber('-0.000000001')).toBe('-.000000001');
10
+ expect(cleanUpNumber('-0.00000000100')).toBe('-.000000001');
11
+ expect(cleanUpNumber('1234')).toBe('1234');
12
+ expect(cleanUpNumber('1234.5')).toBe('1234.5');
13
+ expect(cleanUpNumber('1234.500')).toBe('1234.5');
14
+ expect(cleanUpNumber('1.1368683772161603e-13')).toBe('0');
15
+ });
@@ -0,0 +1,38 @@
1
+ /** Cleans up stringified numbers */
2
+ export const cleanUpNumber = (text: string) => {
3
+ // Regular expression substitions can be somewhat expensive. Only do them
4
+ // if necessary.
5
+
6
+ if (text.indexOf('e') > 0) {
7
+ // Round to zero.
8
+ if (text.match(/[eE][-]\d{2,}$/)) {
9
+ return '0';
10
+ }
11
+ }
12
+
13
+ const lastChar = text.charAt(text.length - 1);
14
+ if (lastChar === '0' || lastChar === '.') {
15
+ // Remove trailing zeroes
16
+ text = text.replace(/([.]\d*[^0]+)0+$/, '$1');
17
+ text = text.replace(/[.]0+$/, '.');
18
+
19
+ // Remove trailing period
20
+ text = text.replace(/[.]$/, '');
21
+ }
22
+
23
+ const firstChar = text.charAt(0);
24
+ if (firstChar === '0' || firstChar === '-') {
25
+ // Remove unnecessary leading zeroes.
26
+ text = text.replace(/^(0+)[.]/, '.');
27
+ text = text.replace(/^-(0+)[.]/, '-.');
28
+ text = text.replace(/^(-?)0+$/, '$10');
29
+ }
30
+
31
+ if (text === '-0') {
32
+ return '0';
33
+ }
34
+
35
+ return text;
36
+ };
37
+
38
+ export default cleanUpNumber;