-1

I want to see if a number is a multiple of π. So ideally it would behave exactly like an ordinary number, but with extra functionality:

>>> pi
3.14...
>>> pi.isMultipleOfPi
true
>>> b = pi * 5
>>> b.isMultipleOfPi
true

With wishful thinking

>>> c = pi + 1
>>> c.isMultipleOfPi
false
>>> d = pi + 2 * pi
>>> d.isMultipleOfPi
true

Is there a way to achieve something like that in Javascript? I think this would be pretty straight forward in Python, but there seems to be no way to create overloads for addition/multiplication etc. in JS. If there were, I could simply extent the Number class.

Setting something on Number.prototype = function also doesn't seem to work as it sets it for the entire class and not on an instance.

KarloSpacapan
  • 173
  • 2
  • 16
  • "*Is there a way to achieve something like that in Javascript?*" I'm sure there is; what have you tried? Where are you getting stuck? Questions in the form of "*is X possible?*" or "*is there a way to do X?*" are generally not good fits for Stack Overflow's Q&A format. See [ask] – esqew Feb 22 '22 at 15:11
  • 1
    You could add a non-enumerable getter to `Number.prototype`, but I don't see any benefit over just passing the number to a function. – Felix Kling Feb 22 '22 at 15:12
  • Any reason why you can't just use the result of `number % Math.PI === 0` to verify if something is a multiple of Pi? Am I missing something? – esqew Feb 22 '22 at 15:12
  • Is [this](https://melvingeorge.me/blog/extending-datatypes-javascript) any help? – Tommi Feb 22 '22 at 15:14
  • @esqew I have tried extending the number class, setting an attribute directly, setting on Number.prototype etc. After a lot of googling, I wasn't able to find a way to do it. Thus, I felt asking if is this even possible was valid. Sorry about that. – KarloSpacapan Feb 22 '22 at 15:14
  • 2
    So you want to set this on just **one specific number**? The main issue with that is that 99.9999999999998% of all numbers you work with in JavaScript are primitives, not objects, and so they don't have any properties (the ones they seem to have only come from `Number.prototype`, which the JavaScript engine will use if you try to access a property on a number primitive. You could instead make an accessor property on `Number.prototype` that determines if the number it's called on is a multiple of PI and returns the right flag, that would give you the syntax you showed, but on all numbers. – T.J. Crowder Feb 22 '22 at 15:15
  • @esqew The problem is that `Math.sin(Math.PI)` does not return `0`. I would wish to define my own class `π`, which would check is this is indeed a multiple of `π` and return exact value from a table. However, I still need it to behave as a normal number in every other situation. – KarloSpacapan Feb 22 '22 at 15:16
  • @Tommi Sadly, I don't see how that can help. Because if I do an arithmetic operation on an extended `Number`, I get a normal `Number`, and not the extended class. – KarloSpacapan Feb 22 '22 at 15:18
  • I think that other than the ideas in the answers below, you're trying to do something that simply cannot be done in JavaScript. Subclassing the Number class won't do you much good, as virtually all code that does numeric computation involves number primitives, not Number instances. You'll probably a lot happier retracing your decisions and finding another approach to solving whatever actual problem you have. – Pointy Feb 22 '22 at 15:25
  • @T.J.Crowder How would that accessor property look like? Because `Number.prototype = function(){ return self % Math.PI ~ 0 }` is not what I'm looking for. I would wish to distinguish between `3.14` vs `3.14` with `isMultipleOfPi` property. The former would be literally a float with 2 significant digits, but the 2nd one would behave in exactly the same way for arithmetic operations, but I would be able to check if it's actually a multiple of `π` in some special cases. – KarloSpacapan Feb 22 '22 at 15:25
  • 1
    Yea, you can't do that. Values of type `number` are not instances of any "class". – Pointy Feb 22 '22 at 15:26
  • 1
    @KarloSpacapan - The only way to do that is to use `Number` objects rather than primitives, as I describe in my answer. – T.J. Crowder Feb 22 '22 at 15:29

2 Answers2

2

You cannot add properties to specific number values; they are not objects and thus they can't have any properties. You can add properties to the Number prototype, as follows:

Object.defineProperty(Number.prototype, "isMultipleOfPi", {
    get: function() {
      return this.valueOf() % Math.PI === 0;
    }
});

const pix2 = Math.PI * 2;
console.log(pix2.isMultipleOfPi);
console.log((7).isMultipleOfPi);

That adds a Number prototype property defined with a "get" function so that the code you posted will work as you expect. The method will be available for all numbers in your program, which doesn't seem like a bad thing if you want that feature at all.

Note also that extending built-in prototype objects is frowned upon by many people. In my example, I extended the prototype in a way that ameliorates some of the problems with extended prototypes.

Pointy
  • 405,095
  • 59
  • 585
  • 614
2

I thought at first you wanted to do this for all numbers, which is answered here.

But if you only want to do this for one specific number, you can, but you probably won't want to. 99.9999999999998% of all numbers you work with in JavaScript are primitives, not objects, and so they don't have any properties (the ones they seem to have only come from Number.prototype [or, in turn, from Object.primitive], which the JavaScript engine will use if you try to access a property on a number primitive).

You could make a number object instead, and put the property on that:

const c = new Number(Math.PI);
c.isMultipleOfPI = true;
console.log(c);
console.log(String(c));
console.log(c.isMultipleOfPI);

The problem with that, though, is demonstrated by the console.log(c); line above. With Chrome's devtools (and perhaps others), you see this:

{
    isMultipleOfPI: true,
}

rather than 3.141592653589793 as you might be expecting.

The reason is that c is an object, not a primitive, and some things that use it (such as console.log) may treat it primarily as an object rather than primarily as a number.

Math and string concatenation work, though:

const c = new Number(Math.PI);
c.isMultipleOfPI = true;
console.log(c * 2);
console.log(c - 2);
console.log(c + 2);
console.log(c / 2);
console.log("The number is " + c);

Just beware that Number instances (objects) are objects, and that very, very little code is written explicitly to work with them properly. Most code using numbers expects number primitives.

Alternatively, you could make an accessor property on Number.prototype (Pointy shows you how) that determines if the number it's called on is a multiple of PI and returns the right flag. That would give you the syntax you showed, but on all numbers. But it is a function call, so accessing the property works it out each time. Just having a isMultipleOfPI(num) function might be clearer.

In a comment you've written:

Because Number.prototype = function(){ return self % Math.PI ~ 0 } is not what I'm looking for. I would wish to distinguish between 3.14 vs 3.14 with isMultipleOfPi property.

The only way to do that is to use Number objects rather than number primitives, as described above.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thank you, this mostly works! Sadly, for my use case I was really hoping there would be a way to control addition/subtraction. – KarloSpacapan Feb 22 '22 at 15:38
  • 1
    @KarloSpacapan - Afraid not. :-) You can get *close* with [`valueOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf), but JavaScript fundamentally doesn't have operator overloading. – T.J. Crowder Feb 22 '22 at 15:53