Как найти (оптимальное) целочисленное соотношение разной точности?

Если у меня есть переменная m типа uint32 а также r типа uint16 а также постоянная float64 со значением т.е. f=0.5820766091346741, Как мне найти m,r которые удовлетворяют f=r/m?

Похож на Fraction.limit_denominator из python.

Этот репозиторий Github содержит различные алгоритмы наилучшего рационального приближения, но ограничивает только знаменатель.

4 ответа

Как мне найти m,r которые удовлетворяют f=r/m?

= подразумевает точное.

Чтобы сделать это точно, если это возможно, см. Ниже. Этот подход не пытается наилучшим образом соответствовать, если точное решение не возможно, поскольку это не будет удовлетворять f=r/m,


Все конечные значения с плавающей точкой являются точными. "0.5820766091346741" сохранен в f может дать f близкое значение, но значение в f это точно.

Учитывая основание числа с плавающей запятой (очень часто 2), все они могут быть представлены точно с помощью: "целое число /(базовыйпоказатель)".

С двоичным 64 самый большой необходимый показатель составляет около (1023 + 53).

Поскольку OP хочет, чтобы результат соответствовал 32-битному r и 16-битный mвполне понятно, что большинство float64 (64-бит) не будет иметь точного решения - просто недостаточно комбинаций для сохранения результата.

Алгоритм ниже в прокомментированном C, предполагая основание 2.

// return true on success
bool fraction(double d, uint32_t *r, uint16_t *m) {
  if (d < 0.0 || isnan(d) || d > UINT32_MAX) {
    return false;
  }

  // Scale d to extract, hopefully a 32+15 bit integer
  uint16_t power_of_2 = 32768; // largest power-of-2 in m
  d *= power_of_2;
  uint64_t ipart = (uint64_t) d;
  // Even after scaling, `d` has a fractional part.
  if (d != ipart) {
    return false;  // value has unrepresentable precision.
  }

  // while big and even, reduce the fraction
  while (ipart > UINT32_MAX && (ipart % 2 == 0)) {
    power_of_2 /= 2;
    ipart /= 2;
  }

  // If reduction was insufficient ...
  if (ipart > UINT32_MAX) {
    return false; // value has unrepresentable precision.
  }

  *r = (uint32_t) ipart;
  *m = power_of_2;
  return true;  // Success!
}

Простой ответ будет:

     ROUND(f * 10^8)
f = ----------------
         10^8

Затем вы можете реализовать небольшой цикл, который пытается разделить числитель и знаменатель на простые числа (начиная с 2 и выше). Что-то вроде (конечно, код не проверен):

var m = f * 10^8 ;
var r = 10^8     ;
var Prime_Numbers = [2,3,5,7,11,13,17,19,....] ;

for (var I = 0 ; I < Prime_Numbers.length ; I++) {

    if ((Prime_Numbers[I] > m) ||
        (Prime_Numbers[I] > r)    ) {

       break;
    }

    if (((m % Prime_Numbers[I]) == 0) &&
         (r % Prime_Numbers[I]) == 0)    ) {
          m = m / Prime_Numbers[I] ;
          r = r / Prime_Numbers[I] ;
    }

console.log("Best m is: " + m) ;
console.log("Best r is: " + r) ;
:
:
}

Теперь вопрос состоит в том, сколько первичных номеров я должен включить в список?

Трудно сказать, но интуитивно не слишком много... Я бы сказал, что это будет зависеть от того, насколько вы строги в отношении ОПТИМАЛЬНО.

Надеюсь, что это дает вам некоторое направление.

Ура!!

РЕДАКТИРОВАТЬ:

Подумав немного дальше, чтобы всегда получать значения ABSOLUTE OPTIMAL, вам нужно включить все первичные числа вплоть до половины максимального значения, которое вы хотите, в качестве точности. Например, если точность тура должна быть 8 цифр (99999999), вам необходимо включить все первичные числа до (99999999/2).

РЕДАКТИРОВАТЬ 2:

Добавлено условие выхода в цикле.

Есть статья Дэвида Т. Эшли и др. который предлагает алгоритм для нахождения рационального приближения двумя целыми числами с разной точностью.

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

Основная идея состоит в том, чтобы преобразовать число с плавающей точкой в ​​непрерывную дробь, а затем искать конвергент высшего порядка, который удовлетворяет ограничениям. Смотрите вики для ознакомления с конвергентами.

Однако упомянутая статья описывает более сложный подход к применению ограничений на целочисленные соотношения (см. Раздел 5), который использует аналогию с решеточными структурами 1.

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

Но я хотел показать, насколько хорошо это представление с плавающей запятой подходит для 64-битного IEEE754. Так что я немного поиграл с концепцией в Smalltalk (Squeak 64 bit).

