Numero approssimativo in virgola mobile con precisione a n cifre


9

Abbiamo un numero in virgola mobile compreso rtra 0 e 1 e un numero intero p.

Trova la frazione di numeri interi con il minimo denominatore, che si avvicina rcon almeno una pprecisione di cifra.

  • Ingressi: r(un numero in virgola mobile) e p(numero intero).
  • Output: ae bnumeri interi, dove
    • a/b(come float) si avvicina ralle pcifre.
    • b è il numero intero così piccolo possibile più piccolo possibile.

Per esempio:

  • se r=0.14159265358979e p=9,
  • allora il risultato è a=4687e b=33102,
  • perché 4687/33102=0.1415926530119026.

Qualsiasi soluzione deve funzionare in teoria con tipi di precisione arbitraria, ma i limiti causati dai tipi di precisione fissa delle implementazioni non contano.

Precisione indica il numero di cifre dopo " 0." in r. Pertanto, se r=0.0123e p=3, quindi, a/bdovrebbe iniziare con 0.012. Se le prime pcifre della parte frazionaria di rsono 0, il comportamento indefinito è accettabile.

Criteri di vincita:

  • Vince l'algoritmo algoritmicamente più veloce. La velocità è misurata in O (p).
  • Se ci sono più algoritmi più veloci, vince il più breve.
  • La mia risposta è esclusa dall'insieme dei possibili vincitori.

Ps la parte matematica è in realtà molto più semplice come sembra, suggerisco di leggere questo post.

Risposte:


7

JavaScript, O (10 p ) e 72 byte

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]}

È banale provare che il ciclo verrà eseguito dopo al massimo O (10 p ) iterazioni.

Mille grazie all'idea di Neil, risparmia 50 byte.


Perché stai armeggiando con padEnde match? Non puoi solo sliceogni stringa della lunghezza corretta e poi sottrarli?
Neil,

@Neil Mi dispiace non aver capito il tuo punto. L'aggiunta padEndviene utilizzata per testcase f(0.001,2)e f(0.3,2).
TSH

Stavo pensando che potresti semplificare fino a qualcosa del genere (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]}(non completamente giocato a golf).
Neil,

@Neil 120 -> 70 byte. :)
TSH

Whoa, va molto meglio!
Neil,

4

Haskell , O (10 p ) nel peggiore dei casi 121 119 byte

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)]

Provalo online!

Salvato 2 byte grazie a Laikoni

Ho usato l'algoritmo da /math/2432123/how-to-find-the-fraction-of-integers-with-the-smallest-denominator-matching-an-i .

Ad ogni passaggio, il nuovo intervallo è la metà dell'intervallo precedente. Pertanto, la dimensione dell'intervallo è 2**-n, dove si ntrova il passaggio corrente. Quando 2**-n < 10**-p, siamo sicuri di avere la giusta approssimazione. Eppure se n > 4*pallora 2**-n < 2**-(4*p) == 16**-p < 10**-p. La conclusione è che l'algoritmo è O(p).

MODIFICA Come sottolineato da orlp in un commento, l'affermazione sopra è falsa. Nel peggiore dei casi, r = 1/10**p( r= 1-1/10**pè simile), ci saranno 10**ppassaggi: 1/2, 1/3, 1/4, .... C'è una soluzione migliore, ma non ho il tempo per risolvere questo problema.


So che il golf del codice è solo l'obiettivo secondario, ma puoi eliminare f=e salvare due byte con z<-floor.(*10^p),u<-a+c,v<-b+d.
Laikoni,

@Laikoni Non ho contato i due byte. Non so come rimuovere f=su TIO nel codice Haskell.
jferard,

Puoi aggiungere il -cppflag del compilatore e scrivere f=\ nell'intestazione: provalo online!
Laikoni,

"Ad ogni passaggio, il nuovo intervallo è la metà dell'intervallo precedente." Come fai a saperlo? Il primo passo è 1/2, sì, ma poi il passo successivo è ad esempio la media di 1/2 e 1/1 che danno 2/3, che non dimezza l'intervallo.
orlp,

@orlp Hai assolutamente ragione. Ero troppo ottimista e la complessità è O (10 ^ p) nel peggiore dei casi. Ho una soluzione migliore ma non ho il tempo di scriverla adesso.
jferard,

0

C, 473 byte (senza contesto), O (p), non concorrenti

Questa soluzione utilizza la parte matematica descritta in questo eccellente post. Ho calcolato solo calc()nella dimensione della risposta.

#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;
}

Si avvicina anche alla soluzione probabilmente più veloce possibile nel senso dei cicli della CPU, almeno su macchine convenzionali.
Peter - Ripristina Monica il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.