Socket
Socket
Sign inDemoInstall

@mathigon/fermat

Package Overview
Dependencies
1
Maintainers
1
Versions
59
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.1.11 to 1.1.12

6

dist/arithmetic.d.ts

@@ -36,8 +36,2 @@ /** Checks if two numbers are nearly equals. */

export declare function roundTo(n: number, increment?: number): number;
/**
* Returns an [numerator, denominator] array that approximated a `decimal` to
* `precision`. See http://en.wikipedia.org/wiki/Continued_fraction
*/
export declare function toFraction(x: number, maxDen?: number, precision?: number): [num: number, den: number] | undefined;
export declare function toMixedNumber(x: number, maxDen?: number, precision?: number): [base: number, num: number, den: number] | undefined;
/** Bounds a number between a lower and an upper limit. */

@@ -44,0 +38,0 @@ export declare function clamp(x: number, min?: number, max?: number): number;

107

dist/index.cjs.js

@@ -67,4 +67,2 @@ "use strict";

subsets: () => subsets,
toFraction: () => toFraction,
toMixedNumber: () => toMixedNumber,
toOrdinal: () => toOrdinal,

@@ -247,24 +245,2 @@ toWord: () => toWord,

}
function toFraction(x, maxDen = 1e3, precision = 1e-12) {
let n = [1, 0];
let d = [0, 1];
const absX = Math.abs(x);
let rem = absX;
while (Math.abs(n[0] / d[0] - absX) > precision) {
const a = Math.floor(rem);
n = [a * n[0] + n[1], n[0]];
d = [a * d[0] + d[1], d[0]];
if (d[0] > maxDen)
return;
rem = 1 / (rem - a);
}
if (d[0] === 1 || !nearlyEquals(n[0] / d[0], absX, precision))
return;
return [sign(x) * n[0], d[0]];
}
function toMixedNumber(x, maxDen, precision) {
const base = Math.trunc(x);
const fraction = toFraction(Math.abs(x - base), maxDen, precision);
return fraction ? [base, ...fraction] : void 0;
}
function clamp(x, min = -Infinity, max = Infinity) {

@@ -567,2 +543,3 @@ return Math.min(max, Math.max(min, x));

var FORMAT = /^([0-9\-.]*)([%πkmbtq]?)(\/([0-9\-.]+))?([%π]?)$/;
var tooBig = (x) => x >= Number.MAX_SAFE_INTEGER;
var XNumber = class _XNumber {

@@ -579,7 +556,37 @@ /** Only used for fractions and always ≥ 0. */

}
toMixed() {
if (!this.den || this.unit)
return this.toString();
const part = Math.abs(this.num) % this.den;
const whole = Math.abs(Math.trunc(this.value));
if (!whole)
return this.toString();
return `${this.sign < 0 ? "\u2013" : ""}${whole} ${part}/${this.den}`;
}
toExpr(type, precision = 4) {
const v = this.value;
if (Math.abs(v) >= Number.MAX_VALUE)
return "\u221E";
if (tooBig(this.num) || this.den && tooBig(this.den))
type = "decimal";
if (type === "scientific" || Math.abs(v) >= Number.MAX_SAFE_INTEGER) {
const [base, power2] = this.value.toExponential(precision - 1).split("e");
if (Math.abs(+power2) >= precision) {
const isNeg = power2.startsWith("-");
const exp = `${isNeg ? "(" : ""}${isNeg ? power2 : power2.slice(1)}${isNeg ? ")" : ""}`;
return `${base.replace(/\.?0+$/, "")} \xD7 10^${exp}${this.unit || ""}`;
}
}
if (!this.unit && !this.den || type === "decimal" || type === "scientific") {
const formatted = numberFormat(this.value, precision);
return formatted.match(/^[\d.]+$/g) ? formatted : `"${formatted}"`;
} else {
return type === "mixed" ? this.toMixed() : this.toString();
}
}
toString(precision = 4) {
const separators = !this.den && !this.unit;
let num = numberFormat(this.num, precision, separators);
let num = numberFormat(this.num, this.den ? 0 : precision, separators);
let unit = this.unit || "";
const den = this.den ? `/${numberFormat(this.den, precision, separators)}` : "";
const den = this.den ? `/${numberFormat(this.den, 0, separators)}` : "";
if (num === "0")

@@ -628,6 +635,11 @@ unit = "";

}
get fraction() {
if (this.unit || !isInteger(this.num))
return;
return [this.num, this.den || 1];
}
// ---------------------------------------------------------------------------
/** Parses a number string, e.g. '1/2' or '20.7%'. */
static fromString(s) {
s = s.toLowerCase().replace(/[\s,]/g, "").replace("\u2013", "-").replace("pi", "\u03C0");
s = s.toLowerCase().replace(/[\s,"]/g, "").replace("\u2013", "-").replace("pi", "\u03C0");
const match = s.match(FORMAT);

@@ -657,29 +669,18 @@ if (!match)

/** Converts a decimal into the closest fraction with a given maximum denominator. */
static fractionFromDecimal(x, maxDen = 100) {
const sign2 = Math.sign(x);
const whole = Math.floor(Math.abs(x));
x = Math.abs(x) - whole;
let a = 0;
let b = 1;
let c = 1;
let d = 1;
while (b <= maxDen && d <= maxDen) {
const mediant = (a + c) / (b + d);
if (x === mediant) {
if (b + d <= maxDen) {
return new _XNumber(sign2 * (whole * (b + d) + a + c), b + d);
} else if (d > b) {
return new _XNumber(sign2 * (whole * d + c), d);
} else {
return new _XNumber(sign2 * (whole * b + a), b);
}
} else if (x > mediant) {
[a, b] = [a + c, b + d];
} else {
[c, d] = [a + c, b + d];
}
static fractionFromDecimal(x, maxDen = 1e3, precision = 1e-12) {
let n = [1, 0];
let d = [0, 1];
const absX = Math.abs(x);
let rem = absX;
while (Math.abs(n[0] / d[0] - absX) > precision) {
const a = Math.floor(rem);
n = [a * n[0] + n[1], n[0]];
d = [a * d[0] + d[1], d[0]];
if (d[0] > maxDen)
return new _XNumber(x);
rem = 1 / (rem - a);
}
if (b > maxDen)
return new _XNumber(sign2 * (whole * d + c), d);
return new _XNumber(sign2 * (whole * b + a), b);
if (!nearlyEquals(n[0] / d[0], absX, precision))
return new _XNumber(x);
return new _XNumber(sign(x) * n[0], d[0] === 1 ? void 0 : d[0]);
}

@@ -686,0 +687,0 @@ // ---------------------------------------------------------------------------

@@ -178,24 +178,2 @@ var __defProp = Object.defineProperty;

}
function toFraction(x, maxDen = 1e3, precision = 1e-12) {
let n = [1, 0];
let d = [0, 1];
const absX = Math.abs(x);
let rem = absX;
while (Math.abs(n[0] / d[0] - absX) > precision) {
const a = Math.floor(rem);
n = [a * n[0] + n[1], n[0]];
d = [a * d[0] + d[1], d[0]];
if (d[0] > maxDen)
return;
rem = 1 / (rem - a);
}
if (d[0] === 1 || !nearlyEquals(n[0] / d[0], absX, precision))
return;
return [sign(x) * n[0], d[0]];
}
function toMixedNumber(x, maxDen, precision) {
const base = Math.trunc(x);
const fraction = toFraction(Math.abs(x - base), maxDen, precision);
return fraction ? [base, ...fraction] : void 0;
}
function clamp(x, min = -Infinity, max = Infinity) {

@@ -498,2 +476,3 @@ return Math.min(max, Math.max(min, x));

var FORMAT = /^([0-9\-.]*)([%πkmbtq]?)(\/([0-9\-.]+))?([%π]?)$/;
var tooBig = (x) => x >= Number.MAX_SAFE_INTEGER;
var XNumber = class _XNumber {

@@ -510,7 +489,37 @@ /** Only used for fractions and always ≥ 0. */

}
toMixed() {
if (!this.den || this.unit)
return this.toString();
const part = Math.abs(this.num) % this.den;
const whole = Math.abs(Math.trunc(this.value));
if (!whole)
return this.toString();
return `${this.sign < 0 ? "\u2013" : ""}${whole} ${part}/${this.den}`;
}
toExpr(type, precision = 4) {
const v = this.value;
if (Math.abs(v) >= Number.MAX_VALUE)
return "\u221E";
if (tooBig(this.num) || this.den && tooBig(this.den))
type = "decimal";
if (type === "scientific" || Math.abs(v) >= Number.MAX_SAFE_INTEGER) {
const [base, power2] = this.value.toExponential(precision - 1).split("e");
if (Math.abs(+power2) >= precision) {
const isNeg = power2.startsWith("-");
const exp = `${isNeg ? "(" : ""}${isNeg ? power2 : power2.slice(1)}${isNeg ? ")" : ""}`;
return `${base.replace(/\.?0+$/, "")} \xD7 10^${exp}${this.unit || ""}`;
}
}
if (!this.unit && !this.den || type === "decimal" || type === "scientific") {
const formatted = numberFormat(this.value, precision);
return formatted.match(/^[\d.]+$/g) ? formatted : `"${formatted}"`;
} else {
return type === "mixed" ? this.toMixed() : this.toString();
}
}
toString(precision = 4) {
const separators = !this.den && !this.unit;
let num = numberFormat(this.num, precision, separators);
let num = numberFormat(this.num, this.den ? 0 : precision, separators);
let unit = this.unit || "";
const den = this.den ? `/${numberFormat(this.den, precision, separators)}` : "";
const den = this.den ? `/${numberFormat(this.den, 0, separators)}` : "";
if (num === "0")

@@ -559,6 +568,11 @@ unit = "";

}
get fraction() {
if (this.unit || !isInteger(this.num))
return;
return [this.num, this.den || 1];
}
// ---------------------------------------------------------------------------
/** Parses a number string, e.g. '1/2' or '20.7%'. */
static fromString(s) {
s = s.toLowerCase().replace(/[\s,]/g, "").replace("\u2013", "-").replace("pi", "\u03C0");
s = s.toLowerCase().replace(/[\s,"]/g, "").replace("\u2013", "-").replace("pi", "\u03C0");
const match = s.match(FORMAT);

@@ -588,29 +602,18 @@ if (!match)

/** Converts a decimal into the closest fraction with a given maximum denominator. */
static fractionFromDecimal(x, maxDen = 100) {
const sign2 = Math.sign(x);
const whole = Math.floor(Math.abs(x));
x = Math.abs(x) - whole;
let a = 0;
let b = 1;
let c = 1;
let d = 1;
while (b <= maxDen && d <= maxDen) {
const mediant = (a + c) / (b + d);
if (x === mediant) {
if (b + d <= maxDen) {
return new _XNumber(sign2 * (whole * (b + d) + a + c), b + d);
} else if (d > b) {
return new _XNumber(sign2 * (whole * d + c), d);
} else {
return new _XNumber(sign2 * (whole * b + a), b);
}
} else if (x > mediant) {
[a, b] = [a + c, b + d];
} else {
[c, d] = [a + c, b + d];
}
static fractionFromDecimal(x, maxDen = 1e3, precision = 1e-12) {
let n = [1, 0];
let d = [0, 1];
const absX = Math.abs(x);
let rem = absX;
while (Math.abs(n[0] / d[0] - absX) > precision) {
const a = Math.floor(rem);
n = [a * n[0] + n[1], n[0]];
d = [a * d[0] + d[1], d[0]];
if (d[0] > maxDen)
return new _XNumber(x);
rem = 1 / (rem - a);
}
if (b > maxDen)
return new _XNumber(sign2 * (whole * d + c), d);
return new _XNumber(sign2 * (whole * b + a), b);
if (!nearlyEquals(n[0] / d[0], absX, precision))
return new _XNumber(x);
return new _XNumber(sign(x) * n[0], d[0] === 1 ? void 0 : d[0]);
}

@@ -1263,4 +1266,2 @@ // ---------------------------------------------------------------------------

subsets,
toFraction,
toMixedNumber,
toOrdinal,

@@ -1267,0 +1268,0 @@ toWord,

@@ -9,2 +9,4 @@ type Suffix = '%' | 'π' | undefined;

valueOf(): number;
toMixed(): string;
toExpr(type?: 'decimal' | 'fraction' | 'mixed' | 'scientific', precision?: number): string;
toString(precision?: number): string;

@@ -24,6 +26,7 @@ toMathML(): string;

get negative(): XNumber;
get fraction(): number[] | undefined;
/** Parses a number string, e.g. '1/2' or '20.7%'. */
static fromString(s: string): XNumber | undefined;
/** Converts a decimal into the closest fraction with a given maximum denominator. */
static fractionFromDecimal(x: number, maxDen?: number): XNumber;
static fractionFromDecimal(x: number, maxDen?: number, precision?: number): XNumber;
clamp(min?: number, max?: number): XNumber;

@@ -30,0 +33,0 @@ add(a: XNumber | number): XNumber;

{
"name": "@mathigon/fermat",
"version": "1.1.11",
"version": "1.1.12",
"license": "MIT",

@@ -38,12 +38,12 @@ "homepage": "https://mathigon.io/fermat",

"dependencies": {
"@mathigon/core": "1.1.12"
"@mathigon/core": "1.1.13"
},
"devDependencies": {
"@types/tape": "5.6.0",
"@typescript-eslint/eslint-plugin": "6.4.0",
"@typescript-eslint/parser": "6.4.0",
"esbuild": "0.19.2",
"eslint": "8.48.0",
"@types/tape": "5.6.1",
"@typescript-eslint/eslint-plugin": "6.7.4",
"@typescript-eslint/parser": "6.7.4",
"esbuild": "0.19.4",
"eslint": "8.50.0",
"eslint-plugin-import": "2.28.1",
"tape": "5.6.6",
"tape": "5.7.0",
"ts-node": "10.9.1",

@@ -50,0 +50,0 @@ "tslib": "2.6.2",

@@ -208,33 +208,3 @@ // ============================================================================

/**
* Returns an [numerator, denominator] array that approximated a `decimal` to
* `precision`. See http://en.wikipedia.org/wiki/Continued_fraction
*/
export function toFraction(x: number, maxDen = 1000, precision = 1e-12): [num: number, den: number] | undefined {
let n = [1, 0];
let d = [0, 1];
const absX = Math.abs(x);
let rem = absX;
while (Math.abs(n[0] / d[0] - absX) > precision) {
const a = Math.floor(rem);
n = [a * n[0] + n[1], n[0]];
d = [a * d[0] + d[1], d[0]];
if (d[0] > maxDen) return;
rem = 1 / (rem - a);
}
// We get as close as we want with our tolerance, and if that fraction is still good past our computation we return it.
// Otherwise, we return false, meaning we didn't find a good enough rational approximation.
if (d[0] === 1 || !nearlyEquals(n[0] / d[0], absX, precision)) return;
return [sign(x) * n[0], d[0]];
}
export function toMixedNumber(x: number, maxDen?: number, precision?: number): [base: number, num: number, den: number] | undefined {
const base = Math.trunc(x);
const fraction = toFraction(Math.abs(x - base), maxDen, precision);
return (fraction) ? [base, ...fraction] : undefined;
}
// -----------------------------------------------------------------------------

@@ -241,0 +211,0 @@ // Simple Operations

@@ -7,3 +7,3 @@ // =============================================================================

import {isInteger, nearlyEquals, numberFormat} from './arithmetic';
import {isInteger, nearlyEquals, numberFormat, sign} from './arithmetic';
import {gcd, lcm} from './number-theory';

@@ -13,2 +13,3 @@

const FORMAT = /^([0-9\-.]*)([%πkmbtq]?)(\/([0-9\-.]+))?([%π]?)$/;
const tooBig = (x: number) => x >= Number.MAX_SAFE_INTEGER;
type Suffix = '%'|'π'|undefined;

@@ -32,7 +33,40 @@

toMixed() {
if (!this.den || this.unit) return this.toString();
const part = Math.abs(this.num) % this.den;
const whole = Math.abs(Math.trunc(this.value));
if (!whole) return this.toString();
return `${this.sign < 0 ? '–' : ''}${whole} ${part}/${this.den}`;
}
toExpr(type?: 'decimal'|'fraction'|'mixed'|'scientific', precision = 4) {
const v = this.value;
// TODO Decide if we really want to return infinity here...
if (Math.abs(v) >= Number.MAX_VALUE) return '∞';
if (tooBig(this.num) || this.den && tooBig(this.den)) type = 'decimal';
// In scientific notation, we try to return a number in the form a × 10^b
if (type === 'scientific' || Math.abs(v) >= Number.MAX_SAFE_INTEGER) {
const [base, power] = this.value.toExponential(precision - 1).split('e');
if (Math.abs(+power) >= precision) {
const isNeg = power.startsWith('-');
const exp = `${isNeg ? '(' : ''}${isNeg ? power : power.slice(1)}${isNeg ? ')' : ''}`;
return `${base.replace(/\.?0+$/, '')} × 10^${exp}${this.unit || ''}`;
}
}
if ((!this.unit && !this.den) || type === 'decimal' || type === 'scientific') {
const formatted = numberFormat(this.value, precision);
// For non-standard number formatting, we add quotes for expr parsing.
return (formatted.match(/^[\d.]+$/g) ? formatted : `"${formatted}"`);
} else {
return type === 'mixed' ? this.toMixed() : this.toString();
}
}
toString(precision = 4) {
const separators = !this.den && !this.unit;
let num = numberFormat(this.num, precision, separators);
let num = numberFormat(this.num, this.den ? 0 : precision, separators);
let unit = this.unit || '';
const den = this.den ? `/${numberFormat(this.den, precision, separators)}` : '';
const den = this.den ? `/${numberFormat(this.den, 0, separators)}` : '';
if (num === '0') unit = '';

@@ -83,2 +117,7 @@ if (unit === 'π' && !this.den && (num === '1' || num === '–1')) num = num.replace('1', '');

get fraction() {
if (this.unit || !isInteger(this.num)) return;
return [this.num, this.den || 1];
}
// ---------------------------------------------------------------------------

@@ -88,3 +127,3 @@

static fromString(s: string) {
s = s.toLowerCase().replace(/[\s,]/g, '').replace('–', '-').replace('pi', 'π');
s = s.toLowerCase().replace(/[\s,"]/g, '').replace('–', '-').replace('pi', 'π');
const match = s.match(FORMAT);

@@ -116,31 +155,18 @@ if (!match) return;

/** Converts a decimal into the closest fraction with a given maximum denominator. */
static fractionFromDecimal(x: number, maxDen = 100) {
const sign = Math.sign(x);
const whole = Math.floor(Math.abs(x));
x = Math.abs(x) - whole;
static fractionFromDecimal(x: number, maxDen = 1000, precision = 1e-12) {
let n = [1, 0];
let d = [0, 1];
const absX = Math.abs(x);
let rem = absX;
let a = 0;
let b = 1;
let c = 1;
let d = 1;
while (b <= maxDen && d <= maxDen) {
const mediant = (a + c) / (b + d);
if (x === mediant) {
if (b + d <= maxDen) {
return new XNumber(sign * (whole * (b + d) + a + c), b + d);
} else if (d > b) {
return new XNumber(sign * (whole * d + c), d);
} else {
return new XNumber(sign * (whole * b + a), b);
}
} else if (x > mediant) {
[a, b] = [a + c, b + d];
} else {
[c, d] = [a + c, b + d];
}
while (Math.abs(n[0] / d[0] - absX) > precision) {
const a = Math.floor(rem);
n = [a * n[0] + n[1], n[0]];
d = [a * d[0] + d[1], d[0]];
if (d[0] > maxDen) return new XNumber(x);
rem = 1 / (rem - a);
}
if (b > maxDen) return new XNumber(sign * (whole * d + c), d);
return new XNumber(sign * (whole * b + a), b);
if (!nearlyEquals(n[0] / d[0], absX, precision)) return new XNumber(x);
return new XNumber(sign(x) * n[0], d[0] === 1 ? undefined : d[0]);
}

@@ -147,0 +173,0 @@

@@ -8,3 +8,3 @@ // =============================================================================

import tape from 'tape';
import {numberFormat, parseNumber, scientificFormat, toFraction, toMixedNumber, toWord} from '../src';
import {numberFormat, parseNumber, scientificFormat, toWord} from '../src';

@@ -111,18 +111,1 @@

});
tape('fractions', (test) => {
test.deepEqual(toFraction(0.333333333333), [1, 3]);
test.deepEqual(toFraction(-0.333333333333), [-1, 3]);
test.deepEqual(toFraction(0.999999999999), undefined);
test.deepEqual(toFraction(0.833333333333), [5, 6]);
test.deepEqual(toFraction(0.171717171717), [17, 99]);
test.deepEqual(toFraction(0.123412341234), undefined);
test.deepEqual(toFraction(0.123412341234, 10000), [1234, 9999]);
test.deepEqual(toMixedNumber(1.333333333333), [1, 1, 3]);
test.deepEqual(toMixedNumber(-1.333333333333), [-1, 1, 3]);
test.end();
});

@@ -14,2 +14,3 @@ // =============================================================================

const dec = (s: number) => XNumber.fractionFromDecimal(s)?.toString();
const expr = (s: number, type: 'decimal'|'fraction'|'mixed'|'scientific') => XNumber.fractionFromDecimal(s)?.toExpr(type);

@@ -24,3 +25,3 @@ tape('XNumber Constructors', (test) => {

test.equal(n(0, 2, 'π').toString(), '0');
test.equal(n(10000, 20000).toString(), '10k/20k');
test.equal(n(10000, 20000).toString(), '10000/20000');
test.end();

@@ -110,8 +111,34 @@ });

test.equal(dec(0.33), '33/100');
test.equal(dec(0.333), '1/3');
test.equal(dec(0.0333), '1/30');
test.equal(dec(0.333), '333/1000');
test.equal(dec(0.333333333333), '1/3');
test.equal(dec(0.0333333333333), '1/30');
test.equal(dec(0.05), '1/20');
test.equal(dec(0.04761904762), '1/21');
test.equal(dec(-5), '–5');
test.equal(dec(0.333333333333), '1/3');
test.equal(dec(-0.333333333333), '–1/3');
test.equal(dec(0.999999999999), '1');
test.equal(dec(0.833333333333), '5/6');
test.equal(dec(0.171717171717), '17/99');
test.end();
});
tape('Mixed numbers', (test) => {
test.deepEqual(expr(1.999999999999, 'mixed'), '2');
test.deepEqual(expr(0.333333333333, 'mixed'), '1/3');
test.deepEqual(expr(1.333333333333, 'mixed'), '1 1/3');
test.deepEqual(expr(-1.333333333333, 'mixed'), '–1 1/3');
test.end();
});
tape('Scientific notation', (test) => {
test.deepEqual(expr(0.000002, 'scientific'), '2 × 10^(-6)');
test.deepEqual(expr(0.00012, 'scientific'), '1.2 × 10^(-4)');
test.deepEqual(expr(1234.3, 'scientific'), '"1,234"');
test.deepEqual(expr(12343.2, 'decimal'), '"12.3k"');
test.deepEqual(expr(12343.2, 'scientific'), '1.234 × 10^4');
test.deepEqual(expr(123432.1, 'scientific'), '1.234 × 10^5');
test.end();
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc