Орієнтовний номер з плаваючою комою з точністю n-цифр


9

У нас є число з плаваючою комою rміж 0 і 1 і цілим числом p.

Знайдіть частку цілих чисел з найменшим знаменником, яка наближається rз принаймні p-значною точністю.

  • Введення: r(число з плаваючою комою) та p(ціле число).
  • Виходи: aі bцілі числа, де
    • a/b(як float) наближається rдо pцифр.
    • b - можливе найменше таке додатне ціле число.

Наприклад:

  • якщо r=0.14159265358979і p=9,
  • то результат a=4687і b=33102,
  • тому що 4687/33102=0.1415926530119026.

Будь-яке рішення має теоретично працювати з типами довільної точності, але обмеження, спричинені типами фіксованої точності впровадження, не мають значення.

Точність означає кількість цифр після " 0." в r. Таким чином, якщо r=0.0123і p=3, тоді a/bслід почати з 0.012. Якщо перші pцифри дробової частини rдорівнюють 0, невизначена поведінка є прийнятною.

Критерії виграшу:

  • Алгоритмічно найшвидший алгоритм виграє. Швидкість вимірюється в O (p).
  • Якщо існує кілька найшвидших алгоритмів, виграє найкоротший.
  • Моя власна відповідь виключається із набору можливих переможців.

Якщо математична частина насправді набагато простіша, як здається, пропоную прочитати цю публікацію.

Відповіді:


7

JavaScript, O (10 p ) та 72 байти

r=>p=>{for(a=0,b=1,t=10**p;(a/b*t|0)-(r*t|0);a/b<r?a++:b++);return[a,b]}

Доказано, що цикл буде виконано після не більше O (10 p ) ітерацій.

Велике спасибі ідеї Ніла, економте 50 байт.


Чому ви возитися з padEndі match? Ви не можете просто sliceвиконати кожну рядок потрібної довжини, а потім відняти їх?
Ніл

@Neil Вибачте, що я не зрозумів вашу думку. Доданий padEndвикористовується для тестових вітрин f(0.001,2)та f(0.3,2).
tsh

Я думав, що ти можеш спростити вниз щось до (r,p)=>{for(a=0,b=1;`${a/b}`.slice(0,p+2)-`${r}`.slice(0,p+2);a/b<r?a++:b++);return[a,b]}кінця (не повністю гольф).
Ніл

@Neil 120 -> 70 байт. :)
tsh

Ого, це набагато краще!
Ніл

4

Haskell , O (10 p ), в гіршому випадку 121 119 байт

g(0,1,1,1)
g(a,b,c,d)r p|z<-floor.(*10^p),u<-a+c,v<-b+d=last$g(last$(u,v,c,d):[(a,b,u,v)|r<u/v])r p:[(u,v)|z r==z(u/v)]

Спробуйте в Інтернеті!

Збережено 2 байти завдяки Лайконі

Я використовував алгоритм із /math/2432123/how-to-find-the-fraction-of-integers-with-the-smallest-denominator-matching-an-i .

На кожному кроці новий інтервал - це половина попереднього інтервалу. Таким чином, розмір інтервалу є 2**-n, де nзнаходиться поточний крок. Коли 2**-n < 10**-pми, безумовно, маємо правильне наближення. І все ж якщо n > 4*pтоді 2**-n < 2**-(4*p) == 16**-p < 10**-p. Висновок такий, що алгоритм є O(p).

EDIT Як зазначено orlp у коментарі, твердження вище є помилковим. У гіршому випадку r = 1/10**p( r= 1-1/10**pаналогічно), буде 10**pкроки: 1/2, 1/3, 1/4, .... Є краще рішення, але я зараз не маю часу, щоб виправити це.


Я знаю, що код гольфу є лише другорядною метою, але ви можете скинути f=і зберегти два байти за допомогою z<-floor.(*10^p),u<-a+c,v<-b+d.
Laikoni

@Laikoni я два байти не рахував. Я не знаю, як видалити f=TIO в коді Haskell.
jferard

Ви можете додати -cppпрапор компілятора і написати f=\ у заголовку: Спробуйте в Інтернеті!
Лайконі

"На кожному кроці новий інтервал - це половина попереднього інтервалу." Звідки ти це знаєш? Перший крок - це 1/2, так, але потім наступним кроком є, наприклад, посередник 1/2 і 1/1, що дає 2/3, що не вдвічі зменшує інтервал.
orlp

@orlp Ви абсолютно не вірні. Я був надто оптимістичним, а складність - O (10 ^ p) у гіршому випадку. У мене є краще рішення, але не маю часу написати це зараз.
jferard

0

C, 473 байти (без контексту), O (p), неконкурентні

Це рішення використовує математичну частину, детально описану в цій чудовій публікації. Я обчислював лише calc()розмір відповіді.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

void calc(float r, int p, int *A, int *B) {
  int a=0, b=1, c=1, d=1, e, f;
  int tmp = r*pow(10, p);
  float ivl = (float)(tmp) / pow(10, p);
  float ivh = (float)(tmp + 1) / pow(10, p);

  for (;;) {
    e = a + c;
    f = b + d;

    if ((ivl <= (float)e/f) && ((float)e/f <= ivh)) {
      *A = e;
      *B = f;
      return;
    }

    if ((float)e/f < ivl) {
      a = e;
      b = f;
      continue;
    } else {
      c = e;
      d = f;
      continue;
    }
  }
}

int main(int argc, char **argv) {
  float r = atof(argv[1]);
  int p = atoi(argv[2]), a, b;
  calc(r, p, &a, &b);
  printf ("a=%i b=%i\n", a, b);
  return 0;
}

Він також наближається до, мабуть, найшвидшого рішення у сенсі циклів процесора, принаймні на звичайних машинах.
peterh
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.