Почему хэш-значения различаются для NaN и Inf - Inf?

Я часто использую эту хеш-функцию, т.е. записываю значение фрейма данных. Хотел посмотреть, смогу ли я сломать это. Почему эти хэш-значения не идентичны?

Это требует дайджеста пакета.

Вывод простого текста:

> digest(Inf-Inf)
[1] "0d59b2dae9351c1ce6c76133295322d7"
> digest(NaN)
[1] "4e9653ddf814f0d16b72624aeb85bc20"
> digest(1)
[1] "6717f2823d3202449301145073ab8719"
> digest(1 + 0)
[1] "6717f2823d3202449301145073ab8719"
> digest(5)
[1] "5e338704a8e069ebd8b38ca71991cf94"
> digest(sum(1, 1, 1, 1, 1))
[1] "5e338704a8e069ebd8b38ca71991cf94"
> digest(1^0)
[1] "6717f2823d3202449301145073ab8719"
> 1^0
[1] 1
> digest(1)
[1] "6717f2823d3202449301145073ab8719"

Дополнительная странность. Вычисления, которые равны NaN, имеют идентичные хэш-значения, но хэш-значения NaN не эквивалентны:

> Inf - Inf
[1] NaN
> 0/0
[1] NaN
> digest(Inf - Inf)
[1] "0d59b2dae9351c1ce6c76133295322d7"
> digest(0/0)
[1] "0d59b2dae9351c1ce6c76133295322d7"
> digest(NaN)
[1] "4e9653ddf814f0d16b72624aeb85bc20"    

Ответы

Ответ 1

Т.Л., д - р это связано с очень глубокими деталями, как NaN представлены в двоичной системе. Вы можете обойти это, используя digest(.,ascii=TRUE)...

В продолжение ответа @Jozef: обратите внимание на жирные цифры...

> base::serialize(Inf-Inf,connection=NULL)
[1] 58 0a 00 00 00 03 00 03 06 00 00 03 05 00 00 00 00 05 55 54 46 2d 38 00 00
[26] 00 0e 00 00 00 01 ff f8 00 00 00 00 00 00
> base::serialize(NaN,connection=NULL)
[1] 58 0a 00 00 00 03 00 03 06 00 00 03 05 00 00 00 00 05 55 54 46 2d 38 00 00
[26] 00 0e 00 00 00 01 7f f8 00 00 00 00 00 00

Кроме того, используя pryr::bytes()...

> bytes(NaN)
[1] "7F F8 00 00 00 00 00 00"
> bytes(Inf-Inf)
[1] "FF F8 00 00 00 00 00 00"

Статья в Википедии о формате с плавающей запятой /NaNs говорит:

Некоторые операции с арифметикой с плавающей точкой недопустимы, например, получение квадратного корня из отрицательного числа. Достижение недопустимого результата называется исключением с плавающей точкой. Исключительный результат представлен специальным кодом, называемым NaN, для "Не числа". Все NaNs в IEEE 754-1985 имеют этот формат:

  • знак = 0 или 1.
  • смещенная экспонента = все 1 бит.
  • фракция = все, кроме всех 0 бит (поскольку все 0 бит представляют бесконечность).

Знак является первым битом; показатель степени - следующие 11 битов; дробь это последние 52 бита. При переводе первых четырех шестнадцатеричных цифр, приведенных выше, в двоичную Inf-Inf, Inf-Inf равен 1111 1111 1111 0100 (знак = 1; экспонента - все единицы, как требуется; дробь начинается с 0100), тогда как NaN - 0111 1111 1111 0100 (то же самое, но с знак = 0).

Чтобы понять, почему Inf-Inf заканчивается знаковым битом 1, а NaN - знаковым битом 0, вам, вероятно, придется глубже изучить способ реализации арифметики с плавающей запятой на этой платформе...

Возможно, стоит поднять вопрос об этом в репозитории GitHub; Я не могу придумать изящного способа сделать это, но кажется разумным, что объекты, для которых identical(x,y) равен TRUE в R, должны иметь идентичные хеш-коды... Обратите внимание, что identical() специально игнорирует эти различия в битовых комбинациях через single.NA (по умолчанию TRUE):

single.NA: логическое указание, если концептуально существует только один числовой "NA и один" NaN; 'single.NA = FALSE различает битовые комбинации.

В коде C похоже, что R просто использует оператор C != Для сравнения значений NaN если не включено побитовое сравнение, и в этом случае он явно проверяет равенство ячеек памяти: см. Здесь. То есть оператор сравнения C, по-видимому, рассматривает различные виды значений NaN как эквивалентные...

Ответ 2

Это связано с digest::digest с использованием base::serialize, который дает неидентичные результаты для двух упомянутых объектов с ascii = FALSE, который по умолчанию передается ему в digest:

identical(
  base::serialize(Inf-Inf, connection = NULL, ascii = FALSE),
  base::serialize(NaN, connection = NULL, ascii = FALSE)
)
# [1] FALSE

Даже если

identical(Inf-Inf, NaN)
# [1] TRUE