Long Story Short: Числовой тип данных JavaScript может содержать число только до (2⁵³-1) без потери точности. Если вы передадите в переменную JS большее число, все будет вести себя как темная магия.

Большая история

Это об одном из проектов, над которыми я работал. У него был список конфиденциальных данных, которые необходимо отправить в пользовательский интерфейс и вернуть выбранные данные.

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

[
  {
    accId: "XXXXXXXXXX123",
    uniqueId: 9062834707864722
  },
  {
    accId: "XXXXXXXXXX345",
    uniqueId: 9061627692585335
  },
  {
    accId: "XXXXXXXXXX789",
    uniqueId: 9034549534024327
  }
]

Пользователь увидит список учетных записей в раскрывающемся списке. После того, как пользователь выберет учетную запись, пользовательский интерфейс отправит uniqueId в бэкэнд. Серверная часть проверяет, совпадает ли этот uniqueId с uniqueId любой учетной записи, в противном случае генерирует исключение.

Когда эта функция была разработана, все работало нормально. Прошли месяцы, но у нас не было ни одного исключения. Ни один тест не провалился. Теперь, по прошествии 4 месяцев, когда мы активно не работали над этим сервисом, внезапно функциональные тесты начали давать сбой. Мы понятия не имели, почему он начал давать сбой, поскольку мы не меняли код последние 4–5 дней.

Итак, мы приступили к его отладке !!!

В приведенном выше примере, когда пользователь выбрал учетную запись, заканчивающуюся на 345, пользовательский интерфейс отправлял обратно uniqueId: 9061627692585336 вместо 9061627692585335. Таким образом, он не соответствовал базе данных. Это сбивало с толку, потому что оно увеличивалось на 1. Мы попробовали с учетной записью, заканчивающейся на 123, пользовательский интерфейс отправил обратно 9062834707864722, что соответствует базе данных. Так почему же это действовало странно только с некоторыми идентификаторами аккаунтов? Мы пытались воспроизвести ошибку на локальном компьютере, но функция работала нормально на локальном компьютере. (Работает на моей машине 😛)

Небольшая справка о том, как мы создавали uniqueId: мы использовали функцию Java System.nanoTime () в сочетании с хэш-кодом строки идентификатора учетной записи, которая всегда предоставляла уникальный номер в любой момент времени для каждого accountId.

Таким образом, все думали, что проблема заключается в реализации, которую мы использовали для генерации uniqueId. Но вскоре мы поняли, что это странно только с нечетными числами uniqueId, но правильно работает с четными числами uniqueId. Это дало нам направление к работе.

Теперь мы попытались сохранить число в переменной JS и снова зарегистрировать его. Мы разобрались с проблемой. Виновником был Number тип данных JS. Он может хранить значения с точностью только до (2⁵³-1), т.е. 9007199254740991. Как только вы пересечете этот предел, вы потеряете точность.

Так что если вы попробуете запустить console.log(9007199254740992 === 9007199254740993). он напечатает true. Разве этот язык НЕ СТРАННЫЙ !!

Возникает вопрос, почему тесты раньше не проваливались ???

Потому что System.nanoTime () возвращает текущее значение источника времени с высоким разрешением на запущенной виртуальной машине Java в наносекундах. Таким образом, чем дольше работает JVM, тем больше времени проходит, что приводит к увеличению числа наносекунд. Но видите ли, Java Long может содержать такое большое число, как (2⁶³-1), поэтому даже если ваша JVM работает в течение 200 лет, переменная не выдаст ошибку «значение вне допустимого диапазона».

Что я узнал при отладке?

  1. Всегда знайте, каковы ограничения типов данных, которые вы используете в определенном языке программирования.
  2. Что такое System.nanoTime ()
  3. Всегда помните, как вы общаетесь с разными языками, и старайтесь использовать совместимые типы данных для общения.

PS: Пожалуйста, помогите мне с лучшим названием для истории.