Какой тип числа у Number в JS? / Хабр

Если вы изучали языки со строгой типизацией, то должны понимать, что определённое значение должно храниться в памяти с заранее выделенным для неё количеством байт. Например, для числа int выделяется 4 байта, что равняется 32 битам и может содержать в себе 2³² значений, это значит мы можем выразить в десятичной системе исчисления от -2 147 483 647 до 2 147 483 647. Какой же тип числа используется в JS?

В стандарте EcmaScript написано, что Number Value: primitive value corresponding to a double-precision 64-bit binary format IEEE 754 value. То есть число double (число двойной точности), занимающее 8 байт = 64 бита (из них 1 бит выделен для обозначения знака числа, 11 бит для порядка и 52 — мантисса, всё что идёт после запятой).

Выделение битов для представления числа double

Диапазон значений: 1,7E +/- 308 (15 знаков). Вы можете проверить и посмотреть это число полностью, выведя в консоли максимальное допустимое число объекта Number: Number. MAX_VALUE.Вот ссылка на стандарт IEEE754: https://en.wikipedia.org/wiki/IEEE_754Если выйти за пределы этого числа, то Number выдаст нам значение Infinity.

Максимальное значение double с плавающей запятой

Хорошо, но сколько же целых чисел можно выразить с помощью double?
Ответ: 9,007,199,254,740,991 или~9 квадрильонов. Все числа до этого числа являются безопасными, что значит их можно сравнивать между собой. Данное максимально безопасное число также можно получить используя объект Number: Number.MAX_SAFE_INTEGER.

Чтобы проверить, что числа действительно сравниваются не правильно, можно выполнить в консоли:

Сравнение чисел, которые больше максимально допустимого целого числа. Математически это не равные значения, однако нам вернётся true.

Если вам нужно работать с большими числами и сравнивать их между собой, то можно проверять является ли число безопасным с помощью метода isSafeInteger():

Проверка является ли число безопасным

Хорошо, мы поняли, что по стандарту числа должны храниться в формате double. Но это же плохо влияет на выделяемую память, её становится больше? Получается, если мы работаем с целыми числами, то у нас нет возможности обозначить, что число целое int и должно занимать в памяти 4 байта, а не 8! На самом деле, если взять к примеру наш любимый V8 и посмотреть в исходниках как создаётся примитивное значение Number:

Разные классы для чисел. Integer наследуется от Number

То мы можем увидеть, что существует классы для создания разных типов чисел. И int и int32 и Uint32 (без знака ±), есть даже BigIntInteger наследуется от NumberInt32 и Uint32 наследуются от Integer.

Файл можно посмотреть на gitHub’е: https://github.com/v8/v8/blob/master/include/v8.h#L3039

То есть на самом деле V8 на C++ выделяет для переменной числа 4 байта если вы присвоите переменной целое число. Однако если вы динамически измените тип этой же переменной, то V8 придётся сделать тип этого числа уже double и выделить 8 байтов. Смена типов на лету является дорогой операцией, поэтому если вы заботитесь о производительности и быстродействии, то лучше так не делать и следить за этим.

Мы динамически изменили тип переменной для V8

Изображение взято из статьи Performance Tip for JS in v8 Chris Wilsonhttps://www.html5rocks.com/en/tutorials/speed/v8/

Также хочу добавить одну интересную вещь. Чтобы вы не забывали JS — это одно, но мы в основном работаем с API браузера. Например, используем window.setTimeout(). Который не относится на прямую к JavaScript. Это относится к браузеру и setTimeout принимает в качестве аргумента число int32, которое как мы рассматривали в самом начале, может принимать максимальное целое число 2 147 483 647, но ни как не 9 квадрильонов. Если вы вызовите setTimout() с задержкой 2 147 483 648 (на 1 больше максимального значения Int32), то функция выполнится немедленно. Не путайте понятия JS и браузер. Нужно смотреть с какими числами работает каждый метод.

