Преобразование и приведение типов | JavaScript Camp
Приведение типов (type coercion)
Это автоматическое или неявное преобразование значений из одного типа данных в другой (например, строки в число). Преобразование типа похоже на приведение типа, потому что они оба преобразуют значения из одного типа данных в другой с одним ключевым🗝️ различием — приведение типа является неявным, тогда как преобразование типа может быть неявным или явным.
Примеры 👇 :
function learnJavaScript() { const value1 = ‘5’ const value2 = 9 let sum = value1 + value2 return sum }
Loading…
В приведённом выше примере JavaScript приводит число 9
в строку, а затем объединяет два 2️⃣ значения вместе, в результате получается строка 59
. JavaScript имел выбор между строкой или числом и решил использовать строку.
Компилятор мог привести строку 5
к числу и вернуть сумму 14
, но он этого не сделал. Чтобы получить этот результат, вам нужно явно преобразовать строку 5
в число, используя метод Number()
👇 :
function learnJavaScript() { const value1 = ‘5’ const value2 = 9 let sum = Number(value1) + value2 return sum }
Loading…
Видео
Преобразование типов (type conversion)
Означает передачу данных из одного типа данных в другой. Неявное преобразование происходит, когда компилятор автоматически присваивает (назначает) типы данных, но исходный код📟 также может явно требовать преобразования для завершения.
Строковое преобразование
Строковое преобразование происходит, когда требуется представление чего-либо в виде строки. Например, мы можем использовать функцию String(value)
, чтобы преобразовать значение к строке 👇 :
function learnJavaScript() { let value = true // boolean value = String(value) return typeof value }
Loading…
Преобразование происходит очевидным образом. true
✅ становится "true"
✅
Численное преобразование
Численное преобразование происходит в математических функциях⚙️ и выражениях.
function learnJavaScript() { let value = ‘6’ / ‘2’ return value }
Loading…
Мы можем использовать функцию Number(value)
, чтобы явно преобразовать value
к числу 👇 :
function learnJavaScript() { let str = ‘123’ let num = Number(str) return typeof num }
Loading. ..
Явное преобразование часто применяется, когда мы ожидаем получить число из строкового контекста, например из текстовых 📜 полей форм.
Если строка не может быть явно приведена к числу, то результатом преобразования будет NaN
(англ. Not-a-Number, «не число»). Например 👇:
function learnJavaScript() { let age = Number(‘Любая строка вместо числа’) return age }
Loading…
Правила численного преобразования:
Значение | Преобразуется в… |
---|---|
undefined | NaN |
null | 0 |
true / false | 1 / 0 |
string | Пробельные символы по краям обрезаются. Далее, если остаётся пустая строка, то получаем 0, иначе из непустой строки «считывается» число. При ошибке🙅♂️ результат NaN. |
Примеры:
Number(' 123 ') // 123
Number('123z') // NaN (ошибка чтения числа на месте символа "z")
Number(true) // 1
Number(false) // 0
Number(null) // 0
Number(undefined) // NaN
Учтите, что null
и undefined
ведут себя по-разному. Так, null
становится нулём, тогда как undefined
приводится к NaN
.
Логическое преобразование
Логическое преобразование самое простое. Происходит в логических операциях, но также может быть выполнено явно с помощью функции⚙️ Boolean(value)
.
Правила логического преобразования:
Значения, которые интуитивно «пустые», вроде 0
, пустой строки, null
, undefined
и NaN
, становятся false
. Все остальные значения становятся true
.
Boolean(1) // true
Boolean(0) // false
Boolean('Привет!') // true
Boolean('') // false
Более короткий способ функкции Boolean
двойное НЕ(!!) используют для преобразования значений к логическому типу:
!!'non-empty string' // true
!!null // false
То есть первое НЕ преобразует значение в логическое значение и возвращает обратное, а второе НЕ снова инвертирует его. В конце мы имеем простое преобразование значения в логическое.
Заметим, что строчка с нулём «0» — это true
Некоторые языки👅 (к примеру, PHP) воспринимают строку "0"
как false
. Но в JavaScript, если строка не пустая, то она всегда true
Boolean('0') // true
Boolean(' ') // пробел это тоже true (любая непустая строка это true)
Проблемы?
Пишите в Discord или телеграмм чат, а также подписывайтесь на наши новости
Вопросы:
Какую функцию нужно использовать для строкового преобразования?
Boolean(value)
String(value)
Number(value)
Что такое приведение типов?
- Передача данных из одного типа в другой
- Преобразование значений из одного типа данных в другой
- Представление чего-либо в виде строки
Какое ключевое различие между приведением типов и преобразованием типов?
- Приведение типа явное, а преобразование типа неявное
- Приведение типа неявное, а преобразование типа явное
- Приведение типа неявное, а преобразование типа может быть и явным и неявным
В каком случае результатом преобразования будет NaN
?
- Когда строка не может быть явно приведена к числу
- Когда число не может быть явно приведено к строке
- Когда в коде есть ошибка
Чем становятся «пустые» значения при преобразовании?
null
true
false
Для того чтобы понять, на сколько вы усвоили этот урок, пройдите тест в мобильном приложении нашей школы по этой теме или в нашем телеграм боте.
Ссылки:
- MDN web docs — Приведение типов
- для подростков: прекрасное руководство по программированию для начинающих, том 1: Javascript — Jeremy Moritz
- JavaScript.ru
- Арифметические действия с целыми числами
Contributors ✨
Thanks goes to these wonderful people (emoji key):
Dmitriy Vasilev 📖💵 | Resoner2005 🐛 🎨 🖋 |
Преобразование типов — JavaScript — Дока
Кратко
Секция статьи «Кратко»Представим ситуацию: у нас есть форма с полем, в которое пользователь вписывает свой возраст в годах.
По умолчанию любой ввод в полях — это строка. Если мы хотим работать с этим значением, как с числом, то нам нужно привести его к числу.
Приведение (или преобразование) типов — это процесс конвертации значения из одного типа в другой.
В JavaScript типы можно преобразовывать явно и неявно.
Когда мы вызываем функцию, чтобы получить конкретный тип — это явное преобразование:
const x = '4'Number(x)const y = 4String(y)
const x = '4'
Number(x)
const y = 4
String(y)
Сравнение бывает строгим и нестрогим. При строгом сравнении (
) интерпретатор учитывает типы сравниваемых значений.
Когда же мы сравниваем значения нестрого между собой с помощью
, JavaScript приводит типы самостоятельно:
console.log(5 == '5')// trueconsole.log(5 === '5')// false
console.log(5 == '5')
// true
console.log(5 === '5')
// false
Чтобы понять, почему так, нам надо сперва разобраться, какие типы в JS есть.
Сперва проведём границу между примитивными типами, объектами и другими.
Примитивные типы
Секция статьи «Примитивные типы»В JavaScript примитивные типы следующие:
// 1. Undefinedtypeof undefined === 'undefined'// 2. Boolean, логическийtypeof true === 'boolean'typeof false === 'boolean'// 3. Number, числоtypeof 42 === 'number'typeof 4.2 === 'number'typeof -42 === 'number'typeof Infinity === 'number'typeof -Infinity === 'number'// 4. String, строкаtypeof '' === 'string'typeof 'string' === 'string'typeof 'number' === 'string'typeof 'boolean' === 'string'// 5. Symbol, символ, ES6typeof Symbol() === 'symbol'// 6. BigInt, большое число, ES6typeof 9007199254740991n === 'bigint'typeof BigInt(9007199254740991) === 'bigint'// 7. Nulltypeof null === 'object'// О том, почему здесь “object” — чуть позже.
// 1. Undefined
typeof undefined === 'undefined'
// 2. Boolean, логический
typeof true === 'boolean'
typeof false === 'boolean'
// 3. Number, число
typeof 42 === 'number'
typeof 4.2 === 'number'
typeof -42 === 'number'
typeof Infinity === 'number'
typeof -Infinity === 'number'
// 4. String, строка
typeof '' === 'string'
typeof 'string' === 'string'
typeof 'number' === 'string'
typeof 'boolean' === 'string'
// 5. Symbol, символ, ES6
typeof Symbol() === 'symbol'
// 6. BigInt, большое число, ES6
typeof 9007199254740991n === 'bigint'
typeof BigInt(9007199254740991) === 'bigint'
// 7. Null
typeof null === 'object'
// О том, почему здесь “object” — чуть позже.
Примитивные типы — это такие типы, значения которых можно только перезаписать, но нельзя изменить.
Например, если мы создали переменную со значением 42
, изменить это значение будет нельзя. Мы сможем его только полностью перезаписать:
let theAnswerToUltimateQuestion = 42theAnswerToUltimateQuestion = 43// Новое значение полностью перезаписало старое;// старое собрано сборщиком мусора и забыто.let theAnswers = [42, 43, 44]theAnswers[0] = 142// Теперь значение переменной [142, 43, 44];// мы не перезаписали его полностью, а лишь изменили часть.
let theAnswerToUltimateQuestion = 42
theAnswerToUltimateQuestion = 43
// Новое значение полностью перезаписало старое;
// старое собрано сборщиком мусора и забыто.
let theAnswers = [42, 43, 44]
theAnswers[0] = 142
// Теперь значение переменной [142, 43, 44];
// мы не перезаписали его полностью, а лишь изменили часть.
Этот механизм связан с тем, как значения переменных хранятся в памяти. Мы не пойдём слишком глубоко в эту тему, но, грубо говоря, примитивные типы «ссылаются на одно и то же значение в памяти», а не примитивные — на разные. Этот вопрос мы разбираем подробнее в статье «Хранение по ссылке и по значению »
Из-за этого, например, примитивы можно сравнивать по значению:
const a = 5const b = 5console.log(a == b)// true
const a = 5
const b = 5
console.log(a == b)
// true
А вот не примитивы — не получится:
const a = [1, 2, 3]const b = [1, 2, 3]console.log(a == b)// false
const a = [1, 2, 3]
const b = [1, 2, 3]
console.log(a == b)
// false
Даже несмотря на то, что массивы содержат одни и те же числа, при сравнении они не являются «одинаковыми».
a
и b
, он, грубо говоря, «сравнивает места в памяти, на которые ссылаются эти переменные». У не примитивов, эти места — разные, из-за чего они считаются неодинаковыми.Объекты
Секция статьи «Объекты»Объекты в JavaScript используются для хранения коллекций значений.
Массивы (Array) в JS — тоже объекты.
Как мы уже говорили, не примитивы сравниваются по ссылке, а не по значению. Объекты и массивы — это как раз не примитивы.
У объектов в JavaScript собственный тип — object
.
const keyValueCollection = { key: 'value' }typeof keyValueCollection === 'object'const listCollection = [1, 2, 3]typeof listCollection === 'object'
const keyValueCollection = { key: 'value' }
typeof keyValueCollection === 'object'
const listCollection = [1, 2, 3]
typeof listCollection === 'object'
У null
оператор typeof
'object'
, хотя это тоже примитив:console. log(typeof null === 'object')// true
console.log(typeof null === 'object')
// true
Функции
Секция статьи «Функции»У функций в JavaScript тоже тип — object
, хотя typeof
возвращает 'function'
:
function simpleFunction() {}console.log(typeof simpleFunction === 'function')// trueconst assignedFunction = function () {}console.log(typeof assignedFunction === 'function')// trueconst arrowFunction = () => {}console.log(typeof arrowFunction === 'function')// trueconsole.log(typeof function () {} === 'function')// truefunction simpleFunction() {} console.log(typeof simpleFunction === 'function') // true const assignedFunction = function () {} console.log(typeof assignedFunction === 'function') // true const arrowFunction = () => {} console.log(typeof arrowFunction === 'function') // true console.log(typeof function () {} === 'function') // true
Разницу между разными видами функций мы описали в статье о функциях Функции.
typeof
Секция статьи «typeof»Оператор typeof
возвращает не непосредственно «тип», а строку. Для всех примитивов, кроме null
, этой строкой будет название этого примитива.
Для объектов он сначала проверит, можно ли его «вызвать». Функции — это как раз такие объекты, поэтому оператор возвращает function
.
Несмотря на то, что typeof
не всегда возвращает то, что мы бы могли ожидать, им удобно пользоваться в некоторых случаях в коде, например, для определения функций.
Преобразование типов
Секция статьи «Преобразование типов»Теперь, когда мы разобрались с типами, посмотрим, как мы можем преобразовывать значения одного типа в значения другого.
В JavaScript существует лишь 3 типа конвертации: в строку, в число или в логическое значение.
Чтобы конвертировать значение в эти типы, можно воспользоваться одноимёнными функциями:
String(42) // Приводит к строке.Number('42') // Приводит к числу. Boolean(42) // Приводит к логическому значению.
String(42) // Приводит к строке.
Number('42') // Приводит к числу.
Boolean(42) // Приводит к логическому значению.
Приведение к строке, числу и логическому значению можно проводить над любыми значениями:
// К строке:String(123) // '123'String(-12.3) // '-12.3'String(null) // 'null'String(undefined) // 'undefined'String(true) // 'true'String(false) // 'false'String(function () {}) // 'function () {}'String({}) // '[object Object]'String({ key: 42 }) // '[object Object]'String([]) // ''String([1, 2]) // '1,2'
// К строке:
String(123) // '123'
String(-12.3) // '-12.3'
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(false) // 'false'
String(function () {}) // 'function () {}'
String({}) // '[object Object]'
String({ key: 42 }) // '[object Object]'
String([]) // ''
String([1, 2]) // '1,2'
К числу также можно пытаться приводить любые значения. Если JavaScript не сможет привести какое-то значение к числу, мы получим NaN
— особое значение, представляющее не-число (Not-a-Number).
// К числу:Number('123') // 123Number('123.4') // 123.4Number('123,4') // NaNNumber('') // 0Number(null) // 0Number(undefined) // NaNNumber(true) // 1Number(false) // 0Number(function () {}) // NaNNumber({}) // NaNNumber([]) // 0Number([1]) // 1Number([1, 2]) // NaN// Обратите внимание, что Number от пустого массива — 0,// от массива с одним числом — это число// и от массива с несколькими числами — NaN.// Почему так происходит, мы поймём чуть ниже.
// К числу:
Number('123') // 123
Number('123.4') // 123.4
Number('123,4') // NaN
Number('') // 0
Number(null) // 0
Number(undefined) // NaN
Number(true) // 1
Number(false) // 0
Number(function () {}) // NaN
Number({}) // NaN
Number([]) // 0
Number([1]) // 1
Number([1, 2]) // NaN
// Обратите внимание, что Number от пустого массива — 0,
// от массива с одним числом — это число
// и от массива с несколькими числами — NaN.
// Почему так происходит, мы поймём чуть ниже.
К логическому также можно приводить любые значения:
Boolean('') // falseBoolean('string') // trueBoolean('false') // trueBoolean(0) // falseBoolean(42) // trueBoolean(-42) // trueBoolean(NaN) // falseBoolean(null) // falseBoolean(undefined) // falseBoolean(function () {}) // trueBoolean({}) // trueBoolean({ key: 42 }) // trueBoolean([]) // trueBoolean([1, 2]) // true// Грубо говоря, всё, кроме пустой строки, нуля,// NaN, null и undefined — true.
Boolean('') // false
Boolean('string') // true
Boolean('false') // true
Boolean(0) // false
Boolean(42) // true
Boolean(-42) // true
Boolean(NaN) // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(function () {}) // true
Boolean({}) // true
Boolean({ key: 42 }) // true
Boolean([]) // true
Boolean([1, 2]) // true
// Грубо говоря, всё, кроме пустой строки, нуля,
// NaN, null и undefined — true.
Неявное преобразование типов
Секция статьи «Неявное преобразование типов»В секции выше мы преобразовывали типы «руками», с помощью функций. Но JavaScript может делать такие преобразования за нас самостоятельно. (Из-за чего в языке появляется много странностей, за которые его не очень сильно любят.)
Такая типизация, при которой тип значения определяется во время присвоения, а по ходу программы может меняться, — называется динамической.
Неявное преобразование происходит, когда мы заставляем JavaScript работать со значениями разных типов. Например, если мы хотим «сложить» число и строку:
5 + '3' === '53'5 - '3' === 25 + '-3' === '5-3'5 - +3 === 25 + -3 === 2// Из-за этого же появилась и такая шутка:Array(16).join('wat' - 1) + ' Batman!'// 'NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman!'
5 + '3' === '53'
5 - '3' === 2
5 + '-3' === '5-3'
5 - +3 === 2
5 + -3 === 2
// Из-за этого же появилась и такая шутка:
Array(16).join('wat' - 1) + ' Batman!'
// 'NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman!'
Дело в том, как JavaScript пробует эти два типа «сопоставить» друг с другом, чтобы с ними работать.
Вначале посмотрим на примитивы.
- Интерпретатор приведёт примитивные значения к логическим, если мы используем
&&
или||
. - К строке, если мы используем
+
, когда один из операндов — строка. - К числу, если:
- мы используем операторы сравнения
<
,<
,= >
,>
;= - используем арифметические операции
,- +
(за исключением пункта 2),
,/ *
. - используем унарный плюс:
+'2'
;= = = 2 - используем оператор нестрогого сравнения
.= =
- мы используем операторы сравнения
Но примитивами дело не заканчивается, JavaScript также неявно приводит и не примитивные значения.
Интерпретатор приводит их к логическому, если мы используем &&
или ||
. (Объекты — всегда true
).
С числом и строкой всё немного интереснее. Чтобы определить, к строке приводить значение или к числу, JavaScript смотрит, какой из двух методов (value
и to
) в текущем объекте объявлен.
- Если перед нами не объект
Date
, то методvalue
вызывается, обычно, первым (если не сильно углубляться в детали спецификации).Of ( ) - Если возвращённое после этого значение — это примитив, то возвращается оно.
- Если нет, то вызывается другой метод (если
value
не вернул примитив, то вызываетсяOf ( ) to
и наоборот).String ( ) - Если после этого вернулся примитив, возвращается он.
- Если даже после этого не вернулся примитив, то будет ошибка
Uncaught TypeError
.: Cannot convert object to primitive value
На примерах
Секция статьи «На примерах»// 1. Простой объектconst obj1 = {}obj1.valueOf() // {}obj1.toString() // '[object Object]'// Чтобы «сложить» число с объектом,// вначале будет вызван obj1.valueOf().// Он вернёт объект (непримитив),// после чего будет вызван obj1.toString().1 + obj1// 1 + '[object Object]'// '1' + '[object Object]'// '1[object Object]'// 2. Объект с указанным .valueOf()const obj2 = {}obj2.valueOf = () => 'obj2'obj2.valueOf() // 'obj2'obj2.toString() // '[object Object]'// Теперь, когда мы объявили метод .valueOf(),// при вызове он будет возвращать строку.// Так как строка — примитив,// она и будет использована при «сложении».1 + obj2// 1 + 'obj2'// '1' + 'obj2'// '1obj2'// 2.1. Если же мы будем возвращать числоconst obj2 = {}obj2.valueOf = () => 42obj2.valueOf() // 42obj2.toString() // '[object Object]'1 + obj2// 1 + 42// 43// 3. Датыconst date = new Date()date.valueOf() // 1467864738527date.toString() // 'Sun Sep 15 2019...'// У дат приоритет методов обратный:// то есть вначале будет вызываться . toString(),// и только после него — .valueOf().1 + date// 1 + 'Sun Sep 15 2019...'// '1' + 'Sun Sep 15 2019...'// '1Sun Sep 15 2019...'
// 1. Простой объект
const obj1 = {}
obj1.valueOf() // {}
obj1.toString() // '[object Object]'
// Чтобы «сложить» число с объектом,
// вначале будет вызван obj1.valueOf().
// Он вернёт объект (непримитив),
// после чего будет вызван obj1.toString().
1 + obj1
// 1 + '[object Object]'
// '1' + '[object Object]'
// '1[object Object]'
// 2. Объект с указанным .valueOf()
const obj2 = {}
obj2.valueOf = () => 'obj2'
obj2.valueOf() // 'obj2'
obj2.toString() // '[object Object]'
// Теперь, когда мы объявили метод .valueOf(),
// при вызове он будет возвращать строку.
// Так как строка — примитив,
// она и будет использована при «сложении».
1 + obj2
// 1 + 'obj2'
// '1' + 'obj2'
// '1obj2'
// 2.1. Если же мы будем возвращать число
const obj2 = {}
obj2.valueOf = () => 42
obj2.valueOf() // 42
obj2.toString() // '[object Object]'
1 + obj2
// 1 + 42
// 43
// 3. Даты
const date = new Date()
date.valueOf() // 1467864738527
date.toString() // 'Sun Sep 15 2019...'
// У дат приоритет методов обратный:
// то есть вначале будет вызываться .toString(),
// и только после него — .valueOf().
1 + date
// 1 + 'Sun Sep 15 2019...'
// '1' + 'Sun Sep 15 2019...'
// '1Sun Sep 15 2019...'
Строгое и нестрогое равенство
Секция статьи «Строгое и нестрогое равенство»Неявное преобразование также используется, когда мы сравниваем значения через нестрогое равенство
.
В отличие от строгого равенства (
), в нём интерпретатор пробует привести типы к одному, чтобы сравнить.
Полный алгоритм сложный. Для удобства его свели в большую матрицу, которая показывает, «что чему равно» при строгом и нестрогом равенстве.
Вот таблица нестрогого равенства (зелёным отмечены значения, которые «равны»):
А вот — для строгого:
Хорошей практикой считается использовать только строгое сравнение, чтобы избежать неявного преобразования типов при сравнении.
На практике
Секция статьи «На практике»Саша Беспоясов советует
Секция статьи «Саша Беспоясов советует»Всегда используйте строгое равенство при сравнении значений.
🛠 Для удобства проверку на существование объекта можно проводить через if
, потому что объекты всегда приводятся к true
.
const exists = {}if (exists) { /* эта ветка выполнится */}const doesntExist = undefinedif (doesntExist) { /* эта ветка не выполнится */}
const exists = {}
if (exists) {
/* эта ветка выполнится */
}
const doesntExist = undefined
if (doesntExist) {
/* эта ветка не выполнится */
}
🛠 Если хочется описать сложную структуру, которая бы умела «вести себя», как число или строка, можно описать методы
или
.
const ticketPrice = { amount: 20, currency: 'USD', valueOf: () => 20, toString: () => '$20',}1 + ticketPrice // 1 + 20 -> 21console.log(ticketPrice)// $20
const ticketPrice = {
amount: 20,
currency: 'USD',
valueOf: () => 20,
toString: () => '$20',
}
1 + ticketPrice // 1 + 20 -> 21
console.log(ticketPrice)
// $20
Преобразование типов в JavaScript | by Serj Bulavyk
Know your engines!
Перевод статьи Alexey Samoshkin “JavaScript type coercion explained”.
Преобразование типов это процесс конвертации значения из одного типа в другой (как например, строки в число, объекта к булевому значению и т. д.). Любой тип, будь то примитив или объект, может быть преобразован в другой тип. Для справки, примитивы это: number
, string
, boolean
, null
, undefined
+ Symbol
(добавлен в ES6).
В качестве примера преобразования типов, можно ознакомиться с JavaScript Comparison Table, где продемонстрировано как ведёт себя оператор нестрогого сравнения ==
для разных типов a
и b
.
Из-за побочного эффекта оператора ==
в виде неявного приведения типа, эта матрица выглядит довольно пугающей, и запомнить все эти комбинации не представляется возможным. К счастью, вам не обязательно это делать, достаточно просто знать принципы, которые лежат в основе преобразования типов.
Эта статья подробно расскажет вам о том, как работает механизм преобразования типов в JavaScript и вооружит вас необходимыми знаниями для того, что-бы вы могли самостоятельно объяснить как вычисляются и каков будет результат следующих выражений. В конце статьи я продемонстрирую ответы и объясню их.
true + false
12 / "6"
"number" + 15 + 3
15 + 3 + "number"
[1] > null
"foo" + + "bar"
'true' == true
false == 'false'
null == ''
!!"false" == !!"true"
[‘x’] == ‘x’
[] + null + 1
[1,2,3] == [1,2,3]
{}+[]+{}+[1]
!+[]+[]+![]
new Date(0) - 0
new Date(0) + 0
Этот список скорее представляет собой те вещи, которые вам как программисту делать не стоит, а поэтому расценивайте этот список как своеобразные упражнения, для того чтобы оценить насколько хорошо вы знаете механизм приведения типов. Если вам станет скучно, вы можете найти больше примеров на wtfjs.com.
Кстати, иногда вы можете встретить подобные вопросы на собеседованиях на позицию JavaScript разработчика. Итак, поехали дальше 🙂
Явное и неявное преобразование
Преобразование типов может происходить явно и неявно.
Когда разработчик хочет намеренно произвести преобразование типов, написав, к примеру Number(value)
, это называется явным преобразованием типов (или type casting).
Так как JavaScript это слабо типизированный язык, преобразование между разными типами может происходить автоматически, и это называется неявным преобразованием типов. Чаще всего это происходит когда вы применяете операторы к значениям разных типов, таких как 1 == null
, 2 / `5`
, null + new Date()
, может происходить в зависимости от контекста, как например, в случае с if (value) {…}
, где value будет приведено к булевому значению.
Оператор строгого равенства ===
не приводит к неявному преобразованию типов. Оператор нестрогого равенства ==
, в свою очередь, производит сравнение операндов и, если требуется, неявное преобразование типов.
Неявное преобразование типов — это палка о двух концах: с одной стороны это источник проблем и разочарований, а с другой — механизм, который позволяет нам писать меньше кода, не теряя при этом читабельности.
Три типа конвертации
Во-первых, следует знать, что в JavaScript существует всего 3 типа преобразования:
- строковое
- булевое
- численное
Во-вторых, логика преобразования для примитивов и объектов работает по-разному, но, и примитивы и объекты могут быть преобразованы только этими тремя способами.
Давайте сначала разберёмся с примитивами.
Приведение к строке
Для явного приведения значения к строке необходимо применить к нему функцию String()
. Неявное преобразование будет вызвано бинарным оператором +
, кода один из операндов является строкой:
String(123) // explicit
123 + '' // implicit
Все примитивы будут приведены к строке вполне естественно, как вы могли и ожидать:
String(123) // '123'
String(-12. 3) // '-12.3'
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(false) // 'false'
Преобразование символов происходит немного сложнее, потому что они могут быть преобразованы только явным образом. Вы можете прочитать об этих правилах подробнее здесь.
String(Symbol('my symbol')) // 'Symbol(my symbol)'
'' + Symbol('my symbol') // TypeError is thrown
Булевое преобразование
Для явного преобразования к булевому значению, нужно применить функцию Boolean()
. Неявное преобразование происходит в логическом контексте if (val) { … }
или при применении логических операторов (||
&&
!
).
Boolean(2) // explicit
if (2) { ... } // implicit due to logical context
!!2 // implicit due to logical operator
2 || 'hello' // implicit due to logical operator
На заметку, логические операторы такие как ||
и &&
производят булевое преобразование под капотом, но при этом всегда возвращают оригинальное значение операндов, даже если они не являются булевыми.
// returns number 123, instead of returning true
// 'hello' and 123 are still coerced to boolean internally to calculate the expression
let x = 'hello' && 123; // x === 123
Поскольку существует всего два возможных результата преобразования, легче просто запомнить список ложных значений:
Boolean('') // false
Boolean(0) // false
Boolean(-0) // false
Boolean(NaN) // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(false) // false
Любое значение, которое не вошло в этот список, будет преобразовано в true
, включая объекты, функции, Array
, Date
и так далее. Символы, пустые объекты и массивы так же будут иметь значение true
.
Boolean({}) // true
Boolean([]) // true
Boolean(Symbol()) // true
!!Symbol() // true
Boolean(function() {}) // true
Численное преобразование
Для явного преобразования к числу нужно применить функцию Number()
, точно так же, как мы делали с Boolean()
и String()
. ~
)
-
+
*
/
%
). Обратите внимание, что бинарный оператор +
не вызывает численного преобразования, если один из операндов является строкой+
==
(включая !=
). Обратите внимание, что данный оператор не вызывает численное преобразование, если оба операнда являются строкамиNumber('123') // explicit
+'123' // implicit
123 != '456' // implicit
4 > '5' // implicit
5/null // implicit
true | 0 // implicit
Примеры того, как примитивы будут преобразованы в числа:
Number(null) // 0
Number(undefined) // NaN
Number(true) // 1
Number(false) // 0
Number(" 12 ") // 12
Number("-12.34") // -12. 34
Number("\n") // 0
Number(" 12s ") // NaN
Number(123) // 123
При преобразовании строки в число, движок сначала отсекает все пробельные символы, символы \n
, и \t
в начале и в конце строки, и возвращает NaN
если обрезанная строка не представляет из себя корректное число. Если строка окажется пустой, то результатом будет 0
.
null
и undefined
обрабатываются по разному: null
станет 0
, в то время как undefined
станет NaN
.
Численное преобразование, как явное так и неявное, не работает для символов. Более того, движок бросает ошибку TypeError
, вместо того, чтобы по-тихому преобразовать Symbol
в NaN
, как это происходит с undefined
. Подробнее о правилах преобразования символов можно посмотреть на MDN.
Number(Symbol('my symbol')) // TypeError is thrown
+Symbol('123') // TypeError is thrown
Существует два специальных правила которые следует запомнить:
- При применении
==
кnull
илиundefined
, численное преобразование не происходит, так какnull
может равняться толькоnull
илиundefined
, и ничему другому.
null == 0 // false, null is not converted to 0
null == null // true
undefined == undefined // true
null == undefined // true
2. NaN
не равен ничему, даже самому себе.
if (value !== value) { console.log("we're dealing with NaN here") }
Преобразование типов для объектов
До текущего момента мы рассматривали преобразования для примитивов, что является достаточно банальным.
Когда дело доходит до объектов, и движок встречает выражение вроде [1] + [2, 3]
, ему сначала необходимо привести объекты к примитивным значениям, а уже потом выполнить финальное преобразование. Как и в случае с примитивами, объект может быть преобразован всего тремя способами: численным, строковым, булевым.
Самый простой пример это булевое преобразование — любое не примитивное значение всегда приводится к true
, включая пустые объекты и массивы.
Объекты приводятся к примитивам посредством вызова внутреннего метода [[ToPrimitive]]
, который отвечает как за численное, так и за строковое преобразование.
Вот псевдокод реализации метода [[ToPrimitive ]]
:
Методу [[ToPrimitive]]
передаётся два аргумента: входящее значение и предпочтительный тип для преобразования: Number
или String
. Второй аргумент является опциональным.
Как для строкового так и для численного преобразования используются два метода объекта: valueOf
и toString
. Оба метода объявлены в Object.prototype
, а значит доступны для всех производных типов, таких как Date
, Array
и т.д.
В общих чертах алгоритм выглядит следующим образом:
- Если входящее значение уже является примитивом, ничего не делать и просто вернуть его.
- Вызвать
input.toString()
, если результат примитив — вернуть его. - Вызвать
input.valueOf()
, если результат примитив — вернуть его. - Если ни один из методов не вернул примитив — бросить ошибку
TypeError
.
При численном преобразовании сначала вызывается метод valueOf()
, а уже затем toString()
. При строковом преобразовании наоборот — сначала происходит вызов toString()
, а уже потом valueOf()
.
Большинство встроенных типов не имеют метода valueOf
или же имеют valueOf
, который возвращает свой собственный объект this
, который игнорируется, так как this
не является примитивом. Вот почему численное и строковое преобразование в большинстве случаев работает одинаково — оба в конечном итоге вызывают метод toString()
.
Разные операторы могут вызывают строковое или численное преобразование при помощи параметра preferredType
. Но существует два исключения: нестрогое равенство ==
и бинарный оператор +
, которые вызывают режим преобразования по умолчанию (preferredType
не указан или равен default
). В таком случае, большинство встроенных типов подразумевают численное преобразование, за исключением Date
, который предпочитает строковое преобразование.
Вот пример преобразования Date
:
Вы можете переопределить методы toString()
и valueOf()
для того, чтобы повлиять на логику преобразования объектов в примитив.
Обратите внимание, как obj + ‘’
возвращает строку 101
. Оператор +
вызывает преобразование в режиме по умолчанию, и как упоминалось выше, Object
подразумевает численное преобразование в таком случае, используя сначала метод valueOf()
, а затем уже toString()
.
Метод ES6 Symbol.toPrimitive
В ES5 вы можете повлиять на логику преобразования объекта в примитив, переопределив методы toString()
и valueOf()
.
В ES6 вы можете пойти дальше и полностью заменить внутреннюю процедуру метода [[ToPrimitive]]
, реализовав метод [Symbol.toPrimitive]()
у объекта.
Примеры
Вооружившись теорией, давайте вернёмся к нашим примерам:
true + false // 1
12 / "6" // 2
"number" + 15 + 3 // 'number153'
15 + 3 + "number" // '18number'
[1] > null // true
"foo" + + "bar" // 'fooNaN'
'true' == true // false
false == 'false' // false
null == '' // false
!!"false" == !!"true" // true
['x'] == 'x' // true
[] + null + 1 // 'null1'
[1,2,3] == [1,2,3] // false
{}+[]+{}+[1] // '0[object Object]1'
!+[]+[]+![] // 'truefalse'
new Date(0) - 0 // 0
new Date(0) + 0 // 'Thu Jan 01 1970 02:00:00(EET)0'
Ниже представлен детальный разбор того, как вычисляется каждое из выражений.
Бинарный оператор +
вызывает численное преобразование для true
и false
:
true + false
==> 1 + 0
==> 1
Оператор деления /
вызывает численное преобразование строки 6
:
12 / '6'
==> 12 / 6
==>> 2
Оператор +
выполняется слева направо, поэтому сначала выполнится выражение “number” + 15
. Поскольку один из операндов это строка, оператор +
вызовет строковое преобразование числа 15
и последующую конкатенацию двух строк. На следующем этапе выражение “number15” + 3
выполнится таким же образом.
“number” + 15 + 3
==> "number15" + 3
==> "number153"
Сначала выполняется сложение чисел 15 + 3
. На данном этапе никакого преобразования не нужно, так как оба операнда являются числами. Затем выполняется выражение 18 + ‘number’
, и так как один из операндов является строкой, то вызывается строковое преобразование для числа 18
, и последующая конкатенация двух строк.
15 + 3 + "number"
==> 18 + "number"
==> "18number"
Оператор сравнения >
вызывает численное преобразование для [1]
и null
[1] > null
==> '1' > 0
==> 1 > 0
==> true
Унарный оператор +
имеет более высокий приоритет чем бинарный оператор +
. Поэтому выражение + 'bar'
выполняется первым. Унарный плюс вызывает численное преобразования строки bar
. Так как эта строка не представляет собой корректное число, результатом будет NaN
. Следующим шагом выполнится выражение 'foo' + NaN
.
"foo" + + "bar"
==> "foo" + (+"bar")
==> "foo" + NaN
==> "fooNaN"
Оператор сравнения ==
вызывает численное преобразование, поэтому строка true
конвертируется в NaN
, а правый операнд true
станет 1
.
'true' == true
==> NaN == 1
==> falsefalse == 'false'
==> 0 == NaN
==> false
Оператор ==
обычно вызывает численное преобразование, но не в случае с null
. Действует исключение из правил: null
равен только null
или undefined
и ничему другому.
null == ''
==> false
Оператор !!
конвертирует строки true
и false
в булевое значение true
, так как это не пустые строки. А дальше оператор ==
просто сравнивает два булевых значения безо всяких преобразований.
!!"false" == !!"true"
==> true == true
==> true
Оператор ==
вызывает численное преобразование для массива. Метод массива valueOf()
возвращает сам массив, а значит результат игнорируется, так как не является примитивом. Далее, вызывается метод массива toString()
, который конвертирует [‘x’]
в строку ‘x’
.
['x'] == 'x'
==> 'x' == 'x'
==> true
Оператор +
вызывает численное преобразование массива. Метод массива valueOf()
вернёт сам массив, поэтому результат игнорируется, поскольку не является примитивом.
Далее выполняется выражение ’’ + null + 1
.
[] + null + 1
==> '' + null + 1
==> 'null' + 1
==> 'null1'
Логические операторы ||
и &&
преобразовывают операнды к булевому значению, но всегда возвращают оригинальное значение операнда (не булевое). 0
станет false
, а поскольку 0
является не пустой строкой, то конвертируется в true
. {}
пустой объект тоже становится true
.
0 || "0" && {}
==> (0 || "0") && {}
==> (false || true) && true // internally
==> "0" && {}
==> true && true // internally
==> {}
В данном примере никакого преобразования не требуется, потому что оба операнда одного типа. Так как оператор ==
сравнивает объекты по ссылке, а не по значению, а данные массивы являются двумя разными экземплярами, результатом будет false
.
[1,2,3] == [1,2,3]
==> false
Все операнды являются не примитивами, поэтому +
вызывает численное преобразование. Методы Object.valueOf()
и Array.valueOf()
возвращают самих себя, соответственно будут проигнорированы. В качестве запасного варианта, вызывается метод toString()
. Трюк в том, что первый {}
воспринимается движком не как создание объекта, а как объявление пустого блока кода и поэтому игнорируется. Выполнение начинается с выражения +[]
, которое преобразуется в пустую строку посредством метода toString()
, и далее в 0
.
{}+[]+{}+[1]
==> +[]+{}+[1]
==> 0 + {} + [1]
==> 0 + '[object Object]' + [1]
==> '0[object Object]' + [1]
==> '0[object Object]' + '1'
==> '0[object Object]1'
Данный пример лучше объяснить пошагово с точки зрения приоритета выполнения операторов:
!+[]+[]+![]
==> (!+[]) + [] + (![])
==> !0 + [] + false
==> true + [] + false
==> true + '' + false
==> 'truefalse'
Оператор -
вызывает численное преобразование для объекта Date
. Date.valueOf()
возвращает количество миллисекунд прошедших с начала Unix эпохи (в данном случае 0
).
new Date(0) - 0
==> 0 - 0
==> 0
Оператор +
вызывает преобразование по умолчанию. Date
, как исключение, подразумевает строковое преобразование, поэтому используется метод toString()
, а не valueOf()
.
new Date(0) + 0
==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)' + 0
==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)0'
Источники
Я бы хотел порекомендовать отличную книгу “Understanding ES6” написанную Nicholas C. Zakascholas. Книга является отличным ресурсом для изучения ES6 с достаточным уровнем освещения: не слишком поверхностно, и в тоже время не погружается в механику работы движка черезчур.
Ещё одна отличная книга, на этот раз по ES5 — SpeakingJS написанная Axel Rauschmayer.
Современный учебник Javascript — https://learn.javascript.ru/,
в особенности эти две страницы, посвящённые преобразованию типов.
JavaScript Comparison Table — https://dorey.github.io/JavaScript-Equality-Table/
wtfjs — небольшой блог о языке, который мы все так любим, не смотря на то что он дает много поводов его ненавидеть — https://wtfjs.com/
JavaScript Numbers Parsing How to Tips and Tricks
Photo by Volkan Olmez on UnsplashJavascript — это язык широкого спектра, который полезен для любого типа веб-приложений. Это сделано намеренно, но для веб-приложений, таких как платформа электронной коммерции или веб-сайт, эта динамика может быть источником довольно запутанного преобразования чисел и, следовательно, во многих случаях приводит к чепухе. Если в вашем приложении есть формы ввода, которые могут использовать различные форматы для чисел, таких как 10e5 или 0b11, или даже единиц, таких как 10px или 10em, и вы хотите проанализировать их все, это может быть довольно тяжелым для вас временем.
В JavaScript арифметические операции используются с любым типом. Но это приводит к приведению типов, когда оба типа не совпадают, что приводит к удивительным результатам:
Используя оператор «+», пусть JavaScript выбирает строковый тип и добавляет к нему второй. Если вы используете оператор «*» или «-», числовой тип выигрывает гонку. Поэтому последние два приводят к правильным арифметическим операциям.
Чтобы убедиться, что назначенные арифметические операции приводят к правильным результатам, данные, полученные из полей ввода, всегда должны быть явно преобразованы в правильный тип. Здесь на помощь приходит JavaScript, который предлагает для этого 3 функции. К сожалению, все они работают по-разному. Пример при анализе значения из CSS-Style-Definition:
КАК видите, первый отсекает десятичные разряды, второй точен, а третий вообще не помогает.
У нас есть вопрос, на который нужно ответить: как я могу анализировать свои числа в JavaScript и больше не ошибаться?
Чтобы приблизиться к ответу, нам нужно фундаментальное понимание чисел в JavaScript. В отличие от строго типизированных языков, где большинство чисел разделено на части, как в C#, где беззнаковое целое может быть только положительным, а число с плавающей запятой — десятичным, в JavaScript ничего подобного нет. Оператор typeOf продемонстрирует, что:
Любой тип номера сжимается в номер типа. В JavaScript всего шесть типов, эти четыре отображаемых: строка, числовое логическое значение и объект — почти все из них.
Мы можем создать значение числового типа с числовым литералом. Обычными числовыми литералами являются целые (10) и числа с плавающей запятой со знаком (-34,8). Но в JS их больше:
Все четыре из них представляют одно и то же значение. Первая — научная запись, вторая — шестнадцатеричная, третья — двоичная, четвертая — восьмеричная. Отличается префиксами. JavaScript также различает значения с конечными номерами и значения с бесконечными номерами:
Хотя математическое деление на ноль не определено, JavaScript это позволяет. Он производит специальное значение +/- бесконечность. Все другие не определенные арифметические параметры приводят к NaN (не число). Что это за неопределенные? Бесконечность / Бесконечность. JavaScript следует спецификации IEEE-754 для чисел с плавающей запятой!
JavaScript, о JavaScript! Все специальные значения являются типами, такими как число, хотя в тексте написано «Не число»!
Чтобы убедиться, что два значения определены арифметически, необходимо проверить, являются ли эти числа конечными. К счастью, JS предоставляет статическую функцию Number.IsFinite: 9.0003
Но будьте осторожны (JS — это действительно джунгли)! Есть разница между введенной в ES6 функцией Number.IsFinite и глобальной функцией IsFinite.
Глобальный тайно конвертирует другие типы в числа, что приведет к любопытным результатам. Статический принимает только переменные типа number. То же самое касается функции брата: IsNaN и Number.IsNaN.
Помните об этом, и вы станете ближе к своей цели, чем раньше!
Иногда входные значения (из форм или полей ввода) не имеют номера типа. Поэтому необходима конверсия. JavaScript также предлагает нам 3 возможности их разбора.
- Number
- parseFloat/ Number.parseFloat
- parseInt / Number.parseInt
ParseInt и ParseFloat также имеют брата по номеру. Конструктор, но они, к счастью, работают одинаково. Обе функции преобразуют строку в число. Они будут читать до тех пор, пока не получат первый нечисловой символ, а затем мгновенно прервут процесс преобразования. Пробелы, символы перевода строки, возвраты и табуляторы игнорируются. Когда строка содержит только неконвертируемые символы или пуста (null), оба возвращают NaN в качестве вывода.
Здесь мы ясно видим разницу. ParseInt мгновенно расценивает десятичную точку как недопустимую и прерывает преобразование. Результат всегда целое число. Он преобразует другие числовые литералы и специальные значения:
Но существуют ограничения для литералов. С одной стороны, ParseInt может преобразовывать шестнадцатеричные, но отчаянно нуждается в преобразованиях для восьмеричных, двоичных и научно-экспоненциальных представлений. С другой стороны, ParseFloat может читать научно-экспоненциальную нотацию, но не остальные три.
Это потенциал для скрытых ошибок и ошибок во время преобразования. Без этих явных знаний вы не знаете об этих незначительных различиях и кодируете свой путь через задачу, не имея ни малейшего понятия. Самое хитрое, что префиксы просто обрезаются, а остальное видится как допустимое значение. Никаких выводов о том, какие значения были раньше, вообще нет.
Если значение не является строкой, эти функции также преобразуют его в строку. Проблема снова: String имеет свой собственный набор правил:
- String(undefined) => undefined
- String(null) => null
- String(true) / String(false) => true / false
Является ли объект типа ввода, тогда String пытается вызвать метод ToString() из самого объекта, чтобы получить примитивный тип, все остальные типы, кроме объекта. Если это не достижение цели, то происходит вызов valueOf(). Если это тоже не достижение цели, будет выброшена ошибка.
Имея эти правила преобразования в своем мозгу, преобразования понятны:
Преобразование массива в одно число объясняется приведенными выше правилами:
- parseFloat([1,2,3]) преобразует ввод с помощью метода String в строку 2,3]) вызвал метод toString, поскольку массивы являются объектами в JS
- [1,2,3].toString() приводит к строке «1,2,3»
- Преобразование продолжается с помощью parseFloat([1 ,2,3])
- parseFloat читает до первого неконвертируемого символа, запятую после 1 и останавливает преобразование
- Таким образом, результатом будет 1
Третий способ преобразовать что-либо в число — это глобальный метод Number. Применяются те же правила, что и для parseInt и parseFloat. Вся строка должна быть конвертируемой, смешанные конвертации невозможны и не принесут результата. Существуют следующие специальные правила:
- «», null и false будут преобразованы в 0
- Истинные результаты в 1
- Неопределенные результаты в NaN
- Все префиксы разрешены
Если ввод имеет тип объекта, то эта функция делает это в точности наоборот, чем parseInt и parseFloat. Сначала он преобразуется с помощью valueOf, и если это не удается, он вызывает преобразование toString. Из-за этого есть некоторые незначительные отличия:
Из-за этого объекты-даты используются с числом по-особому. Вызов valueOf возвращает метку времени UNIX, которая также имеет тип number и вообще не будет преобразована, а будет просто передана по конвейеру. Массивы возвращают себя при вызове valueOf и из-за этого неудачного преобразования (Number требуется число) приводят к вызову метода toString. Возврат строки со специальными символами приводит к прерванному преобразованию. Результат NaN.
Ниже я резюмирую, как работают эти три функции.
Теперь возникает вопрос: какую из этих трех функций следует использовать для преобразования заданного ввода для правильного расчета с преобразованиями?
Если требуются как числа с плавающей запятой, так и целые числа, parseInt не подходит. Кроме того, принимаются ли шестнадцатеричные литералы? — parseFloat тоже не тот. Использование Number может быть вполне правильным, но может привести к странным результатам, когда речь идет о датах, логических значениях, null или некоторых отдельных объектах. Кроме того, преобразование может привести к NaN и Infinity, которые не являются допустимыми значениями для арифметических операций.
Должен быть фильтр, прежде чем преобразование может быть выполнено успешно. Функция, которая проверяет, допустимы ли входные данные для преобразования в конечное число.
Некоторым фреймворкам также удалось ответить на этот вопрос. JQuery предлагает метод IsNumeric. Во время выпусков разных версий JQuery немного менялась и реализация. Выберите нужную версию для ваших нужд: ниже я суммирую различные фреймворки и их способность преобразовывать значения в числа.
Обзор фреймворков и их преобразованийПреобразование чисел в JavaScript — нетривиальная задача. Выберите функцию, чтобы конвертировать ваши числа с умом. Отфильтруйте нежелательные значения (NaN, Infinity, Date с временными метками Unix), в противном случае последуют нежелательные и странные результаты. Напишите модульные тесты для ваших методов фильтрации и преобразования.
Следовательно, безопасное преобразование должно выполняться в несколько шагов:
Проверить, является ли введенное значение числовым значением
Преобразовать значение в число
Что каждый разработчик JavaScript должен знать о плавающих точках
Автор Xuanyi Chew
В какой-то момент своей карьеры разработчик JavaScript сталкивается со странными ошибками — элементарной математикой, которая кажется бессмысленной. И в какой-то момент ей скажут, что числа в JavaScript на самом деле представляют собой числа с плавающей запятой. Любые попытки понять плавающие точки или почему они так странно себя ведут, в итоге приведут к длинной и трудной для чтения статье. Цель этой статьи — упростить работу с плавающей запятой для разработчиков JavaScript.
В этой статье предполагается, что читатель знаком с представлением по основанию 2 (двоичных) чисел по основанию 10 (десятичных) (т. е. 1 записывается как 1 b
, 2 – 10 b
, 3 – ). 11 b
, 4 — 100 b
… и т. д.). Чтобы еще больше прояснить ситуацию, в этой статье слово «десятичный» в основном относится к десятичному представлению чисел (например: 2,718) в машине. Слово «двоичный» в этой статье относится к машинному представлению. Письменные представления будут обозначаться как «база-10» и «база-2» соответственно.
Плавающие запятые
Чтобы понять, что такое плавающая запятая, мы сначала начнем с идеи, что существует много типов чисел, которые мы рассмотрим. Мы называем 1
целым числом — это целое число без дробных значений.
½ — это то, что называется дробью. Это означает, что целое число 1 делится на 2. Концепция дробей очень важна для получения чисел с плавающей запятой.
0,5
обычно называют десятичным числом. Однако необходимо сделать очень важное различие – 0,5
на самом деле является десятичным (с основанием 10) представлением дроби ½. Вот как ½ представляется в виде числа с основанием 10 — в этой статье мы будем называть это точечной нотацией. Мы называем 0,5
конечным представлением, потому что числа в представлении для дроби конечны — больше нет чисел после 5
в 0,5
. Бесконечным представлением будет, например, 0,3333...
при представлении ⅓. Опять же, эта идея будет важна позже в нашем обсуждении.
Существует другой способ представления чисел, отличный от целых чисел, дробей или десятичных представлений. Возможно, вы действительно видели это раньше. Выглядит это примерно так: 6,022 x 10 23
(Дело: это число Авогадро, количество молекул в моле химического раствора). Это широко известно как стандартная форма или научная нотация. Эту форму можно обобщить до следующего вида:
D 1 .D 2 D 3 D 4 ...D p x B E
Общая форма называется с плавающей запятой .
Последовательность p
цифр D, D 1 .D 2 D 3 D 4 ...D p
называются Significs.and p
— это число значащих цифр, обычно называемое точностью . x
следует за мантиссом и является частью системы обозначений (символ умножения, который будет использоваться в этой статье, будет *
). Затем следует цифра Base , за которой следует Exponent . Показатель степени может быть положительным или отрицательным числом.
Прелесть чисел с плавающей запятой в том, что их можно использовать для представления любого числа . Например, целое число 1
можно представить как 1,0 x 10 0
. Скорость света можно представить как 2,99792458 х 10 8
метра в секунду. 1/2 может быть представлено по основанию 2 как 0,1 х 2 0
.
Удаление точки счисления
В приведенных выше примерах мы по-прежнему сильно привязаны к точке счисления (точка в числе). Это создает некоторые проблемы, когда дело доходит до представления чего-либо в двоичном виде. Учитывая произвольную плавающую точку, скажем, π, мы можем представить ее как плавающую точку как таковую: 3,14159 x 10 0
. В двоичном виде это будет выглядеть примерно так: 11.00100100 001111...
. Предполагая, что число представлено 16-битным способом, это означает, что цифры будут располагаться в машине следующим образом: 11001001000011111
. Теперь вопрос в следующем: где должна быть точка счисления? Это даже еще не включает показатель степени (мы неявно предполагаем, что основание равно основанию 2).
Что делать, если число было 5.14159
? Целая часть будет 101
вместо 11
, что потребует еще одного битового поля. Конечно, мы могли бы указать, что первые n бит поля относятся к целой части (то есть слева от точки счисления), а остальные — к дробным частям, но это тема для другой статьи о числах с фиксированной точкой.
Как только мы удалим точку счисления, нам останется только отслеживать две вещи: показатель степени и мантиссу. Мы можем удалить точку счисления, применив формулу преобразования, сделав обобщенную плавающую точку такой: p-1 ) x B E
Отсюда мы получаем большую часть наших двоичных чисел с плавающей запятой. Обратите внимание, что мантиссы теперь являются целыми числами. Это значительно упрощает хранение числа с плавающей запятой в машине. Фактически, наиболее широко используемый метод представления чисел с плавающей запятой в двоичном формате — это формат IEEE 754.
IEEE 754
Представление чисел с плавающей запятой в JavaScript соответствует формату, указанному в IEEE-754. В частности, это формат двойной точности, означающий, что для каждой с плавающей запятой выделяется 64 бита. Хотя это не единственный способ представления чисел с плавающей запятой в двоичном формате, это наиболее широко используемый формат. Формат представлен в виде 64-битного двоичного кода:
Можно заметить, что схема машинного представления немного отличается от письменного представления числа с плавающей запятой — это вопрос соглашения. Из 64 доступных битов 1 бит используется для знака — является ли число положительным или нет. 11 бит используются для экспоненты – это позволяет использовать до 1024
в качестве экспоненты. Остальные 52 бита отведены под мантиссу. Если вы когда-нибудь задумывались, почему в JavaScript существуют такие вещи, как +0
и -0
, знаковый бит объясняет это совсем немного — все числа в JavaScript имеют знаковый бит. Infinity
и NaN
также закодированы с плавающей запятой — с 2047
в качестве специального показателя степени. Если мантисса равна 0
, это либо положительное, либо отрицательное число Бесконечность
. Если это не так, то это NaN
.
Ошибки округления
Закончив знакомство с плавающей запятой, мы переходим к более щекотливой теме — ошибкам округления. Это бич всех разработчиков, которые работают с числами с плавающей запятой, разработчиков JavaScript вдвойне, потому что единственный числовой формат, доступный разработчикам JavaScript, — это числа с плавающей запятой.
Ранее упоминалось, что дроби типа ⅓ не могут быть конечно представлены в десятичной системе счисления. На самом деле это верно для всех чисел, представленных в любом основании. Например, в числах с основанием 2 1/10 не может быть конечно представлено. Он представлен как 0.00110011001100110011...
. Обратите внимание, что 0011
повторяется бесконечно. Именно из-за этой особенности возникают ошибки округления.
Но сначала немного об ошибках округления. Рассмотрим одно из самых известных иррациональных чисел, Пи: 3,141592653589793...
. Большинство людей очень хорошо помнят первые 5 мантиссов ( 3,1415
) — это пример округления в меньшую сторону, который мы будем использовать в этом примере. Следовательно, ошибка округления может быть рассчитана следующим образом:
(R - A) / B p-1
…где R
означает округленное число, а A
— фактическое число. B
— это база, как было показано ранее, как и p
, то есть точность. Таким образом, часто вспоминаемое число Пи имеет ошибку округления: 0,00009265...
).
Хотя это звучит не так серьезно, давайте попробуем эту идею с числами с основанием 2. Рассмотрим дробь 1/10. В базе 10 это записывается как 0,1
. В базе 2 это: 0.0011001100110011. ..
. Предполагая, что мы округлим всего до 5 мантисса, это будет записано как 0,0001
. Но 0,0001
в двоичном виде на самом деле 1/16 (или 0,0625)! Это означает, что ошибка округления составляет 0,0375, что довольно велико. Представьте, что вы выполняете базовую математику, например 0,1 + 0,2
, и ответ возвращает 0,2625
!
К счастью, спецификация с плавающей запятой, которую использует ECMAScript, указывает до 52 мантиссы, поэтому ошибки округления довольно малы — в спецификации конкретно указаны ожидаемые ошибки округления для большинства чисел. Поскольку выполнение арифметических операций с плавающей точкой со временем приводит к накоплению ошибок, спецификация IEEE 754 также включает специальные алгоритмы для математических операций.
Однако следует отметить, что, несмотря на все это, ассоциативность арифметических операций (таких как сложение, вычитание, умножение и вычитание) не гарантируется при работе с числами с плавающей запятой, даже при высокой точности. Под этим я подразумеваю, что ((x + y) + a + b)
не обязательно равно ((x + y) + (a + b))
.
И это бич разработчиков JavaScript. Например, в JavaScript 0,1 + 0,2 === 0,3
даст ложь
. Надеюсь, теперь вы понимаете, почему. Хуже, конечно, то, что ошибки округления складываются с каждой последующей математической операцией, выполняемой над ним.
Обработка чисел с плавающей запятой в JavaScript
Было много предложений, как хороших, так и плохих, когда дело доходит до работы с числами JavaScript. Большинство из этих предложений связаны с округлением чисел в JavaScript до или после арифметических операций.
Среди менее удачных предложений, которые я встречал до сих пор, — сохранение всего в виде целого числа (не типа) для операций, а затем форматирование его для отображения. Пример можно увидеть в Stripe — суммы хранятся в центах. У этого есть заметная проблема — не все валюты в мире на самом деле десятичные (Маврикия). Кроме того, в мире есть валюты, в которых нет субъединиц (японская иена), или не 100 субъединиц (иорданские динары), или более одной субъединицы (китайские юани). В конце концов, вы бы просто воссоздали плавающую точку — возможно, тоже плохо.
Лучшими предложениями по обработке чисел с плавающей запятой, которые я видел, было использование должным образом протестированных библиотек, таких как sinfuljs или mathjs. Лично я предпочитаю mathjs (но на самом деле, для всего, что связано с математикой, я бы даже близко не подходил к JavaScript). BigDecimal также чрезвычайно полезен, когда необходимо выполнить математические вычисления с произвольной точностью.
Еще один часто повторяемый совет — использовать встроенные методы toPrecision()
и toFixed()
для чисел. Большое предупреждение всем, кто думает об их использовании — эти методы возвращают строки. Итак, если у вас есть что-то вроде:
функция foo(x, y) { вернуть x.toPrecision() + y. toPrecision() } > фу(0.1, 0.2) "0.10.2"
Встроенные методы toPrecision()
и toFixed()
предназначены только для отображения. Используйте с осторожностью! Теперь идите вперед и размножайтесь (безопасно)!
Заключение
Числа в JavaScript — это числа с плавающей запятой. Из-за несоответствий при представлении чисел по основанию 2, а также из-за конечной машины у нас остается формат, заполненный ошибками округления. В этой статье объясняются эти ошибки округления и причины их возникновения. Всегда используйте хорошую библиотеку для чисел вместо создания собственной.
Брайан Ринальди
Брайан Ринальди — основатель Flippin’ Awesome. Брайан работает менеджером контента для разработчиков в Telerik (хотя этот сайт не связан с его работодателем) и занимается тем, что команда по связям с разработчиками создает первоклассный контент для сообщества веб-разработчиков. Ранее Брайан занимался публикацией контента для разработчиков в формате HTML, CSS и JavaScript для Adobe Developer Connection в Adobe.
На протяжении многих лет Брайан публиковался в различных технических изданиях, выступал на многочисленных конференциях и мероприятиях и работал техническим редактором ряда книг.
Вы можете прочитать архив блога Брайана с более чем 9-летним содержанием на remote Synthetic.com (он все еще публикует сообщения, но нечасто). Вы можете найти полный список прошлых публикаций и презентаций Брайана. Следите за новостями Брайана в Твиттере @remotesynth.
Связь жестокого обращения с детьми и дисфункции в семье со многими основными причинами смерти взрослых. Исследование неблагоприятных детских переживаний (ACE)
. 1998 г., май; 14(4):245-58.
дои: 10.1016/s0749-3797(98)00017-8.
В. Дж. Фелитти 1 , Р. Ф. Анда, Д. Норденберг, Д. Ф. Уильямсон, А. М. Шпиц, В. Эдвардс, М. П. Косс, Дж. С. Маркс
Принадлежности
принадлежность
- 1 Департамент профилактической медицины, Медицинская группа Перманенте Южной Калифорнии (Kaiser Permanente), Сан-Диего 92111, США.
- PMID: 9635069
- DOI: 10.1016/s0749-3797(98)00017-8
В. Дж. Фелитти и соавт. Am J Prev Med. 1998 май.
. 1998 г., май; 14(4):245-58.
doi: 10.1016/s0749-3797(98)00017-8.
Авторы
В. Дж. Фелитти 1 , Р. Ф. Анда, Д. Норденберг, Д. Ф. Уильямсон, А. М. Шпиц, В. Эдвардс, М. П. Косс, Дж. С. Маркс
принадлежность
- 1 Департамент профилактической медицины, Медицинская группа Перманенте Южной Калифорнии (Kaiser Permanente), Сан-Диего 92111, США.
- PMID: 9635069
- DOI: 10.1016/s0749-3797(98)00017-8
Абстрактный
Фон: Взаимосвязь рискованного для здоровья поведения и заболеваний во взрослом возрасте с широтой подверженности эмоциональному, физическому или сексуальному насилию в детстве и семейной дисфункцией в детстве ранее не описывалась.
Методы: Анкета о неблагоприятном детском опыте была разослана по почте 13 494 взрослым, прошедшим стандартное медицинское обследование в крупной HMO; 9 508 (70,5%) ответили. Были изучены семь категорий неблагоприятных детских переживаний: психологическое, физическое или сексуальное насилие; насилие над матерью; или проживание с членами семьи, которые злоупотребляли психоактивными веществами, психически больными или склонными к суициду, или когда-либо находились в заключении. Затем количество категорий этих неблагоприятных детских переживаний сравнивали с показателями рискованного поведения взрослых, состояния здоровья и болезней. Логистическая регрессия использовалась для корректировки влияния демографических факторов на связь между кумулятивным числом категорий воздействия в детстве (диапазон: 0–7) и факторами риска основных причин смерти во взрослой жизни.
Полученные результаты: Более половины респондентов сообщили как минимум об одной, а одна четвертая сообщила о > или = 2 категориях воздействия в детстве. Мы обнаружили градуированную взаимосвязь между количеством категорий воздействия в детстве и каждым из изученных видов поведения и заболеваний, связанных с риском для здоровья взрослых (P < 0,001). У лиц, подвергшихся четырем или более категориям воздействия в детстве, по сравнению с теми, кто не испытал ни одного, риск для здоровья в отношении алкоголизма, наркомании, депрессии и попытки самоубийства был в 4–12 раз выше; 2–4-кратное увеличение курения, плохая самооценка здоровья, > или = 50 половых партнеров и заболевания, передающиеся половым путем; и 1,4-1,6-кратное увеличение физической активности и тяжелого ожирения. Количество категорий неблагоприятных воздействий в детском возрасте продемонстрировало градуированную связь с наличием заболеваний у взрослых, включая ишемическую болезнь сердца, рак, хронические заболевания легких, переломы скелета и заболевания печени. Семь категорий неблагоприятных детских переживаний были тесно взаимосвязаны, и люди с несколькими категориями воздействия в детстве, вероятно, будут иметь несколько факторов риска для здоровья в более позднем возрасте.
Выводы: Мы обнаружили сильную градуированную взаимосвязь между широтой подверженности жестокому обращению или домашним дисфункциям в детстве и множественными факторами риска для нескольких основных причин смерти у взрослых.
Похожие статьи
ПЕРЕПЕЧАТАНИЕ: Связь жестокого обращения с детьми и дисфункций в семье со многими основными причинами смерти взрослых: исследование неблагоприятных детских переживаний (ACE).
Felitti VJ, Anda RF, Nordenberg D, Williamson DF, Spitz AM, Edwards V, Koss MP, Marks JS. Фелитти В.Дж. и соавт. Am J Prev Med. 2019 июнь; 56 (6): 774-786. doi: 10.1016/j.amepre.2019.04.001. Am J Prev Med. 2019. PMID: 31104722
Жестокое обращение в детстве, дисфункция домохозяйства и риск попытки самоубийства на протяжении всей жизни: результаты исследования неблагоприятных детских переживаний.
Дубе С.Р., Анда Р.Ф., Фелитти В.Дж., Чепмен Д.П., Уильямсон Д.Ф., Джайлз В.Х. Дубе С.Р. и соавт. ДЖАМА. 2001 г., 26 декабря; 286(24):3089-96. дои: 10.1001/jama.286.24.3089. ДЖАМА. 2001. PMID: 11754674
Суицидальная идея.
Хармер Б., Ли С., Дуонг ТВХ, Саадабади А. Хармер Б. и др. 2022 г., 18 мая. В: StatPearls [Интернет]. Остров сокровищ (Флорида): StatPearls Publishing; 2022 янв.–. 2022 г., 18 мая. В: StatPearls [Интернет]. Остров сокровищ (Флорида): StatPearls Publishing; 2022 янв.–. PMID: 33351435 Бесплатные книги и документы.
Связь подверженности сексуальному насилию в детстве с другими формами жестокого обращения, пренебрежения и семейной дисфункции в детстве.
Донг М. , Анда Р.Ф., Дубе С.Р., Джайлз В.Х., Фелитти В.Дж. Донг М. и др. Жестокое обращение с детьми Негл. 2003 июнь; 27 (6): 625-39. doi: 10.1016/s0145-2134(03)00105-4. Жестокое обращение с детьми Негл. 2003. PMID: 12818611
Домашнее насилие во Флориде.
Хаусман Б., Семен Г. Хаусман Б. и др. 2022 г., 1 мая. В: StatPearls [Интернет]. Остров сокровищ (Флорида): StatPearls Publishing; 2022 янв.–. 2022 г., 1 мая. В: StatPearls [Интернет]. Остров сокровищ (Флорида): StatPearls Publishing; 2022 янв.–. PMID: 29630246 Бесплатные книги и документы.
Посмотреть все похожие статьи
Цитируется
Связь между стрессорами в детстве, тревогой, связанной с COVID-19, и качеством сна взрослых во время третьей волны пандемии COVID-19 в Израиле.
Хаимов И., Шепсенволь О., Коэн А. Хаймов И. и др. Естественный научный сон. 2022, 20 сентября; 14:1665-1675. doi: 10.2147/NSS.S378271. Электронная коллекция 2022. Естественный научный сон. 2022. PMID: 36164409 Бесплатная статья ЧВК.
Эпигенетическое старение и воспринимаемый психологический стресс в пожилом возрасте.
Веттер В.М., Древелис Дж., Соммерер Ю., Калиес К.Х., Регитц-Загросек В., Бертрам Л., Герсторф Д., Демут И. Веттер В.М. и соавт. Трансл Психиатрия. 2022 26 сентября; 12 (1): 410. doi: 10.1038/s41398-022-02181-9. Трансл Психиатрия. 2022. PMID: 36163242 Бесплатная статья ЧВК.
Продольный мониторинг динамики ансамбля префронтальной коры открывает новое понимание привыкания к стрессу.
Патель С., Джонсон К., Аданк Д., Росас-Видаль Л.Е. Патель С. и др. Нейробиол стресс. 2022 3 сентября; 20:100481. doi: 10.1016/j.ynstr.2022.100481. электронная коллекция 2022 сент. Нейробиол стресс. 2022. PMID: 36160815 Бесплатная статья ЧВК.
Неблагоприятный опыт детства у пациентов с неврологическими заболеваниями.
Мендизабал А., Натан К.Л., Ханханян П., Анто М., Клайберн С., Акаба-Беррокаль А., Брин Л., Даходвала Н. Мендизабал А. и соавт. Нейрол Клин Практ. 2022 февраль;12(1):60-67. doi: 10.1212/CPJ.0000000000001134. Нейрол Клин Практ. 2022. PMID: 36157623
Наблюдение за родительским арестом как предиктор интернализации и экстернализации симптомов ребенка во время и после родительского тюремного заключения.