Skip to content

Minor text edits #148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 30, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 51 additions & 51 deletions 1-js/05-data-types/02-number/article.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Numbers

All numbers in JavaScript are stored in 64-bit format [IEEE-754](http://en.wikipedia.org/wiki/IEEE_754-1985) also known as "double precision".
All numbers in JavaScript are stored in 64-bit format [IEEE-754](http://en.wikipedia.org/wiki/IEEE_754-1985), also known as "double precision".

Let's recap what we know about them and add a little bit more.
Let's recap and expand upon what we currently know about them.

## More ways to write a number

Imagine, we need to write a billion. The obvious way is:
Imagine we need to write 1 billion. The obvious way is:

```js
let billion = 1000000000;
```

But in real life we usually dislike writing many zeroes. It's easy to mistype. Also we are lazy. We will usually write something like `"1bn"` for a billion or `"7.3bn"` for 7 billions 300 millions. The similar is true for other big numbers.
But in real life we usually avoid writing a long string of zeroes as it's easy to mistype. Also, we are lazy. We will usually write something like `"1bn"` for a billion or `"7.3bn"` for 7 billion 300 million. The same is true for most large numbers.

In JavaScript, we can do almost the same by appending the letter `"e"` to the number and specifying the zeroes count:
In JavaScript, we shorten a number by appending the letter `"e"` to the number and specifying the zeroes count:

```js run
let billion = 1e9; // 1 billion, literally: 1 and 9 zeroes
Expand All @@ -36,7 +36,7 @@ Now let's write something very small. Say, 1 microsecond (one millionth of a sec
let ms = 0.000001;
```

Also the same `"e"` can help. If we'd like not to write down the zeroes explicitly, the same number is:
Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could say:

```js
let ms = 1e-6; // six zeroes to the left from 1
Expand All @@ -56,7 +56,7 @@ In other words, a negative number after `"e"` means a division by 1 with the giv

### Hex, binary and octal numbers

[Hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal) numbers are widely used in JavaScript: to represent colors, encode characters and for many other things. So there exists a short way to write them: `0x` and then the number.
[Hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal) numbers are widely used in JavaScript to represent colors, encode characters, and for many other things. So naturally, there exists a shorter way to write them: `0x` and then the number.

For instance:

Expand All @@ -65,7 +65,7 @@ alert( 0xff ); // 255
alert( 0xFF ); // 255 (the same, case doesn't matter)
```

Binary and octal numeral systems are rarely used, but also supported using `0b` and `0o` prefixes:
Binary and octal numeral systems are rarely used, but also supported using the `0b` and `0o` prefixes:


```js run
Expand All @@ -75,7 +75,7 @@ let b = 0o377; // octal form of 255
alert( a == b ); // true, the same number 255 at both sides
```

There are only 3 numeral systems with such support. For other numeral systems we should use function `parseInt` (later in this chapter).
There are only 3 numeral systems with such support. For other numeral systems, we should use the function `parseInt` (which we will see later in this chapter).

## toString(base)

Expand All @@ -91,7 +91,7 @@ alert( num.toString(2) ); // 11111111

The `base` can vary from `2` to `36`. By default it's `10`.

Most often use cases are:
Common use cases for this are:

- **base=16** is used for hex colors, character encodings etc, digits can be `0..9` or `A..F`.
- **base=2** is mostly for debugging bitwise operations, digits can be `0` or `1`.
Expand All @@ -111,9 +111,9 @@ Also could write `(123456).toString(36)`.

## Rounding

One of most often operations with numbers is the rounding.
One of the most used operations when working with numbers is rounding.

There are following built-in functions for rounding:
There are several built-in functions for rounding:

`Math.floor`
: Rounds down: `3.1` becomes `3`, and `-1.1` becomes `-2`.
Expand All @@ -125,7 +125,7 @@ There are following built-in functions for rounding:
: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4` and `-1.1` becomes `-1`.

`Math.trunc` (not supported by Internet Explorer)
: Removes the decimal part: `3.1` becomes `3`, `-1.1` becomes `-1`.
: Removes anything after the decimal point without rounding: `3.1` becomes `3`, `-1.1` becomes `-1`.

Here's the table to summarize the differences between them:

Expand All @@ -137,15 +137,15 @@ Here's the table to summarize the differences between them:
|`-1.6`| `-2` | `-1` | `-2` | `-1` |


These functions cover all possible ways to deal with the decimal part as a whole. But what if we'd like to round the number to `n-th` digit after the point?
These functions cover all of the possible ways to deal with the decimal part of a number. But what if we'd like to round the number to `n-th` digit after the decimal?

For instance, we have `1.2345` and want to round it to 2 digits, getting only `1.23`.

There are two ways to do so.
There are two ways to do so:

1. Multiply-and-divide.

For instance, to round the number to the 2nd digit after the point, we can multiply the number by `100`, call the rounding function and then divide back.
For example, to round the number to the 2nd digit after the decimal, we can multiply the number by `100`, call the rounding function and then divide it back.
```js run
let num = 1.23456;

Expand All @@ -159,14 +159,14 @@ There are two ways to do so.
alert( num.toFixed(1) ); // "12.3"
```

The rounding goes to the nearest value, similar to `Math.round`:
This rounds up or down to the nearest value, similar to `Math.round`:

```js run
let num = 12.36;
alert( num.toFixed(1) ); // "12.4"
```

Please note that result of `toFixed` is a string. If the decimal part is shorter than required, zeroes are appended to its end:
Please note that result of `toFixed` is a string. If the decimal part is shorter than required, zeroes are appended to the end:

```js run
let num = 12.34;
Expand All @@ -177,23 +177,23 @@ There are two ways to do so.

## Imprecise calculations

Internally, a number is represented in 64-bit format [IEEE-754](http://en.wikipedia.org/wiki/IEEE_754-1985). So, there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point (they are zero for integer numbers) and 1 bit for the sign.
Internally, a number is represented in 64-bit format [IEEE-754](http://en.wikipedia.org/wiki/IEEE_754-1985), so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point (they are zero for integer numbers), and 1 bit is for the sign.

If a number is too big, it would overflow the 64-bit storage, potentially giving an infinity:

```js run
alert( 1e500 ); // Infinity
```

But what may be a little bit more obvious, but happens much often is the loss of precision.
What may be a little less obvious, but happens quite often, is the loss of precision.

Consider this (falsy!) test:

```js run
alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!*
```

Yes, indeed, if we check whether the sum of `0.1` and `0.2` is `0.3`, we get `false`.
That's right, if we check whether the sum of `0.1` and `0.2` is `0.3`, we get `false`.

Strange! What is it then if not `0.3`?

Expand All @@ -203,24 +203,24 @@ alert( 0.1 + 0.2 ); // 0.30000000000000004

Ouch! There are more consequences than an incorrect comparison here. Imagine you're making an e-shopping site and the visitor puts `$0.10` and `$0.20` goods into his chart. The order total will be `$0.30000000000000004`. That would surprise anyone.

Why does it work like that?
But why does this happen?

A number is stored in memory in it's binary form, as a sequence of ones and zeroes. But fractions like `0.1`, `0.2` that look simple in the decimal numeric system are actually unending fractions in their binary form.
A number is stored in memory in its binary form, a sequence of ones and zeroes. But fractions like `0.1`, `0.2` that look simple in the decimal numeric system are actually unending fractions in their binary form.

In other words, what is `0.1`? It is one divided by ten `1/10`, one-tenth. In decimal numeral system such numbers are easily representable. Compare it to one-third: `1/3`. It becomes an endless fraction `0.33333(3)`.

So, division by powers `10` is guaranteed to look well in the decimal system, but the division by `3` is not. For the same reason, in the binary numeral system, the division by powers of `2` is guaranteed to look good, but `1/10` becomes an endless binary fraction.
So, division by powers `10` is guaranteed to work well in the decimal system, but division by `3` is not. For the same reason, in the binary numeral system, the division by powers of `2` is guaranteed to work, but `1/10` becomes an endless binary fraction.

There's just no way to store *exactly 0.1* or *exactly 0.2* in the binary system, just like there is no way to store one-third as a decimal fraction.
There's just no way to store *exactly 0.1* or *exactly 0.2* using the binary system, just like there is no way to store one-third as a decimal fraction.

The numeric format IEEE-754 solves that by storing the nearest possible number. There are rounding rules that normally don't allow us to see that "tiny precision loss", so the number shows up as `0.3`. But the loss still exists.
The numeric format IEEE-754 solves this by rounding to the nearest possible number. These rounding rules normally don't allow us to see that "tiny precision loss", so the number shows up as `0.3`. But beware, the loss still exists.

We can see it like this:
We can see this in action:
```js run
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
```

And when we sum two numbers, then their "precision losses" sum up.
And when we sum two numbers, their "precision losses" add up.

That's why `0.1 + 0.2` is not exactly `0.3`.

Expand All @@ -230,7 +230,7 @@ The same issue exists in many other programming languages.
PHP, Java, C, Perl, Ruby give exactly the same result, because they are based on the same numeric format.
```

Can we work around the problem? Sure, there's a number of ways:
Can we work around the problem? Sure, there're a number of ways:

1. We can round the result with the help of a method [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed):

Expand All @@ -239,22 +239,22 @@ Can we work around the problem? Sure, there's a number of ways:
alert( sum.toFixed(2) ); // 0.30
```

Please note that `toFixed` always returns a string. It ensures that it has 2 digits after the decimal point. That's actually convenient if we have an e-shopping and need to show `$0.30`. For other cases we can use the unary plus to coerce it into a number:
Please note that `toFixed` always returns a string. It ensures that it has 2 digits after the decimal point. That's actually convenient if we have an e-shopping and need to show `$0.30`. For other cases, we can use the unary plus to coerce it into a number:

```js run
let sum = 0.1 + 0.2;
alert( +sum.toFixed(2) ); // 0.3
```

2. We can temporarily turn numbers into integers for the maths and then go back. That would looks like this:
2. We can temporarily turn numbers into integers for the maths and then revert it back. It works like this:

```js run
alert( (0.1*10 + 0.2*10) / 10 ); // 0.3
```

It works, because when we get `0.1*10 = 1` and `0.2 * 10 = 2` then both numbers are integers, there's no precision loss for them.
This works because when we do `0.1*10 = 1` and `0.2 * 10 = 2` then both numbers become integers, and there's no precision loss.

3. If it's a shop, then the most radical solution would be to store all prices in cents. No fractions at all. But what if we apply a discount of 30%? In practice, totally evading fractions is rarely feasible, so the solutions listed above are here to help.
3. If we were dealing with a shop, then the most radical solution would be to store all prices in cents and use no fractions at all. But what if we apply a discount of 30%? In practice, totally evading fractions is rarely feasible, so the solutions above help avoid this pitfall.

````smart header="The funny thing"
Try running this:
Expand All @@ -264,15 +264,15 @@ Try running this:
alert( 9999999999999999 ); // shows 10000000000000000
```

The reason is the same: loss of precision. There are 64 bits for the number, 52 of them can be used to store digits, and that's not enough. So the least significant digits disappear.
This suffers from the same issue: a loss of precision. There are 64 bits for the number, 52 of them can be used to store digits, but that's not enough. So the least significant digits disappear.

JavaScript doesn't trigger an error in such case. It does the best to fit the number into the format. Unfortunately, the format is not big enough.
JavaScript doesn't trigger an error in such events. It does its best to fit the number into the desired format, but unfortunately, this format is not big enough.
````

```smart header="Two zeroes"
Another funny consequence of the internal representation is the existance of two zeroes: `0` and `-0`.
Another funny consequence of the internal representation of numbers is the existance of two zeroes: `0` and `-0`.

That's because a sign is represented by a single bit, so every number can be positive or negative, including the zero.
That's because a sign is represented by a single bit, so every number can be positive or negative, including a zero.

In most cases the distinction is unnoticeable, because operators are suited to treat them as the same.
```
Expand All @@ -281,22 +281,22 @@ In most cases the distinction is unnoticeable, because operators are suited to t

## Tests: isFinite and isNaN

Remember the two special numeric values?
Remember these two special numeric values?

- `Infinite` (and `-Infinite`) is a special numeric value that is greater (less) than anything.
- `NaN` represents an error.

They belong to the type `number`, but are not "normal" numbers, so there are special functions to check for them:


- `isNaN(value)` converts its argument to a number and then tests if for being `NaN`:
- `isNaN(value)` converts its argument to a number and then tests it for being `NaN`:

```js run
alert( isNaN(NaN) ); // true
alert( isNaN("str") ); // true
```

But do we need the function? Can we just use the comparison `=== NaN`? Sorry, but no. The value `NaN` is unique in that it does not equal anything including itself:
But do we need this function? Can't we just use the comparison `=== NaN`? Sorry, but the answer is no. The value `NaN` is unique in that it does not equal anything, including itself:

```js run
alert( NaN === NaN ); // false
Expand All @@ -310,7 +310,7 @@ They belong to the type `number`, but are not "normal" numbers, so there are spe
alert( isFinite(Infinity) ); // false, because a special value: Infinity
```

Sometimes `isFinite` is used to validate the string value for being a regular number:
Sometimes `isFinite` is used to validate whether a string value is a regular number:


```js run
Expand All @@ -320,7 +320,7 @@ let num = +prompt("Enter a number", '');
alert( isFinite(num) );
```

Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`.
Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`.

```smart header="Compare with `Object.is`"

Expand All @@ -337,29 +337,29 @@ This way of comparison is often used in JavaScript specification. When an intern

## parseInt and parseFloat

The numeric conversion using a plus `+` or `Number()` is strict. If a value is not exactly a number, it fails:
Numeric conversion using a plus `+` or `Number()` is strict. If a value is not exactly a number, it fails:

```js run
alert( +"100px" ); // NaN
```

The sole exception is spaces before and after the line, they are ignored.
The sole exception is spaces at the beginning or at the end of the string, as they are ignored.

But in real life we often have values in units, like `"100px"` or `"12pt"` in CSS. Also in many countries the currency symbol goes after the amount, so we have `"19€"` and would like to extract a numeric value out of that.

That's what `parseInt` and `parseFloat` are for.

They "read" a number from a string until they can. In case of an error, the gathered number is returned. Function `parseInt` reads an integer number, `parseFloat` reads any number:
They "read" a number from a string until they can. In case of an error, the gathered number is returned. The function `parseInt` returns an integer, whilst `parseFloat` will return a floating-point number:

```js run
alert( parseInt('100px') ); // 100
alert( parseFloat('12.5em') ); // 12.5

alert( parseInt('12.3') ); // 12, only integer part
alert( parseInt('12.3') ); // 12, only the integer part is returned
alert( parseFloat('12.3.4') ); // 12.3, the second point stops the reading
```

Of course, there are situations when `parseInt/parseFloat` return `NaN`. It happens when no digits could be read:
There are situations when `parseInt/parseFloat` will return `NaN`. It happens when no digits could be read:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why "will" ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of these make sense to me. They seem equivalent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could say 'will return' or 'returns'. Just 'return' reads wrong when read.


```js run
alert( parseInt('a123') ); // NaN, the first symbol stops the process
Expand Down Expand Up @@ -392,7 +392,7 @@ A few examples:
```

`Math.max(a, b, c...)` / `Math.min(a, b, c...)`
: Return the greatest/smallest from the arbitrary number of arguments.
: Returns the greatest/smallest from the arbitrary number of arguments.

```js run
alert( Math.max(3, 5, -10, 0, 1) ); // 5
Expand All @@ -406,7 +406,7 @@ A few examples:
alert( Math.pow(2, 10) ); // 2 in power 10 = 1024
```

There are more functions and constants in `Math`, including trigonometry, you can find them in the [docs for the Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object.
There are more functions and constants in `Math` object, including trigonometry, which you can find in the [docs for the Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object.

## Summary

Expand All @@ -423,12 +423,12 @@ For different numeral systems:

For converting values like `12pt` and `100px` to a number:

- Use `parseInt/parseFloat` for the "soft" conversion, which reads a number from a string until it can.
- Use `parseInt/parseFloat` for the "soft" conversion, which reads a number from a string and then returns the value they could read before the error.

For fractions:

- Round using `Math.floor`, `Math.ceil`, `Math.trunc`, `Math.round` or `num.toFixed(precision)`.
- Remember about the loss of precision when working with fractions.
- Make sure to remember there's a loss of precision when working with fractions.

More mathematical functions:

Expand Down