P.S. да, конечно никто не будет вызывать setTimeout в браузере на 25 дней. Но просто знание того, как это работает не помешает.

Всем спасибо, подписывайтесь на мою страничку в ВК и вступайте в нашу группу любителей frontend разработки

Число — JavaScript — Дока

Кратко

Скопировано

Тип данных «число» (number) содержит числа с плавающей точкой в диапазоне от -(253 − 1) до 253 − 1, а также специальные значения Infinity, -Infinity и NaN.

Для этого типа данных определены стандартные арифметические операции сложения +, вычитания -, умножения *, деления /, взятия остатка от целочисленного деления %, сравнения >, <, >=, <=, ==, ===, !=, !==.

В JavaScript отсутствует отдельный тип данных для целых чисел, для целых чисел также используется тип number.

Как пишется

Скопировано

Для записи чисел используются цифры, для разделения целой и десятичной части используется точка:

const int = 4const decimal = 0.101const sameDecimal = .101
          const int = 4
const decimal = 0.101
const sameDecimal = .101

Можно использовать экспоненциальную запись. Например, один миллион в экспоненциальной записи:

const scientific = 1e6
          const scientific = 1e6

Числа так же могут быть представлены в двоичном, восьмеричном или шестнадцатеричном виде. Такие числа начинаются с приставки 0b, 0o, 0x соответственно. При выводе на экран они будут преобразованы в десятичную систему счисления:

const binary = 0b11console.log(binary)// 3const octal = 0o77console.log(octal)// 63const hexadecimal = 0xFFconsole.
log(hexadecimal)// 255 const binary = 0b11 console.log(binary) // 3 const octal = 0o77 console.log(octal) // 63 const hexadecimal = 0xFF console.log(hexadecimal) // 255

Как понять

Скопировано

Число с плавающей точкой

Скопировано

Число в JavaScript представлено в виде 64-битного формата IEEE-754. Формат хранит произвольное число в виде трёх значений: 1 бит на знак числа, 52 бита значения числа и ещё 11 бит местоположения точки. С таким подходом можно эффективно хранить значения в большом диапазоне от -(253 − 1) до 253 − 1.

Из-за того, что положение точки в числе хранится отдельным значением, формат и называется числом с плавающей точкой (floating point number).

Проблема этого представления в том, что оно не может представить числа абсолютно точно, а только с некоторой погрешностью.

Неточные вычисления

Скопировано

В десятичной системе счисления есть числа, которые не могут быть записаны точно. Например, треть ¹⁄₃ записывается как бесконечная дробь 0.33(3).

Компьютер хранит данные в двоичном виде — наборе нулей и единиц. В этой системе счисления тоже есть дроби, которые не могут быть записаны точно. В этом случае формат округляет значение до ближайшего представимого. При арифметических операциях эти неточности складываются и приводят к эффектам, подобным этому:

console.log(0.2 + 0.7)// 0.8999999999999999
          console.log(0.2 + 0.7)
// 0.8999999999999999

Это не ошибка JavaScript, а фундаментальная особенность хранения дробных чисел в памяти компьютера, с ней нужно уметь работать. Для уменьшения эффекта используется комбинация подходов — использовать как можно меньше дробных значений, а когда этого невозможно избежать — округлять числа, тем самым сбрасывая накопившийся остаток.

Например, если ваша система работает с деньгами, то лучше хранить цены в копейках или центах. Это позволит избежать большого количества операций с дробями. Для вывода цен можно пользоваться методом

toFixed, который округлит число до указанного разряда:

const priceInCents = 15650const discount = priceInCents * 0. 33const total = (priceInCents - discount) / 100console.log(total.toFixed(2))// 104.86
          const priceInCents = 15650
const discount = priceInCents * 0.33
const total = (priceInCents - discount) / 100
console.log(total.toFixed(2))
// 104.86

Похожую проблему можно наблюдать при сравнении очень маленьких и очень больших чисел. В таких случаях из-за округления точность теряется и различные числа компьютер представляет одинаковыми:

const small = 0.11111111111111111const smaller = 0.11111111111111110console.log(small.toFixed(20))// 0.11111111111111110494console.log(smaller.toFixed(20))// 0.11111111111111110494console.log(small === smaller)// true
          const small = 0.11111111111111111
const smaller = 0.11111111111111110
console.log(small.toFixed(20))
// 0.11111111111111110494
console.log(smaller.toFixed(20))
// 0.11111111111111110494
console.log(small === smaller)
// true

Специальные значения

Скопировано

Стандарт IEEE-754 определяет три специальных значения. Эти значения принадлежат типу number, но не работают, как обычные числа:

  • бесконечность Infinity;
  • минус бесконечность -Infinity;
  • не число (not a number) NaN.

Бесконечности используются, чтобы определить результат некоторых арифметических операций. Например, деление на ноль в JavaScript вернёт бесконечность:

console.log(5 / 0)// Infinityconsole.log(-3 / 0)// -Infinity
          console.log(5 / 0)
// Infinity
console.log(-3 / 0)
// -Infinity

Если попытаться создать число, которое находится вне диапазона доступных чисел, результатом будет тоже бесконечность:

console.log(1e999)// Infinity
          console.log(1e999)
// Infinity

Значение NaN используется, чтобы сообщить об операции, результатом которой оказалось не число. В JavaScript существует пять операций, которые могут вернуть NaN:

  • ошибка парсинга числа (например, при попытке превратить строку в число parseInt('привет')).
  • результат математической операции не находится в полей действительных чисел (например, взятие корня от -1).
  • один из операндов в арифметической операции — NaN (5 + NaN).
  • результат арифметической операции не определён для переданных операндов (undefined + undefined).
  • арифметическая операция со строкой, кроме сложения ('привет' * 5)

Согласно спецификации, NaN не равен самому себе. Проверить, что в переменной хранится NaN простым сравнением не получится:

const result = NaNconsole.log(result === NaN)// false
          const result = NaN
console.log(result === NaN)
// false

Для проверки на NaN пользуйтесь функцией Number.isNaN(), которая возвращает true если переданное значение — NaN:

const result = NaNconsole. log(Number.isNaN(result))// true
          const result = NaN
console.log(Number.isNaN(result))
// true

Для проверки, что значение в переменной является конечным числом, а не специальным значением, пользуйтесь функцией

Number.isFinite(), она возвращает true, если переданный аргумент — число.

const inf = Infinityconst nan = NaNconst num = 99999console.log(Number.isFinite(inf))// falseconsole.log(Number.isFinite(nan))// falseconsole.log(Number.isFinite(num))// true
          const inf = Infinity
const nan = NaN
const num = 99999
console.log(Number.isFinite(inf))
// false
console.log(Number.isFinite(nan))
// false
console.log(Number.isFinite(num))
// true

Операции с числами

Скопировано

С числами можно выполнять стандартные математические операции, для определения приоритета операций пользуются скобками:

const a = 5const b = 10console. log(-a)// -5console.log(a + b)// 15console.log(a - b)// -5console.log(a / b)// 0.5console.log(a * b)// 50console.log((a + b) / 10)// 1.5
          const a = 5
const b = 10
console.log(-a)
// -5
console.log(a + b)
// 15
console.log(a - b)
// -5
console.log(a / b)
// 0.5
console.log(a * b)
// 50
console.log((a + b) / 10)
// 1.5

Существует оператор взятия остатка от деления нацело

%:

console.log(5 % 2)// 1, потому что 5 = 2 * 2 + 1console.log(5 % 3)// 2, потому что 5 = 1 * 3 + 2console.log(5 % 5)// 0, потому что 5 = 5 * 1 + 0
          console.log(5 % 2)
// 1, потому что 5 = 2 * 2 + 1
console.log(5 % 3)
// 2, потому что 5 = 1 * 3 + 2
console.log(5 % 5)
// 0, потому что 5 = 5 * 1 + 0