Существует только 48 бит для представления r/m, причем многие комбинации представляют одно и то же значение (1/1=2/2=... 1/2=2/4=3/6=...), хотя уже есть 2^53 различных 64бит плавают в интервале [0,5,1,0). Таким образом, мы можем сказать, что большую часть времени мы не собираемся точно соответствовать f. Задача состоит в том, чтобы найти пару (r/m), которая округляет ближайшую к f.

Я не могу разумно играть с 48 битами, но могу с половиной точности и собрать все комбинации uint8/uint16:

v := Array new: 1<<24.
0 to: 1<<8-1 do: [:r |
    0 to: 1<<16-1 do: [:m |
        v at: (m<<8+r+1) put: ([r asFloat/m asFloat]
            on: ZeroDivide do: [:exc | exc return: Float infinity])]].
s := v asSet sorted.
s size-2.

За исключением 0 и inf, это примерно 10 173 377 различных комбинаций из 16 777 216.

Меня интересует разрыв между двумя последовательными представимыми числами:

x := s copyFrom: 2 to: s size - 1.
y := (2 to: s size-1) collect: [:i |  (s at: i) - (s at: i-1) / (s at: i) ulp].

минимум

u := y detectMin: #yourself.

около 2.71618435e8 ulp.

Давайте посмотрим, как формируются числитель и знаменатель:

p := y indexOf: u.
{((v  indexOf: (x at: p)) - 1) hex.
 ((v  indexOf: (x at: p-1)) - 1) hex}.

результат в #('16rFDFFFE' '16rFEFFFF') первые 4 цифры кодируют den (m), последние две цифры (r).

Таким образом, минимальный разрыв получается для

s1 := (1<<8-1) / (1<<8-1<<8-1).
s2 := (1<<8-2) / (1<<8-2<<8-1).
s2 asFloat - s1 asFloat / s2 asFloat ulp = u.

Это около значения 1/256 (или где-то рядом).

Мы можем предположить, что минимальный разрыв для 48-битной реперентации

s1 := (1<<16-1) / (1<<16-1<<16-1).
s2 := (1<<16-2) / (1<<16-2<<16-1).
s2 asFloat - s1 asFloat / s2 asFloat ulp.

Это около 16 ulp, не так уж и плохо, а максимальная плотность около 1/65536 (или где-то рядом).

Какова будет плотность около 0,5, как в вашем примере? Для 24-битного представления:

h := x indexOf: 0.5.

10133738. Давайте проверим точность в окрестности:

k := (h to: h +512) detectMin: [:i | (y at: i)].
u2 := y at: k.

Это 3.4903102168e10 ulp (примерно в 128 раз меньше плотности). Получается для:

s1 := (1<<8-1) / (1<<8-1<<1-1).
s2 := (1<<8-2) / (1<<8-2<<1-1).
s2 asFloat- s1 asFloat / s2 asFloat ulp = u2.

Таким образом, с 48 битами, мы можем ожидать плотность около

s1 := (1<<16-1) / (1<<16-1<<1-1).
s2 := (1<<16-2) / (1<<16-2<<1-1).
s2 asFloat- s1 asFloat / s2 asFloat ulp.

то есть 524320 ulp, или точность приблизительно 5.821121362714621e-11.

Редактировать: Как насчет наихудшей точности?

В зоне лучшей плотности:

q := (p-512 to:p+512) detectMax: [:i | y at: i].
{((v  indexOf: (x at: q)) - 1) hex.
 ((v  indexOf: (x at: q-1)) - 1) hex.}.

То есть #('16rFEFFFF' '16r10001')или, другими словами, непосредственно перед наилучшей точностью мы имеем локально худшее: w := y at: q. что составляет 6,8990021713e10 ulp для этих чисел:

s2 := (1<<8-1) / (1<<8-1<<8-1).
s1 := (1) / (1<<8).
s2 asFloat - s1 asFloat / s2 asFloat ulp = w.

В переводе на 48 бит, то есть около 1,048592e6 ulp:

s2 := (1<<16-1) / (1<<16-1<<16-1).
s1 := (1) / (1<<16).
s2 asFloat - s1 asFloat / s2 asFloat ulp.

И около 0,5 худшее составляет около 8,847936399549e12 ulp для 24 битов:

j := (h-512 to: h +512) detectMax: [:i | (y at: i)].
w2 := y at: j.
s2 := (1<<8-1) / (1<<8-1<<1-1).
s1 := (1) / (1<<1).
s2 asFloat- s1 asFloat / s2 asFloat ulp = w2.

или переведено в 48 бит, 3.4360524818e10 ulp:

s2 := (1<<16-1) / (1<<16-1<<1-1).
s1 := (1) / (1<<1).
s2 asFloat- s1 asFloat / s2 asFloat ulp.

Это примерно 3.814784579114772e-6 абсолютной точности, не очень хорошо.

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

Другие вопросы по тегам