diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index 07f55f303c..0a9feb1c83 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -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 @@ -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 @@ -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: @@ -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 @@ -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) @@ -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`. @@ -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`. @@ -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: @@ -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; @@ -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; @@ -177,7 +177,7 @@ 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: @@ -185,7 +185,7 @@ If a number is too big, it would overflow the 64-bit storage, potentially giving 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: @@ -193,7 +193,7 @@ Consider this (falsy!) test: 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`? @@ -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`. @@ -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): @@ -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: @@ -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. ``` @@ -281,7 +281,7 @@ 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. @@ -289,14 +289,14 @@ Remember the two special numeric values? 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 @@ -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 @@ -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`" @@ -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: ```js run alert( parseInt('a123') ); // NaN, the first symbol stops the process @@ -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 @@ -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 @@ -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: