2016-03-25 10 views
6

Ho fatto una funzione che converte un double a una frazione semplificata in Java:Verificare se un doppio = 1/3

public static int gcm(int a, int b) { 
    return b == 0 ? a : gcm(b, a % b); 
} 
public static String toFraction(double d) { 
    int decimals = String.valueOf(d).split("\\.")[1].length(); 
    int mult = (int) Math.pow(10, decimals); 
    int numerator = (int) (d * mult); 
    int denominator = mult; 
    // now simplify 
    int gcm = gcm(numerator, denominator); 
    numerator /= gcm; 
    denominator /= gcm; 
    return numerator + "/" + denominator; 
} 

Funziona, tranne per il fatto che se uso toFraction(1.0/3), questa volontà, comprensibilmente, restituire "715827882/2147483647". Come posso risolvere questo problema per restituire "1/3"?

+1

@MickMnemonic In che modo BigDecimal rappresenta 1/3 senza errore di rappresentazione? –

+2

Non vedo come funzionerà questo approccio. un doppio non è un decimale, né sarebbe di aiuto se lo fosse. Sei aperto ad altre soluzioni? –

+0

concordato. L'OP dovrebbe usare BigDecimal o qualcosa di simile. – pczeus

risposta

6

È necessario consentire un determinato errore e non tutte le frazioni possono essere rappresentate esattamente come valori scalari.

public static String toFraction(double d, double err) { 
    String s = Long.toString((long) d); 
    d -= (long) d; 
    if (d > err) { 
     for (int den = 2, max = (int) (1/err); den < max; den++) { 
      long num = Math.round(d * den); 
      double d2 = (double) num/den; 
      if (Math.abs(d - d2) <= err) 
       return (s.equals("0") ? "" : s + " ") + num +"/"+den; 
     } 
    } 
    return s; 
} 

public static void main(String... args) { 
    System.out.println(toFraction(1.0/3, 1e-6)); 
    System.out.println(toFraction(1.23456789, 1e-6)); 
    System.out.println(toFraction(Math.E, 1e-6)); 
    System.out.println(toFraction(Math.PI, 1e-6)); 
    for (double d = 10; d < 1e15; d *= 10) 
     System.out.println(toFraction(Math.PI, 1.0/d)); 
} 

stampe

1/3 
1 19/81 
2 719/1001 
3 16/113 
3 1/5 
3 1/7 
3 9/64 
3 15/106 
3 16/113 
3 16/113 
3 3423/24175 
3 4543/32085 
3 4687/33102 
3 14093/99532 
3 37576/265381 
3 192583/1360120 
3 244252/1725033 
3 2635103/18610450 

Nota: questo trova le approssimazioni 21/7, 333/106 e 355/113 per PI.

+1

Il problema sembra molto correlato alla stampa di punti flottanti. Mi chiedo se è possibile utilizzare la stessa strategia grisu3 che trova la stringa stampabile più piccola che restituirà la rappresentazione binaria del float. Il problema è un po 'diverso però: trova la frazione con il denominatore più piccolo che durante la conversione in virgola mobile restituirà il numero in virgola mobile. Grisu3 deve ricorrere all'aritmetica bignum per farlo anche se in un piccolo numero di casi. – JasonN

3

No double il valore è uguale a un terzo, quindi l'unico modo in cui è possibile eseguire il programma per la stampa 1/3 è se si modificano le specifiche del metodo per favorire risposte "carine" anziché la risposta che è tecnicamente corretta.

Una cosa che potresti fare è scegliere un denominatore massimo per le risposte, diciamo 100, e restituire la frazione più vicina al denominatore 100 o meno.

Ecco come si potrebbe implementare questo utilizzando Java 8 flussi:

public static String toFraction(double val) { 
    int b = IntStream.rangeClosed(1, 100) 
        .boxed() 
        .min(Comparator.comparingDouble(n -> Math.abs(val * n - Math.round(val * n)))) 
        .get(); 
    int a = (int) Math.round(val * b); 
    int h = gcm(a, b); 
    return a/h + "/" + b/h; 
} 

Non c'è bel approccio a questo. double non è molto buono per questo genere di cose. Nota che BigDecimal non può rappresentare neanche 1/3, quindi avrai lo stesso problema con quella classe.

0

Ci sono molti modi per gestirlo, ma è necessario esaminare casi particolari. Ad esempio, se il numeratore è 1, la frazione è già ridotta e basta semplicemente rimuovere le cifre decimali e restituire ciò che ti è stato dato.