Возведения в степень **:

console.log(2 ** 4)// 16
          console.log(2 ** 4)
// 16

Для округления, взятия корней и других математических операций в JavaScript существует отдельный модуль Math.

Операторы сравнения, возвращают булевое значение:

console.log(5 > 10)// falseconsole.log(5 >= 10)// falseconsole.log(5 < 10)// trueconsole.log(10 <= 10)// trueconsole.log(5 == 10)// falseconsole.log(5 === 10)// falseconsole.log(5 != 10)// trueconsole.log(5 !== 10)// true
          console.log(5 > 10)
// false
console.log(5 >= 10)
// false
console.log(5 < 10)
// true
console.log(10 <= 10)
// true
console.log(5 == 10)
// false
console.log(5 === 10)
// false
console.log(5 != 10)
// true
console.log(5 !== 10)
// true

☝️

Строгое === и нестрогое == сравнение работает одинаково, когда оба сравниваемых значения имеют тип «число». В других случаях их поведение отличается.

Числовой разделитель

Скопировано

В спецификации EcmaScript 2021 года (ES12) появилась возможность добавлять в числа разделители. Например:

const number = 1_000_000_000console.log(number)// 1000000000
          const number = 1_000_000_000
console.log(number)
// 1000000000

Разделители делают большие числа более читаемыми, внешне выделяя разряды чисел.

const integer = 1_234_567_890const float = 0.123_456_789
          const integer = 1_234_567_890
const float = 0.123_456_789

Разделители доступны для чисел других счислений и BigInt:

const binary = 0b0101_1111_0001const hex = 0x12_AB_34_CDconst bigInt = 1_234_567_890n
          const binary = 0b0101_1111_0001
const hex = 0x12_AB_34_CD
const bigInt = 1_234_567_890n

Дополнительные методы

Скопировано

Сам по себе примитивный тип «число» не имеет методов. Когда происходит вызов метода у числа, оно автоматически оборачивается в специальную обёртку, которая и содержит методы:

const num = 99.99console.log(num. toFixed(1))// 100.0
          const num = 99.99
console.log(num.toFixed(1))
// 100.0

Часто используемые методы обёртки, такие как toFixed(), toString() и toLocaleString(), описаны в отдельной статье.

На собеседовании

Скопировано

Это партнёрская рубрика, мы выпускаем её совместно с сервисом онлайн-образования Яндекс Практикум. Приносите вопрос, на который не знаете ответа, в задачи, мы разложим всё по полочкам и опубликуем. Если знаете ответ, присылайте пулреквест на GitHub.

Как проверить, что значение переменной (например, possiblyWrongNumber) не является NaN? Предполагаем что typeof possiblyWrongNumber === 'number'

Скопировано

Редакция

Полина Гуртовая  отвечает

Скопировано

Ответ очень простой – нужно воспользоваться специальной функцией Number. isNan(). В качестве аргумента нужно передать проверяемую переменную.

Вас может запутать формулировка вопроса: typeof possiblyWrongNumber === 'number'. Это же not a number, подумаете вы. Дело в том, что NaN используется для обозначения результатов вычислений, в которых «что-то пошло не так». Можете думать о NaN так: «Тут должно быть число, но вычисление сломалось». Это может произойти, если захотите вычислить квадратный корень из -1. В результате получится комплексное число, с которым наш браузер из коробки работать не умеет.

Неправильным ответом на этот вопрос будет вот такая проверка:

possiblyWrongNumber === NaN
          possiblyWrongNumber === NaN

Однако в ней уже содержится ответ на более хитрый дополнительный вопрос:

❓ Представим себе, что функции Number. isNan() не существует. Как в таком случае проверить, что значение переменной possiblyWrongNumber не является NaN? Условие typeof possiblyWrongNumber === 'number' по-прежнему выполняется.

Ответ такой: просто нужно сравнить значение переменной possiblyWrongNumber с самим собой. Если они равны, значит в переменной не NaN:

