Нахождение «дискретной» разницы между близкими числами с плавающей запятой

#floating-point #numerical #x87

#с плавающей запятой #числовой #x87

Вопрос:

Предположим, у меня есть два числа с плавающей запятой, x и y , причем их значения очень близки.

Существует дискретное количество чисел с плавающей запятой, которые можно представить на компьютере, поэтому мы можем перечислять их в порядке возрастания: f_1, f_2, f_3, ... . Я хочу найти расстояние x и y в этом списке (т. Е. Находятся ли они на расстоянии 1, 2, 3, … или n дискретных шагов друг от друга?)

Возможно ли это сделать, используя только арифметические операции ( -*/ ) и не рассматривая двоичное представление? Меня в первую очередь интересует, как это работает на x86.

Является ли следующее приближение правильным, предполагая, что y > x и что x и y находятся всего в нескольких шагах (скажем, < 100) друг от друга? (Вероятно, нет …)

 (y-x) / x / eps
  

Здесь eps обозначает машинный эпсилон. (Машинный эпсилон — это разница между 1.0 и следующим наименьшим числом с плавающей запятой.)

Комментарии:

1. @ShreevatsaR, вот почему я x тоже разделил на (не только eps ). (y-x) / x / eps = (y-x) / (x*eps) . Хотя я все еще не уверен, что это правильно.

2. Ах, ладно, извините. Ваша формула может быть правильной; давайте подумаем. 🙂 Тем временем вы можете прочитать, что каждый специалист по информатике должен знать об арифметике с плавающей запятой .

Ответ №1:

Числа с плавающей запятой лексикографически упорядочены, поэтому:

 int steps(float a, float b){

  int ai = *(int*)amp;a;  // reinterpret as integer
  int bi = *(int*)amp;b;  // reinterpret as integer
  return bi - ai;
}

steps(5.0e-1, 5.0000054e-1);  // returns 9
  

Такой метод используется при сравнении чисел с плавающей запятой.

Комментарии:

1. пока числа > 0. Некоторая логика все еще необходима для покрытия отрицательных значений и 0 (включая «-0»).

2. 1, Интересное наблюдение о лексикографическом упорядочении. Что я действительно хотел бы знать, хотя все еще, возможно ли это, используя только арифметические операции с плавающей запятой. Язык, который я использовал, когда задавал этот вопрос, не поддерживает переосмысление битовых шаблонов как другого типа.

3. @Szabolcs какой язык вы использовали? Я написал код на C , но он должен работать на C и D.

4. На самом деле мой вопрос был не о том, как это сделать на этом языке, мне просто было любопытно, можно ли это сделать, используя только операции с плавающей запятой, не прибегая каким-либо образом к изучению битового представления числа с плавающей запятой. Я использовал Mathematica. Даже если это не прямой ответ на мой вопрос, ваш ответ был интересным, и я ценю это (особенно учитывая, сколько лет вопросу).

Ответ №2:

Я думаю, вам не обязательно проверять двоичное представление напрямую, но вы должны полагаться на него, чтобы получить точный ответ.

Начните с использования frexp() для разбиения x на экспоненту exp и мантиссу. Я считаю, что следующее значение с плавающей запятой больше x x eps * 2^(exp-1) . («-1» означает, что frexp возвращает мантиссу в диапазоне [1/2, 1), а не [1, 2).)

Если x и y имеют одинаковый показатель, вы в основном закончили. В противном случае вам нужно подсчитать, сколько шагов приходится на степень 2, что просто 1.0/eps . Другими словами, количество шагов между 2 ^ n и 2 ^(n 1) равно 1.0/eps .

Итак, для y > x подсчитайте, сколько шагов от x до следующей степени двойки; затем подсчитайте, сколько еще шагов потребуется, чтобы получить наибольшую степень, равную 2, меньшую, чем y; затем подсчитайте, сколько еще шагов потребуется, чтобы перейти оттуда к y . Я думаю, все это довольно легко выразить в терминах eps .