backspaces
backspaces

Reputation: 3942

JavaScript Math.floor: how guarantee number will round down?

I want to normalize an array so that each value is
in [0-1) .. i.e. "the max will never be 1 but the min can be 0."

This is not unlike the random function returning numbers in the same range.

While looking at this, I found that .99999999999999999===1 is true!

Ditto (1-Number.MIN_VALUE) === 1 But Math.ceil(Number.MIN_VALUE) is 1, as it should be.

Some others: Math.floor(.999999999999) is 0
while Math.floor(.99999999999999999) is 1

OK so there are rounding problems in JS.

Is there any way I can normalize a set of numbers to lie in the range [0,1)?

Upvotes: 3

Views: 1855

Answers (3)

Eric Postpischil
Eric Postpischil

Reputation: 222372

It may help to examine the steps that JavaScript performs of each of your expressions.

In .99999999999999999===1:

  1. The source text .99999999999999999 is converted to a Number. The closest Number is 1, so that is the result. (The next closest Number is 0.99999999999999988897769753748434595763683319091796875, which is 1–2–53.)
  2. Then 1 is compared to 1. The result is true.

In (1-Number.MIN_VALUE) === 1:

  1. Number.MIN_VALUE is 2–1074, about 5e–304.
  2. 1–2–1074 is extremely close to one. The exact value cannot be represented as a Number, so the nearest value is used. Again, the nearest value is 1.
  3. Then 1 is compared to 1. The result is true.

In Math.ceil(Number.MIN_VALUE):

  1. Number.MIN_VALUE is 2–1074, about 5e–304.
  2. The ceiling function of that value is 1.

In Math.floor(.999999999999):

  1. The source text .999999999999 is converted to a Number. The closest Number is 0.99999999999900002212172012150404043495655059814453125, so that is the result.
  2. The floor function of that value is 0.

In Math.floor(.99999999999999999):

  1. The source text .99999999999999999 is converted to a Number. The closest Number is 1, so that is the result.
  2. The floor function of 1 is 1.

There are only two surprising things here, at most. One is that the numerals in the source text are converted to internal Number values. But this should not be surprising. Of course text has to be converted to internal representations of numbers, and the Number type cannot perfectly store all the infinitely many numbers. So it has to round. And of course numbers very near 1 round to 1.

The other possibly surprising thing is that 1-Number.MIN_VALUE is 1. But this is actually the same issue: The exact result is not representable, but it is very near 1, so 1 is used.

The Math.floor function works correctly. It never introduces any error, and you do not have to do anything to guarantee that it will round down. It always does.

However, since you want to normalize numbers, it seems likely you are going to divide numbers at some point. When you divide, there may be rounding problems, because many results of division are not exactly representable, so they must be rounded.

However, that is a separate problem, and you have not given enough information in this question to address the specific calculations you plan to do. You should open a separate question for it.

Upvotes: 4

raina77ow
raina77ow

Reputation: 106385

Please understand one thing: this...

.999999999999999999

... is just a Number literal. Just as

.999999999999999998
.999999999999999997
.999999999999999996
...

... you see the pattern.

How JavaScript treats these literals is completely another story. And yes, this treatment is limited by the number of bits that can be used to store a Number value.

The number of possible floating point literals is infinite by definition - no matter how small is the range set for them. For example, take the ones shown above: how many of numbers very close to 1 you may express? Right, it's infinite: just keep appending 9 to the line.

But the container for each Number value is quite finite: it has 64 bits. That means, it can store 2^64 different values (Infinite, -Infinite and NaN among them) - and that's all.

You want to work with such literals anyway? Use Strings to store them, not Numbers - and some BigMath JS library (take your pick) to work with those values - as Strings, again.

But from your question it looks like you're not, as you talked about array of Numbers - Number values, that is. And in no way there can be .999999999999999999 stored there, as there is no such Number value in JavaScript.

Upvotes: 0

Jason Nichols
Jason Nichols

Reputation: 3760

Javascript will treat any number between 0.999999999999999994 and 1 as 1, so just subtract .000000000000000006.

Of course that's not as easy as it sounds, since .000000000000000006 is evaluated as 0 in Javascript, so you could do something like:

function trueFloor(x)
{
    x = x * 100;
    if(x > .0000000000000006)
        x = x - .0000000000000006;
    x = Math.floor(x/100);
    return x;
}

EDIT: Or at least you'd think you could. Apparently JS casts .99999999999999999 to 1 before passing it to a function, so you'd have to try something like:

trueFloor("0.99999999999999999")

function trueFloor(str)
{
    x=str.substring(0,9) + 0;
    return Math.floor(x); //=> 0
}

Not sure why you'd need that level of precision, but in theory, I guess it works. You can see a working fiddle here

As long as you cast your insanely precise float as a string, that's probably your best bet.

Upvotes: 0

Related Questions