Nombre approximatif à virgule flottante avec une précision à n chiffres

9

Nous avons un nombre à virgule flottante rentre 0 et 1 et un entier p.

Trouvez la fraction d'entiers avec le plus petit dénominateur, qui se rapproche ravec une pprécision d' au moins chiffres.

  • Entrées: r(un nombre à virgule flottante) et p(entier).
  • Sorties: aet bentiers, où
    • a/b(flottant) se rapproche rjusqu'aux pchiffres.
    • b est le plus petit entier positif possible.

Par exemple:

  • si r=0.14159265358979et p=9,
  • alors le résultat est a=4687et b=33102,
  • parce que 4687/33102=0.1415926530119026.

Toute solution doit fonctionner en théorie avec des types à précision arbitraire, mais les limitations causées par les types à précision fixe des implémentations n'ont pas d'importance.

La précision signifie le nombre de chiffres après " 0." dans r. Ainsi, si r=0.0123et p=3, alors a/bdevrait commencer par 0.012. Si les premiers pchiffres de la partie fractionnaire de rsont 0, un comportement non défini est acceptable.

Critères de victoire:

  • L'algorithme algorithmiquement le plus rapide gagne. La vitesse est mesurée en O (p).
  • S'il existe plusieurs algorithmes les plus rapides, le plus court l'emporte.
  • Ma propre réponse est exclue de l'ensemble des gagnants possibles.

Ps la partie mathématique est en fait beaucoup plus facile qu'il n'y paraît, je vous suggère de lire ce post.

peterh - Réintégrer Monica
la source

Réponses:

7

JavaScript, O (10 p ) et 72 octets

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

Il est trivial de prouver que la boucle se fera après au plus O (10 p ) itérations.

Un grand merci à l'idée de Neil, économisez 50 octets.

tsh
la source
Pourquoi jouez-vous avec padEndet match? Vous ne pouvez pas simplement slicechaque chaîne à la bonne longueur, puis les soustraire?
Neil
@Neil Désolé, je n'avais pas compris votre point. L'ajout padEndest utilisé pour le testcase f(0.001,2)et f(0.3,2).
tsh
Je pensais que vous pourriez simplifier quelque chose dans le sens de (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]}(pas entièrement joué au golf).
Neil
@Neil 120 -> 70 octets. :)
tsh
Whoa, c'est beaucoup mieux!
Neil
4

Haskell , O (10 p ) dans le pire des cas 121 119 octets

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

Essayez-le en ligne!

Enregistré 2 octets grâce à Laikoni

J'ai utilisé l'algorithme de /math/2432123/how-to-find-the-fraction-of-integers-with-the-smallest-denominator-matching-an-i .

À chaque étape, le nouvel intervalle représente la moitié de l'intervalle précédent. Ainsi, la taille de l'intervalle est 2**-n, où nest l'étape actuelle. Quand 2**-n < 10**-p, nous sommes sûrs d'avoir la bonne approximation. Pourtant si n > 4*palors 2**-n < 2**-(4*p) == 16**-p < 10**-p. La conclusion est que l'algorithme l'est O(p).

EDIT Comme l'a souligné orlp dans un commentaire, l'affirmation ci-dessus est fausse. Dans le pire des cas, r = 1/10**p( r= 1-1/10**pest similaire), il y aura des 10**pétapes: 1/2, 1/3, 1/4, .... Il y a une meilleure solution, mais je n'ai pas le temps pour l'instant de résoudre ce problème.

jferard
la source
Je sais que le golf par code n'est que l'objectif secondaire, mais vous pouvez supprimer le f=et enregistrer deux octets avec z<-floor.(*10^p),u<-a+c,v<-b+d.
Laikoni
@Laikoni Je n'ai pas compté les deux octets. Je ne sais pas comment supprimer f=sur TIO dans le code Haskell.
jferard
Vous pouvez ajouter le -cppdrapeau du compilateur et écrire f=\ dans l'en-tête: Essayez-le en ligne!
Laikoni
"A chaque étape, le nouvel intervalle est la moitié de l'intervalle précédent." Comment sais-tu cela? La première étape est 1/2, oui, mais la prochaine étape est par exemple le médian de 1/2 et 1/1 donnant 2/3, ce qui ne réduit pas de moitié l'intervalle.
orlp
@orlp Vous avez absolument raison. J'étais beaucoup trop optimiste et la complexité est O (10 ^ p) dans le pire des cas. J'ai une meilleure solution mais je n'ai pas le temps de l'écrire maintenant.
jferard
0

C, 473 octets (sans contexte), O (p), non concurrent

Cette solution utilise la partie mathématique détaillée dans cet excellent article. J'ai calculé uniquement calc()la taille de la réponse.

#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 - Réintégrer Monica
la source
Il se rapproche également de la solution probablement la plus rapide possible dans le sens des cycles de CPU, au moins sur les machines conventionnelles.
peterh