possiblyWrongNumber === possiblyWrongNumber
          possiblyWrongNumber === possiblyWrongNumber

Js.Int | ReScript API

Предоставляет утилиты для обработки int .

toExponential

 

let toExponential: int => string

Форматирует int с использованием экспоненциальной (научной) записи. Возвращает строку , представляющую заданное значение в экспоненциальной записи. Вызывает RangeError , если цифры не находятся в диапазоне [0, 20] (включительно).

 

RES

/* печатает "7.7e+1" */ Js.log(Js.Int.toExponential(77))

toExponentialWithPrecision

 

let toExponentialWithPrecision: (int, ~digits: int) => строка

Форматирует int с использованием экспоненциальной (научной) записи. цифры указывает, сколько цифр должно быть после запятой. Значение должно быть в диапазоне [0, 20] (включительно).

Возвращает строку , представляющую заданное значение в экспоненциальной записи.

Выходные данные будут округлены или дополнены нулями, если это необходимо. Поднимает RangeError , если цифры не находятся в диапазоне [0, 20] (включительно).

 

RES

/* печатает "7.70e+1" */ Js.log(Js.Int.toExponentialWithPrecision(77, ~цифры=2)) /* печатает "5.68e+3" */ Js.log(Js.Int.toExponentialWithPrecision(5678, ~цифры=2))

toPrecision

 

let toPrecision: int => string

Форматирует int , используя некоторые довольно произвольные правила. Возвращает строку , представляющий заданное значение в фиксированной точке (обычно).

toPrecision отличается от toFixed тем, что первый форматирует число с полной точностью, а второй не выводит никаких цифр после запятой. Вызывает RangeError , если цифры не входят в диапазон, принятый этой функцией.

 

RES

/* печатает "123456789" */ Js.log(Js.Int.toPrecision(123456789))

toPrecisionWithPrecision

 

let toPrecisionWithPrecision: (int, ~digits: int) => строка

Форматирует int , используя некоторые довольно произвольные правила. цифры указывает, сколько цифр должно появиться в общей сложности. Значение должно быть между 0 и некоторым произвольным числом, которое, как мы надеемся, по крайней мере больше 20 (для Node это 21. Почему? Кто знает).

Возвращает строку , представляющую заданное значение в фиксированной точке или экспоненциальном представлении.

Выходные данные будут округлены или дополнены нулями, если это необходимо.

toPrecisionWithPrecision отличается от toFixedWithPrecision тем, что в первом случае учитываются все цифры с учетом точности, а во втором учитываются только цифры после запятой. toPrecisionWithPrecision также будет использовать экспоненциальное представление, если указанная точность меньше количества цифр до десятичной точки. Вызывает RangeError , если цифры не входят в диапазон, принятый этой функцией.

 

RES

/* печатает "1. 2e+8" */ Js.log(Js.Int.toPrecisionWithPrecision(123456789, ~цифры=2)) /* печатает "0.0" */ Js.log(Js.Int.toPrecisionWithPrecision(0, ~цифры=2))

toString

 

let toString: int => string

Форматирует int как строку . Возвращает строку , представляющую заданное значение в фиксированной точке (обычно).

 

RES

/* печатает "123456789" */ Js.log(Js.Int.toString(123456789))

toStringWithRadix

 

let toStringWithRadix: (int, ~radix: int) => string

Форматирует int как строку . основание счисления указывает основание счисления, используемое для форматированного числа. Значение должно быть в диапазоне [2, 36] (включительно). Возвращает строку , представляющую заданное значение в фиксированной точке (обычно). Выдает RangeError , если основание счисления не входит в диапазон [2, 36] (включительно).

 

RES

/* печатает "110" */ Js.log(Js.Int.toStringWithRadix(6, ~radix=2)) /* печатает "deadbeef" */ Js.log(Js.Int.toStringWithRadix(3735928559, ~radix=16)) /* печатает "2n9c" */ Js.log(Js.Int.toStringWithRadix(123456, ~radix=36))

toFloat

 

