> For the complete documentation index, see [llms.txt](https://lochiwei.gitbook.io/web/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lochiwei.gitbook.io/web/js/val/prim/num/number+ext.md).

# Number+ext

🚧 under construction

[JS](/web/js.md) ⟩ [value](/web/js/val.md) ⟩ [primitive](/web/js/val/prim.md) ⟩ [number](/web/js/val/prim/num.md) ⟩ +ext

{% hint style="success" %}
extend <mark style="color:yellow;">**Number**</mark> with custom methods.

:point\_right: [custom](/web/appendix/custom.md)
{% endhint %}

{% tabs %}
{% tab title="💾 程式" %}

* replit ⟩ [Number extension (2)](https://replit.com/@pegasusroe/Number-extension-2#ext/Number_ext.js)

```javascript
// 2023.03.04 - 06:50 (+) Quaternion, n.asQuaternion
// 2023.03.01 - 13:30 (/) isEqualTo -> equal, Number.EPSILON -> 1e-10
//                    (-) areEqualNumbers()
//                    (+) .round()
// 2023.01.20 - 16:45 (+) deg
// 2023.01.20 - 11:47 (+) .clamp(), .isEqualTo (*first recorded)
// -----------------------------------------------------

const { Random } = require('./Random.js');
const { sqrt, abs } = Math;
const {log} = console;

// deg
const deg = Math.PI / 180;

// ⭐ Number+ext
// -----------------------------------------------------
//   (constants)
// - deg                // degree = PI/180
// -----------------------------------------------------
//   (quaternion)
// 🔸 n.asQuaternion
//
// -----------------------------------------------------
//   (integer)
// 🔸 n.isEven
// 🔸 n.isOdd
// 🔸 n.binary
// 🔸 n.octal
// 🔸 n.hex
// 🔹 n.toBinary({prefix:, pad: {length:, char:}})
// 🔹 n.toOctal({prefix:, pad: {length:, char:})
// 🔹 n.toHex({prefix:, pad: {length:, char:})
// -----------------------------------------------------
// 🔹 .clamp()
// 🔹 .equal()
// 🔹 .round()
//
Object.defineProperties(Number.prototype, {

    // ---------------
    // quaternion
    // ---------------

    // 🔸 n.asQuaternion
    asQuaternion: {
        get() { return new Quaternion(this) },
    },

    // ---------------
    // integer
    // ---------------

    // 🔸 n.isEven
    isEven: {
        get() { return this % 2 === 0 },
    },

    // 🔸 n.isOdd
    isOdd: {
        get() { return Math.abs(this % 2) === 1 },
    },

    // 🔸 n.binary
    binary: {
        get() { return this.toString(2) },
    },

    // 🔸 n.octal
    octal: {
        get() { return this.toString(8) },
    },

    // 🔸 n.hex
    hex: {
        get() { return this.toString(16).toUpperCase() },
    },

    // 🔹 n.toBinary({prefix:, pad: {length:, char:}})
    toBinary: {
        value: formatFunc('0b', 2),
    },

    // 🔹 n.toOctal({prefix:, pad: {length:, char:}})
    toOctal: {
        value: formatFunc('0o', 8),
    },

    // 🔹 n.toHex({prefix:, pad: {length:, char:}})
    toHex: {
        value: formatFunc('0x', 16),
    },

    // ---------------
    // range
    // ---------------

    // 🔹 .clamp(min, max)
    clamp: {
        value: function(min, max){
            if (min > max) [min, max] = [max, min];
            return Math.min(max, Math.max(min, this));
        },
    },

    // 🔹 .equal()
    equal: {
        value: function(y, {threshold=1e-10}={}){
            return abs(this - y) < threshold; 
        },
    },

    // 🔹 .round()
    round: {
        value: function(places){
            return +this.toFixed(places); 
        },
    },

});

// ⭐ helpers
// -----------------------------------------------------

// general format function
function formatFunc(pre, base) {
    return function format({
        prefix = pre,
        pad = { length: 0, char: '0' },
    } = {}) {
        // ⭐ `pad` might be overridden, better destructure again
        const { length = 0, char = '0' } = pad;
        return prefix + 
            this.toString(base).padStart(length, char).toUpperCase();
    }
}

// 2023.03.04 - 07:19 (+) .scalarPart, .vectorPart
// 2023.03.04 - 06:50 (•) -------- merged into "Number+ext" --------
// 2023.03.03 - 17:08 (+) .dot, .cross
// 2023.03.01 - 13:42 (+) .randomInt, .randomFloat, .toString
// 2023.02.24 - 15:33 (+) static members, .conjugate, .norm, .inverse, ...
// 2023.02.24 - 13:35 (•) first draft (by chatGPT)
// -------------------------------------------------

// ⭐ Quaternion
// --------------------------------------------------
// - Quaternion.zero, .one, .i, .j, .k
// - Quaternion.randomInt()
// - Quaternion.randomFloat()
// --------------------------------------------------
// - .scalarPart, vectorPart
// - .conjugate
// - .normSquare
// - .norm
// - .inverse
// --------------------------------------------------
// - .add()            p + q
// - .subtract()       p - q
// - .multiply()       pq
// - .divide()         p/q = p q^(-1)
// - .scale()          kp
// - .dot()            p • q
// - .cross()          p x q
// --------------------------------------------------
// - .distance()       |p-q|
// - .equal()          p == q
//
class Quaternion {

    // i, j, k
    static get zero() { return new Quaternion(0, 0, 0, 0) }
    static get one() { return new Quaternion(1, 0, 0, 0) }
    static get i() { return new Quaternion(0, 1, 0, 0) }
    static get j() { return new Quaternion(0, 0, 1, 0) }
    static get k() { return new Quaternion(0, 0, 0, 1) }

    // init
    constructor(a=0, b=0, c=0, d=0) {
        this.a = a;
        this.b = b;
        this.c = c;
        this.d = d;
    }

    // ----------------------
    //     static methods
    // ----------------------

    // Quaternion.randomInt()
    static randomInt(min, max) {
        return new Quaternion(
            Random.int(min, max),
            Random.int(min, max),
            Random.int(min, max),
            Random.int(min, max),
        )
    }

    // Quaternion.randomFloat()
    static randomFloat(min, max) {
        return new Quaternion(
            Random.float(min, max),
            Random.float(min, max),
            Random.float(min, max),
            Random.float(min, max),
        )
    }


    // ----------------------
    //     getters
    // ----------------------

    // scalarPart
    get scalarPart() {
        return this.a;
    }

    // vectorPart
    get vectorPart() {
        const { b, c, d } = this;
        return new Quaternion(0, b, c, d);
    }

    // conjugate
    get conjugate() {
        const { a, b, c, d } = this;
        return new Quaternion(a, -b, -c, -d);
    }

    // inverse (reciprocal)
    get inverse() {
        const n = this.normSquare;
        return this.conjugate.scale(1 / n);
    }

    // |p|^2 = p • p
    get normSquare() {
        return this.dot(this);
    }

    // norm
    get norm() {
        const { a, b, c, d } = this;
        return sqrt(this.normSquare);
    }

    // ---------------------------
    //   ⭐ + - * / operations 
    // ---------------------------

    add(q) {
        const { a, b, c, d } = this;
        return new Quaternion(
            a + q.a, b + q.b, c + q.c, d + q.d
        );
    }

    subtract(q) {
        const { a, b, c, d } = this;
        return new Quaternion(
            a - q.a, b - q.b, c - q.c, d - q.d
        );
    }

    multiply(q) {
        const { a, b, c, d } = this;
        return new Quaternion(
            a * q.a - b * q.b - c * q.c - d * q.d,
            a * q.b + b * q.a + c * q.d - d * q.c,
            a * q.c - b * q.d + c * q.a + d * q.b,
            a * q.d + b * q.c - c * q.b + d * q.a,
        );
    }

    // p/q = p q^(-1)
    // ❗ note: p q^(-1) !== q^(-1) p
    divide(q) {
        return this.multiply(q.inverse);
    }

    // kq
    scale(k) {
        const { a, b, c, d } = this;
        return new Quaternion(k * a, k * b, k * c, k * d);
    }

    // p • q
    dot(q) {
        const { a, b, c, d } = q;
        return this.a * a + this.b * b + this.c * c + this.d * d;
    }

    // p x q = (p • q) - bar(p) q
    cross(q) {
        return this.dot(q).asQuaternion.subtract(
            this.conjugate.multiply(q)
        );
    }

    // ---------------------------
    //   ⭐ helpers 
    // ---------------------------

    // distance
    distance(q) {
        return this.subtract(q).norm;
    }

    // equal
    equal(q, {threshold=1e-10}={}) {
        return this.distance(q).equal(0, {threshold});
    }

    // toString
    toString(places=3) {
        const { a, b, c, d } = this;
        return `(${a.round(places)},${b.round(places)},${c.round(places)},${d.round(places)})`;
    }
}


// ⭐ CommonJS export
module.exports = {
    Quaternion,
    deg,
};


// export {deg};    // ES module export
```

{% endtab %}

{% tab title="💈範例" %}

```javascript
'use strict';                // ⭐ toggle sloppy/strict mode
const { log } = console;

// ⭐ import
const _Number = require('./ext/Number+ext.js');      // Number+ext
// ---------------------------------------------------------------------------

const e = Number.EPSILON;         // 2^(-52)    (very small)
const BIG = Math.pow(10, 300);    // 10^300     (very big)

// ✅ log expressions that never throw
// ---------------------------------------------------------------------------
;[
    // without prefix
    (45).binary,        // '101101'
    (88).octal,         // '130'
    (123).hex,          // '7B'

    // with prefix
    (123).toBinary(),   // '0b1111011'
    (123).toOctal(),    // '0o173'
    (123).toHex(),      // '0x7B'
    
    // with options
    (123).toHex({pad: {length: 4}}),               // '0x007B'
    (123).toHex({pad: {char: '_'}}),               // '0x7B'    (lenth = 0, by default)
    (123).toHex({pad: {char: '_', length: 6}}),    // '0x____7B'
    (123).toHex({prefix: '#', pad: {length: 3}}),  // '#07B'

    // equality
    Number.EPSILON,               //  2.220446049250313e-16 = 2^(-52)
    
    (0.1 + 0.2) - 0.3,            //  5.551115123125783e-17 < e
    0.1 + 0.2 === 0.3,            // ❗️ false (should be true)
    (0.1 + 0.2).isEqualTo(0.3),   // true

    1.5 + e > 1.5,                // true
    2.5 + e > 2.5,                // ❗️ false  (should be true)
    BIG + 1 > BIG,                // ❗️ false  (should be true)   
    BIG + 1 === BIG,              // ❗️ true  (should be false)

    // test threshold
    (0.1).isEqualTo(0.11, {threshold: 0.05}),    // true
    
].forEach(x => log(x));
```

{% endtab %}

{% tab title="👥 相關" %}

* [Number.EPSILON](/web/js/val/prim/num/special/number.epsilon.md)
* [sloppy equality (==)](/web/js/grammar/op/relational/equal/sloppy-equality.md)
* [strict equality (===)](/web/js/grammar/op/relational/equal/strict-equality.md)
  {% endtab %}

{% tab title="⬇️ 應用" %}

* [Vector](/web/appendix/custom/class/vector.md) - use `.isEqualTo()`.
* [Canvas+ext](/web/browser/canvas/+ext.md) - use `deg`.
* [grapheme cluster](/web/js/val/prim/str/unicode/grapheme-cluster.md)
* [Quaternion](/web/js/val/prim/num/quaternion.md)
  {% endtab %}

{% tab title="📗 參考" %}

* [ ] JavaScript Data Structures and Algorithms (p.21)
* [ ] [JavaScript: The Definitive Guide](/web/master/ref/javascript-the-definitive-guide.md) ⟩ 3.9.2 Explicit Conversions
  {% endtab %}

{% tab title="📘 手冊" %}

* [parseInt()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt) - global function
* [parseFloat()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat)
* [Number.parseInt()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseInt) === parseInt()
* [Number.parseFloat()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat) === parseFloat()
  {% endtab %}

{% tab title="🗣 討論" %}

* [How to round to at most 2 decimal places, if necessary](https://stackoverflow.com/a/28728643/7185426)
  {% endtab %}
  {% endtabs %}

## History

{% tabs %}
{% tab title="idx" %}

```
0 (•) first version
1 (+) .clamp(), .isEqualTo()
2 (/) .isEqualTo() renamed to .equal()
3 (/) Number.EPSILON -> 1e-10
```

{% endtab %}

{% tab title="0" %}

```javascript
// ----------------------------------
//    Number.prototype extension
// ----------------------------------
// 🔸 n.isEven
// 🔸 n.isOdd
// 🔸 n.binary
// 🔸 n.octal
// 🔸 n.hex
// 🔹 n.toBinary({prefix:, pad: {length:, char:}})
// 🔹 n.toOctal({prefix:, pad: {length:, char:})
// 🔹 n.toHex({prefix:, pad: {length:, char:})

// ⭐ multiple of
Object.defineProperties(Number.prototype, {

    // 🔸 n.isEven
    isEven: {
        get() { return this % 2 === 0 },
    },

    // 🔸 n.isOdd
    isOdd: {
        get() { return Math.abs(this % 2) === 1 },
    },

    // 🔸 n.binary
    binary: {
        get() { return this.toString(2) },
    },

    // 🔸 n.octal
    octal: {
        get() { return this.toString(8) },
    },

    // 🔸 n.hex
    hex: {
        get() { return this.toString(16).toUpperCase() },
    },

    // 🔹 n.toBinary({prefix:, pad: {length:, char:}})
    toBinary: {
        value: formatFunc('0b', 2),
    },

    // 🔹 n.toOctal({prefix:, pad: {length:, char:}})
    toOctal: {
        value: formatFunc('0o', 8),
    },

    // 🔹 n.toHex({prefix:, pad: {length:, char:}})
    toHex: {
        value: formatFunc('0x', 16),
    },

});

// ⭐ general function
function formatFunc(pre, base) {
    return function format({
        prefix = pre,
        pad = { length: 0, char: '0' },
    } = {}) {
        // ⭐ `pad` might be overridden, better destructure again
        const { length = 0, char = '0' } = pad;
        return prefix + 
            this.toString(base).padStart(length, char).toUpperCase();
    }
}

// CommonJS export
module.exports = {};
```

{% endtab %}

{% tab title="1" %}

```javascript
// 2023.01.20 - 11:47 (+) .clamp(), .isEqualTo
// -----------------------------------------------------

// ⭐ Number+ext
// -----------------------------------------------------
//   (integer-related)
// 🔸 n.isEven
// 🔸 n.isOdd
// 🔸 n.binary
// 🔸 n.octal
// 🔸 n.hex
// 🔹 n.toBinary({prefix:, pad: {length:, char:}})
// 🔹 n.toOctal({prefix:, pad: {length:, char:})
// 🔹 n.toHex({prefix:, pad: {length:, char:})
// -----------------------------------------------------
// 🔹 .clamp()
// 🔹 .isEqualTo()
//
Object.defineProperties(Number.prototype, {

    // ---------------
    // integer-related
    // ---------------

    // 🔸 n.isEven
    isEven: {
        get() { return this % 2 === 0 },
    },

    // 🔸 n.isOdd
    isOdd: {
        get() { return Math.abs(this % 2) === 1 },
    },

    // 🔸 n.binary
    binary: {
        get() { return this.toString(2) },
    },

    // 🔸 n.octal
    octal: {
        get() { return this.toString(8) },
    },

    // 🔸 n.hex
    hex: {
        get() { return this.toString(16).toUpperCase() },
    },

    // 🔹 n.toBinary({prefix:, pad: {length:, char:}})
    toBinary: {
        value: formatFunc('0b', 2),
    },

    // 🔹 n.toOctal({prefix:, pad: {length:, char:}})
    toOctal: {
        value: formatFunc('0o', 8),
    },

    // 🔹 n.toHex({prefix:, pad: {length:, char:}})
    toHex: {
        value: formatFunc('0x', 16),
    },

    // ---------------
    // range
    // ---------------

    // 🔹 .clamp(min, max)
    clamp: {
        value: function(min, max){
            if (min > max) [min, max] = [max, min];
            return Math.min(max, Math.max(min, this));
        },
    },

    // 🔹 .isEqualTo()
    isEqualTo: {
        value: function(n, {threshold = Number.EPSILON}={}){
            return areEqualNumbers(this, n, {threshold});
        },
    },

});

// ⭐ helpers
// -----------------------------------------------------

// general format function
function formatFunc(pre, base) {
    return function format({
        prefix = pre,
        pad = { length: 0, char: '0' },
    } = {}) {
        // ⭐ `pad` might be overridden, better destructure again
        const { length = 0, char = '0' } = pad;
        return prefix + 
            this.toString(base).padStart(length, char).toUpperCase();
    }
}

// areEqualNumbers(x, y {threshold})
function areEqualNumbers(x, y, {threshold = Number.EPSILON}={}) { 
    return Math.abs(x - y) < threshold; 
}

// ⭐ export
module.exports = {};    // CommonJS export
// export default {};    // ES module export
```

{% endtab %}

{% tab title="2" %}

```javascript
// 2023.01.20 - 16:45 (+) deg
// 2023.01.20 - 11:47 (+) .clamp(), .isEqualTo (*first recorded)
// -----------------------------------------------------

// ⭐ Number+ext
// -----------------------------------------------------
//   (constants)
// - deg                // degree = PI/180
// -----------------------------------------------------
//   (integer-related)
// 🔸 n.isEven
// 🔸 n.isOdd
// 🔸 n.binary
// 🔸 n.octal
// 🔸 n.hex
// 🔹 n.toBinary({prefix:, pad: {length:, char:}})
// 🔹 n.toOctal({prefix:, pad: {length:, char:})
// 🔹 n.toHex({prefix:, pad: {length:, char:})
// -----------------------------------------------------
// 🔹 .clamp()
// 🔹 .isEqualTo()
//
Object.defineProperties(Number.prototype, {

    // ---------------
    // integer-related
    // ---------------

    // 🔸 n.isEven
    isEven: {
        get() { return this % 2 === 0 },
    },

    // 🔸 n.isOdd
    isOdd: {
        get() { return Math.abs(this % 2) === 1 },
    },

    // 🔸 n.binary
    binary: {
        get() { return this.toString(2) },
    },

    // 🔸 n.octal
    octal: {
        get() { return this.toString(8) },
    },

    // 🔸 n.hex
    hex: {
        get() { return this.toString(16).toUpperCase() },
    },

    // 🔹 n.toBinary({prefix:, pad: {length:, char:}})
    toBinary: {
        value: formatFunc('0b', 2),
    },

    // 🔹 n.toOctal({prefix:, pad: {length:, char:}})
    toOctal: {
        value: formatFunc('0o', 8),
    },

    // 🔹 n.toHex({prefix:, pad: {length:, char:}})
    toHex: {
        value: formatFunc('0x', 16),
    },

    // ---------------
    // range
    // ---------------

    // 🔹 .clamp(min, max)
    clamp: {
        value: function(min, max){
            if (min > max) [min, max] = [max, min];
            return Math.min(max, Math.max(min, this));
        },
    },

    // 🔹 .isEqualTo()
    isEqualTo: {
        value: function(n, {threshold = Number.EPSILON}={}){
            return areEqualNumbers(this, n, {threshold});
        },
    },

});

// ⭐ helpers
// -----------------------------------------------------

// general format function
function formatFunc(pre, base) {
    return function format({
        prefix = pre,
        pad = { length: 0, char: '0' },
    } = {}) {
        // ⭐ `pad` might be overridden, better destructure again
        const { length = 0, char = '0' } = pad;
        return prefix + 
            this.toString(base).padStart(length, char).toUpperCase();
    }
}

// areEqualNumbers(x, y {threshold})
function areEqualNumbers(x, y, {threshold = Number.EPSILON}={}) { 
    return Math.abs(x - y) < threshold; 
}

// deg
const deg = Math.PI / 180;

// ⭐ export
module.exports = {deg};    // CommonJS export
// export {deg};    // ES module export
```

{% endtab %}

{% tab title="3" %}

```javascript
// 2023.03.01 - 13:30 (/) isEqualTo -> equal, Number.EPSILON -> 1e-10
//                    (-) areEqualNumbers()
//                    (+) .round()
// 2023.01.20 - 16:45 (+) deg
// 2023.01.20 - 11:47 (+) .clamp(), .isEqualTo (*first recorded)
// -----------------------------------------------------

const {abs} = Math;
const {log} = console;

// deg
const deg = Math.PI / 180;

// ⭐ Number+ext
// -----------------------------------------------------
//   (constants)
// - deg                // degree = PI/180
// -----------------------------------------------------
//   (integer-related)
// 🔸 n.isEven
// 🔸 n.isOdd
// 🔸 n.binary
// 🔸 n.octal
// 🔸 n.hex
// 🔹 n.toBinary({prefix:, pad: {length:, char:}})
// 🔹 n.toOctal({prefix:, pad: {length:, char:})
// 🔹 n.toHex({prefix:, pad: {length:, char:})
// -----------------------------------------------------
// 🔹 .clamp()
// 🔹 .equal()
//
Object.defineProperties(Number.prototype, {

    // ---------------
    // integer-related
    // ---------------

    // 🔸 n.isEven
    isEven: {
        get() { return this % 2 === 0 },
    },

    // 🔸 n.isOdd
    isOdd: {
        get() { return Math.abs(this % 2) === 1 },
    },

    // 🔸 n.binary
    binary: {
        get() { return this.toString(2) },
    },

    // 🔸 n.octal
    octal: {
        get() { return this.toString(8) },
    },

    // 🔸 n.hex
    hex: {
        get() { return this.toString(16).toUpperCase() },
    },

    // 🔹 n.toBinary({prefix:, pad: {length:, char:}})
    toBinary: {
        value: formatFunc('0b', 2),
    },

    // 🔹 n.toOctal({prefix:, pad: {length:, char:}})
    toOctal: {
        value: formatFunc('0o', 8),
    },

    // 🔹 n.toHex({prefix:, pad: {length:, char:}})
    toHex: {
        value: formatFunc('0x', 16),
    },

    // ---------------
    // range
    // ---------------

    // 🔹 .clamp(min, max)
    clamp: {
        value: function(min, max){
            if (min > max) [min, max] = [max, min];
            return Math.min(max, Math.max(min, this));
        },
    },

    // 🔹 .equal()
    equal: {
        value: function(y, {threshold=1e-10}={}){
            return abs(this - y) < threshold; 
        },
    },

    // 🔹 .round()
    round: {
        value: function(places){
            return +this.toFixed(places); 
        },
    },

});

// ⭐ helpers
// -----------------------------------------------------

// general format function
function formatFunc(pre, base) {
    return function format({
        prefix = pre,
        pad = { length: 0, char: '0' },
    } = {}) {
        // ⭐ `pad` might be overridden, better destructure again
        const { length = 0, char = '0' } = pad;
        return prefix + 
            this.toString(base).padStart(length, char).toUpperCase();
    }
}



// ⭐ export
module.exports = {deg};    // CommonJS export
// export {deg};    // ES module export
```

{% endtab %}
{% endtabs %}
