Перейти к содержанию
  • Лента
  • Категории
  • Последние
  • Метки
  • Популярные
  • Пользователи
  • Группы
Свернуть
exlends
Категории
  1. Главная
  2. Категории
  3. Языки программирования
  4. JavaScript
  5. ECMAScript 2026: Math.sumPrecise и Map.getOrInsert

ECMAScript 2026: Math.sumPrecise и Map.getOrInsert

Запланировано Прикреплена Закрыта Перенесена JavaScript
ecmascript 2026javascriptmath.sumprecise
1 Сообщения 1 Постеры 66 Просмотры
  • Сначала старые
  • Сначала новые
  • По количеству голосов
Ответить
  • Ответить, создав новую тему
Авторизуйтесь, чтобы ответить
Эта тема была удалена. Только пользователи с правом управления темами могут её видеть.
  • hannadevH Не в сети
    hannadevH Не в сети
    hannadev
    написал в отредактировано
    #1

    Обложка: 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 - это замедлит код. Используйте метод если:

    1. Точность критична - финансы, научные вычисления, медицина
    2. Большой диапазон чисел - микронаносекунды рядом с миллиардами
    3. Много операций суммирования - ошибки накапливаются
    4. Требования стандартов - 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.

    1 ответ Последний ответ
    0

    Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.

    Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.

    С вашими комментариями этот пост мог бы стать ещё лучше 💗

    Зарегистрироваться Войти

    Категории

    • Главная
    • Новости
    • Фронтенд
    • Бекенд
    • Языки программирования

    Контакты

    • Сотрудничество
    • info@exlends.com

    © 2024 - 2026 ExLends, Inc. Все права защищены.

    Политика конфиденциальности
    • Войти

    • Нет учётной записи? Зарегистрироваться

    • Войдите или зарегистрируйтесь для поиска.
    • Первое сообщение
      Последнее сообщение
    0
    • Лента
    • Категории
    • Последние
    • Метки
    • Популярные
    • Пользователи
    • Группы