ECMAScript 2026: Math.sumPrecise и Map.getOrInsert
-

В этой статье разберёмся с двумя полезными методами, которые появились в ECMAScript 2026. Они решают реальные проблемы разработчиков: точность вычислений в финансовых приложениях и удобство работы с картами. Эти инструменты уже в спецификации и готовы к использованию.
Методы простые, но мощные. Они экономят код, снижают ошибки и делают логику понятнее. Если вы работаете с JavaScript на боевом уровне, стоит знать, как они устроены и когда их применять.
Проблема потери точности при суммировании
Когда вы складываете числа в JavaScript обычным способом, может случиться беда. Браузер работает с числами в формате float64, и при операциях теряется точность. Представьте финансовый калькулятор: пользователь вносит 100 долларов, программа теряет копейку, и в итоге вместо нужной суммы на счёте оказывается совсем другое число.
Классический пример - вот такой код:
let values = [1e20, 0.1, -1e20]; let result = values.reduce((a, b) => a + b, 0); console.log(result); // 0Хотя математически ответ должен быть 0.1, JavaScript вернёт 0. Дело в том, что число 1e20 настолько велико, что 0.1 просто теряется при округлении. Наивное суммирование игнорирует эту проблему, поскольку оперирует числами слева направо без учёта масштаба.
Эта проблема критична для:
- Финансовых приложений, где каждая копейка на счёте
- Научных расчётов с очень большими и очень маленькими числами
- Статистических анализов и агрегирования данных
- Любых вычислений, где точность - требование, а не опция
Math.sumPrecise - безошибочное суммирование
Метод Math.sumPrecise() решает эту задачу раз и навсегда. Он принимает iterable с числами и складывает их, используя специальный алгоритм, который максимально точен. Фактически, результат будет таким же, как если бы вы считали с произвольной точностью, а потом конвертировали обратно в float.
Вот как это работает в реальности:
let values = [1e20, 0.1, -1e20]; let preciseResult = Math.sumPrecise(values); console.log(preciseResult); // 0.1 - вот это правильно!Метод особенно полезен для приближения математических констант. Если вы считаете число e через степенной ряд:
function* sequenceOfE() { let member = 1; yield member; for (let i = 1; i < 20; ++i) { member /= i; yield member; } } // Наивное суммирование const regularSum = Array.from(sequenceOfE()).reduce((acc, cur) => acc + cur, 0); console.log(regularSum); // 2.7182818284590455 console.log(Math.abs(Math.E - regularSum)); // 4.44e-16 - большая ошибка // Точное суммирование const preciseSum = Math.sumPrecise(sequenceOfE()); console.log(preciseSum); // 2.718281828459045 console.log(Math.abs(Math.E - preciseSum)); // 0 - идеально!Замечали ошибку в четвёртом знаке после запятой? Math.sumPrecise полностью её устраняет. Это достигается благодаря алгоритму из работы Shewchuk 1996 года, который обрабатывает переполнение и учитывает все детали floating point арифметики.
Важные особенности метода:
- Работает только с числами - если передадите строку или объект, получите ошибку
- Возвращает -0 для пустого массива (это математически правильно, так как -0 - аддитивная единица в float64)
- Работает медленнее наивного суммирования, потому что проводит больше вычислений
- Не поддерживает BigInt - только обычные Number
Примеры использования в реальном коде:
// Финансовые расчёты const prices = [19.99, 29.99, 39.99, 10.01]; const total = Math.sumPrecise(prices); // Правильная сумма // Агрегирование данных с датчиков const measurements = [0.001, 0.002, 0.001, 0.0005]; const average = Math.sumPrecise(measurements) / measurements.length; // Расчёт средневзвешенного значения const weights = [100, 200, 150, 50]; const values = [1.5, 2.0, 1.8, 1.2]; const weighted = Math.sumPrecise( Array.from({ length: weights.length }, (_, i) => weights[i] * values[i]) ) / Math.sumPrecise(weights);Когда использовать Math.sumPrecise
Не спешите заменять все reduce на sumPrecise - это замедлит код. Используйте метод если:
- Точность критична - финансы, научные вычисления, медицина
- Большой диапазон чисел - микронаносекунды рядом с миллиардами
- Много операций суммирования - ошибки накапливаются
- Требования стандартов - GDPR, финрегулирование и подобные
Для простых случаев типа подсчёта элементов массива или суммирования ID обычный reduce будет быстрее и достаточно точен.
Map.getOrInsert - удобство работы с картами
Вторая полезная штука - Map.prototype.getOrInsert(). Это метод, который одновременно проверяет наличие ключа и вставляет значение по умолчанию, если ключа нет. Вместо трёх операций - одна.
До этого разработчики писали так:
const map = new Map(); if (map.has('key')) { return map.get('key'); } else { map.set('key', 'default'); return 'default'; } // Или через паттерн с nullish coalescing map.set('key', map.get('key') ?? 'default'); return map.get('key');Теперь всё просто:
const value = map.getOrInsert('key', 'default');Метод работает так: если ключ найден, вернёт его значение. Если нет - вставит переданное значение и вернёт его. Это эквивалентно первому варианту выше, но читается как одна логичная операция.
Есть и вариант с вычислением значения - Map.prototype.getOrInsertComputed(). Он принимает callback вместо значения и вызывает его только если ключ не найден. Это экономит вычисления, когда значение дорогое:
const map = new Map(); // Callback вызовётся только если ключ 'expensive' не существует const result = map.getOrInsertComputed('expensive', (key) => { console.log('Вычисляю...'); return heavyCalculation(key); });Популярный паттерн - группировка элементов массива по ключам:
const items = [ { category: 'fruit', name: 'apple' }, { category: 'fruit', name: 'banana' }, { category: 'vegetable', name: 'carrot' }, ]; const grouped = new Map(); for (const item of items) { // Получаем или создаём пустой массив для категории grouped.getOrInsert(item.category, []).push(item); } console.log(grouped); // Map { // 'fruit' => [ { category: 'fruit', name: 'apple' }, ... ], // 'vegetable' => [ { category: 'vegetable', name: 'carrot' } ] // }Это гораздо читабельнее, чем если бы вы проверяли наличие ключа каждый раз вручную.
Семейство методов включает также WeakMap.prototype.getOrInsert() и WeakMap.prototype.getOrInsertComputed() для работы с WeakMap. Используется редко, но логика та же.
Практические примеры и сравнение
Примеры применения getOrInsert в боевом коде:
// Подсчёт частоты символов в строке const string = "counting characters in a string"; const charCount = new Map(); for (const char of string) { const counter = charCount.getOrInsert(char, { char, count: 0 }); counter.count++; } console.log(charCount);// Кэширование результатов дорогих операций const cache = new Map(); function expensiveOperation(key) { return cache.getOrInsertComputed(key, (k) => { // Долгое вычисление return complexCalculation(k); }); }// Граф смежности (граф для сетевых алгоритмов) const graph = new Map(); function addEdge(from, to) { graph.getOrInsert(from, []).push(to); graph.getOrInsert(to, []).push(from); }Сравним старый и новый подходы:
Задача Старый способ С getOrInsert Получить или создать 3-4 строки кода 1 строка Вычислить только если нужно Условие + вычисление 1 вызов с callback Читаемость Средняя Высокая Производительность Одинаковая Одинаковая Синтаксическая сахаристость Низкая Высокая В WeakMap ситуация особенная:
const domNodeData = new WeakMap(); const node = document.getElementById('myNode'); // Старый способ - можно потерять ссылку if (!domNodeData.has(node)) { domNodeData.set(node, { listeners: [] }); } // Новый способ - безопаснее const data = domNodeData.getOrInsert(node, { listeners: [] }); data.listeners.push(handler);Мелкие детали, которые важны
Метод getOrInsert не перезаписывает существующие значения. Это критично, потому что иногда в Map лежат null или undefined, и вы хотите их сохранить. Способ старый (с нулишь coalasing) может коварно перезаписать значение:
const map = new Map([['key', undefined]]); // Старый способ - ОШИБКА! map.set('key', map.get('key') ?? 'default'); console.log(map.get('key')); // 'default' - потеряли undefined! // Новый способ - правильно const value = map.getOrInsert('key', 'default'); console.log(value); // undefined - сохранили!Оба метода (getOrInsert и getOrInsertComputed) работают с любыми объектами как ключами - это полезно для Map:
const objKey = { id: 1 }; const map = new Map(); const data = map.getOrInsert(objKey, { count: 0 }); data.count++; console.log(map.get(objKey)); // { count: 1 }Но запомните: ключи в Map сравниваются по ссылке для объектов. Два разных объекта с одинаковыми свойствами - это разные ключи.
Производительность getOrInsert не хуже, чем старый способ. Это просто удобство и безопасность. Не придётся беспокоиться о перезаписи null и undefined.
Когда эти методы точно помогут
Используйте Math.sumPrecise, если:
- Работаете с финансами или денежными единицами
- Складываете очень большие и маленькие числа
- Нужна гарантированная точность для отчётов
- Требования регулятора или стандарта
Используйте Map.getOrInsert, если:
- Часто проверяете наличие и вставляете значение
- Строите группировки, кэши, графы
- Хотите понятнее читать код
- Боитесь случайно перезаписать null или undefined
Безопасность и удобство идут рука об руку в этих методах. Они не добавляют магии, а решают типичные задачи минимальным и очевидным кодом.
Поддержка браузерами и будущее
Оба метода уже в спецификации ECMAScript 2026 и готовы к использованию в современных JavaScript движках. Однако важная оговорка: Math.sumPrecise имеет ограниченную поддержку, поэтому в production коде будьте осторожны и проверяйте, доступен ли метод.
Для getOrInsert поддержка лучше, но всё равно стоит добавить полифилл для старых браузеров, если вы поддерживаете Internet Explorer или древние версии.
В будущем эти методы станут стандартом, и разработчики смогут писать код безопаснее и быстрее. Сейчас уже стоит с ними познакомиться и применять где уместно. Эти небольшие удобства экономят часы отладки и снижают количество багов в production.
Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.
Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост мог бы стать ещё лучше 💗
Зарегистрироваться Войти© 2024 - 2026 ExLends, Inc. Все права защищены.