let toFloat: int => float

равно

 

пусть равно: (int, int) => bool

макс

 

пусть макс: целое

мин

 

пусть мин: целое

Приступая к работе с ESLint — ESLint

ESLint — это инструмент для выявления шаблонов, обнаруженных в коде ECMAScript/JavaScript, и составления отчетов, с целью сделать код более согласованным и избежать ошибок.

ESLint полностью подключаемый. Каждое отдельное правило является плагином, и вы можете добавить больше во время выполнения. Вы также можете добавить подключаемые модули, конфигурации и парсеры сообщества, чтобы расширить функциональность ESLint. 914.17.0 или >=16.0.0 ) установлен и построен с поддержкой SSL. (Если вы используете официальный дистрибутив Node.js, SSL всегда встроен).

1

Если вы хотите использовать конкретную общую конфигурацию, размещенную на npm, вы можете использовать параметр --config и указать имя пакета:

 # use `eslint-config-semistandard` общая конфигурация
# нпм 7+
npm init @eslint/config --config полустандартный
# или (префикс `eslint-config` необязателен)
npm init @eslint/config --config eslint-config-semistandard
# ⚠️ npm 6.x без лишних двойных дефисов:
npm init @eslint/config --config полустандартный
 

1
2
3
4
5
6
7
8
9
10
11

. -конфиг полустандарт,стандарт # или npm init @eslint/config —config полустандартный —config стандартный

1
2
3

Примечание: npm init @eslint/config предполагает, что у вас уже есть файл package.json . Если вы этого не сделаете, обязательно запустите npm init или пряжа init заранее.

После этого вы можете запустить ESLint для любого файла или каталога следующим образом:

 npx eslint yourfile.js
# или
пряжа запустить eslint yourfile.js
 

1
2
3
4
5

Конфигурация

Примечание. Если вы используете версию до 1.0.0, см. руководство по миграции.

После запуска npm init @eslint/config у вас будет .eslintrc.{js,yml,json} в вашем каталоге. В нем вы увидите несколько правил, настроенных следующим образом:

 {
    "правила": {
        "полу": ["ошибка", "всегда"],
        "кавычки": ["ошибка", "двойная"]
    }
}
 

1
2
3
4
5
6

Имена «полу» и «кавычки» — это имена правил в ESLint. Первое значение — это уровень ошибки правила и может быть одним из следующих значений:

  • «выкл.» или 0 — выключить правило
  • "warn" или 1 — включить правило как предупреждение (не влияет на код выхода)
  • "ошибка" или 2 — включить правило как ошибку (код выхода будет 1)

Три уровня ошибок позволяют точно контролировать, как ESLint применяет правила (дополнительные параметры конфигурации и подробности см. в документации по конфигурации).

Ваш файл конфигурации .eslintrc.{js,yml,json} также будет содержать строку:

 {
    "расширяет": "eslint:рекомендуется"
}
 

1
2
3

Из-за этой строки будут включены все правила, помеченные как «(рекомендуется)» на странице правил. В качестве альтернативы вы можете использовать конфигурации, созданные другими, выполнив поиск «eslint-config» на npmjs.com. ESLint не будет анализировать ваш код, если вы не расширите его из общей конфигурации или явно не включите правила в своей конфигурации.

Глобальная установка

Также можно установить ESLint глобально, а не локально, используя npm установить eslint --global . Однако это не рекомендуется, и любые подключаемые модули или общие конфигурации, которые вы используете, все равно должны быть установлены локально, если вы устанавливаете ESLint глобально.

Настройка вручную

Вы также можете вручную настроить ESLint в своем проекте.

Прежде чем начать, у вас уже должен быть файл package.json . Если вы этого не сделаете, обязательно запустите npm init или yarn init , чтобы заранее создать файл.

  1. Установите пакет ESLint в свой проект:

     npm install --save-dev eslint
     

    1

  2. Добавьте файл .eslintrc в одном из поддерживаемых форматов файла конфигурации.