2008-11-05 18 views
7

Zur Zeit habe ich diese Methode:Wie überprüft man, ob ein Double maximal n Dezimalstellen hat?

static boolean checkDecimalPlaces(double d, int decimalPlaces){ 
    if (d==0) return true; 

    double multiplier = Math.pow(10, decimalPlaces); 
    double check = d * multiplier; 
    check = Math.round(check);  
    check = check/multiplier; 
    return (d==check);  
} 

Aber diese Methode nicht für checkDecmialPlaces(649632196443.4279, 4) wahrscheinlich, weil ich auf eine Basis 2 Nummer Basis 10 Mathematik zu tun.

Wie also kann diese Überprüfung korrekt durchgeführt werden?

Ich dachte daran, eine String-Darstellung des doppelten Wertes zu bekommen und dann das mit einem Regexp zu überprüfen - aber das fühlte sich komisch an.

EDIT: Danke für alle Antworten. Es gibt Fälle, in denen ich eine doppelte bekommen wirklich und für jene Fälle, die ich umgesetzt folgendes:

private static boolean checkDecimalPlaces(double d, int decimalPlaces) { 
    if (d == 0) return true; 

    final double epsilon = Math.pow(10.0, ((decimalPlaces + 1) * -1)); 

    double multiplier = Math.pow(10, decimalPlaces); 
    double check = d * multiplier; 
    long checkLong = (long) Math.abs(check); 
    check = checkLong/multiplier; 

    double e = Math.abs(d - check); 
    return e < epsilon; 
} 

ich die round zu einer Verkürzung geändert. Scheint, dass die in round vorgenommene Berechnung die Ungenauigkeit zu sehr erhöht. Zumindest im fehlgeschlagenen Testfall.
Wie einige von euch wies darauf hin, wenn ich auf die ‚echten‘ String-Input bekommen konnte ich Ich habe BigDecimal zu überprüfen und so getan verwenden sollten:

BigDecimal decimal = new BigDecimal(value); 
BigDecimal checkDecimal = decimal.movePointRight(decimalPlaces); 
return checkDecimal.scale() == 0; 

Der double Wert ich kommt von der Apache POI-API, liest Excel-Dateien. Ich habe ein paar Tests gemacht und herausgefunden, dass, obwohl die API double Werte für numerische Zellen zurückgibt, kann ich eine genaue Darstellung, wenn ich sofort formatieren, dass double mit der DecimalFormat:

DecimalFormat decimalFormat = new DecimalFormat(); 
decimalFormat.setMaximumIntegerDigits(Integer.MAX_VALUE); 
// don't use grouping for numeric-type cells 
decimalFormat.setGroupingUsed(false); 
decimalFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US)); 
value = decimalFormat.format(numericValue); 

Dies gilt auch für die Werte, die nicht funktioniert im Binärformat genau dargestellt werden.

+0

„richtig“ Exponent verhören bedeutet nicht viel hier. Ihre zufälligen Dezimalzahlen haben keine genauen binären Darstellungen. Sie suchen nach einer Dezimalzahl, die "nahe genug" ist, so dass Leute die Dezimalversion des binären Werts mögen. Wie nah ist nahe genug? –

Antwort

6

Der Test schlägt fehl, weil Sie die Genauigkeit der erreichten binäre Gleitkommadarstellung, die etwa 16 Ziffern mit IEEE754 double precision ist. Durch Multiplikation mit 649632196443.4279 mit 10000 wird die Binärdarstellung abgeschnitten, was zu Fehlern beim Runden und Dividieren nachher führt, wodurch das Ergebnis Ihrer Funktion vollständig ungültig wird.

Weitere Details finden http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

Eine bessere Möglichkeit wäre zu prüfen, ob die n+1 Dezimalstellen unter einem bestimmten Schwellenwert sind. Wenn d - round(d) kleiner als epsilon ist (siehe limit), hat die dezimale Darstellung von d keine signifikanten Dezimalstellen. Ähnlich, wenn (d - round(d)) * 10^n weniger als epsilon ist, kann d höchstens n bedeutende Orte haben.

Verwenden Sie Jon Skeet 's , um nach den Fällen zu suchen, in denen d nicht genau genug ist, um die Dezimalstellen zu halten, die Sie suchen.

+0

Ich schaute auch auf den DoubleConverter Jon, der in seinem gepostet wurde antworte und es scheint so ähnlich zu sein wie BigDecimal. Ich frage mich, warum er seine Antwort entfernt hat. – Turismo

3

Wie bei allen Fließkomma-Arithmetik sollten Sie nicht auf Gleichheit prüfen, sondern dass der Fehler (epsilon) ausreichend klein ist.

Wenn Sie ersetzen:

return (d==check); 

mit so etwas wie

return (Math.abs(d-check) <= 0.0000001); 

sollte es funktionieren. Offensichtlich sollte das Epsilon klein genug gewählt werden, verglichen mit der Anzahl der Dezimalstellen, nach denen Sie suchen.

+0

Das größte Problem ist Überlauf, wenn Sie tun: check = d * Multiplikator; dies funktioniert nicht für große Zahlen; – tvanfosson

+0

IEE 754 64-Bit-Doubles haben nur eine Genauigkeit von etwa 18 Stellen. Wenn Sie also in Zahlen auftauchen, bei denen das Multiplizieren mit 1000 zu einem Überlauf führt (etwa 10^304), können Sie sicher sein, dass in der Darstellung keine Dezimalstellen vorhanden sind. – paxdiablo

1

Der Typ double ist eine binäre Fließkommazahl. Es gibt immer scheinbare Ungenauigkeiten im Umgang mit ihnen, als ob sie Dezimal-Gleitkommazahlen wären. Ich weiß nicht, dass du deine Funktion jemals so schreiben kannst, dass sie so funktioniert, wie du es willst.

Sie werden wahrscheinlich zur ursprünglichen Quelle der Nummer zurückkehren müssen (möglicherweise eine Zeichenfolgeeingabe) und die Dezimaldarstellung beibehalten, wenn es für Sie wichtig ist.

5

Wenn Ihr Ziel ist eine Zahl mit genau n signifikanten Zahlen rechts von der Dezimalzahl darzustellen ist, ist BigDecimal die Klasse zu verwenden.

Unveränderlich, arbitrary-precision signed Dezimalzahlen.Ein BigDecimal besteht aus einer beliebigen Ganzzahl unscaled Wert und einer 32-Bit-Ganzzahl Skala. Wenn Null oder positiv, ist die Skala die Anzahl der Ziffern rechts vom Dezimalpunkt . Wenn negativ, wird der unskalierte Wert der Zahl mit zehn multipliziert mit der Potenz der Negation der Skala multipliziert. Der Wert der durch die BigDecimal dargestellten Zahl ist daher (unscaledValue × 10-scale).

scale über setScale(int)

0

eingestellt werden Ich bin nicht sicher, dass dies in der Regel wirklich machbar ist. Zum Beispiel, wie viele Dezimalstellen hat 1.0e-13? Was ist, wenn es aus einem Rundungsfehler beim Rechnen resultiert und wirklich nur 0 verkleidet ist? Wenn auf der anderen Seite dem Sie gefragt werden, ob es irgendwelche Nicht-Null-Ziffern sind in den ersten n Dezimalstellen Sie so etwas wie tun:

static boolean checkDecimalPlaces(double d, unsigned int decimalPlaces){ 
     // take advantage of truncation, may need to use BigInt here 
     // depending on your range 
     double d_abs = Math.abs(d); 
     unsigned long d_i = d_abs; 
     unsigned long e = (d_abs - d_i) * Math.pow(10, decimalPlaces); 
     return e > 0; 
    } 
+0

tatsächlich half mir deine Antwort, aber wie es jetzt ist, ist es fehlerhaft, da du 'd' multiplizieren musst, bevor du es abschneidest, um keine signifikanten Ziffern zu verlieren - siehe meine Bearbeitung der Frage – Turismo

1

Wenn Sie zu BigDecimal wechseln können, dann sollten Sie, wie Ken G erklärt, genau das verwenden.

Wenn nicht, dann müssen Sie sich mit einer Reihe von Problemen befassen, wie in den anderen Antworten erwähnt. Sie haben es mit einer Binärzahl (double) zu tun und fragen nach einer dezimalen Darstellung dieser Zahl; Sie fragen nach einem String. Ich denke deine Intuition ist richtig.

0

Ich denke, das Convert to String besser ist und den Wert für die

public int calcBase10Exponet (Number increment) 
{ 
    //toSting of 0.0=0.0 
    //toSting of 1.0=1.0 
    //toSting of 10.0=10.0 
    //toSting of 100.0=100.0 
    //toSting of 1000.0=1000.0 
    //toSting of 10000.0=10000.0 
    //toSting of 100000.0=100000.0 
    //toSting of 1000000.0=1000000.0 
    //toSting of 1.0E7=1.0E7 
    //toSting of 1.0E8=1.0E8 
    //toSting of 1.0E9=1.0E9 
    //toSting of 1.0E10=1.0E10 
    //toSting of 1.0E11=1.0E11 
    //toSting of 0.1=0.1 
    //toSting of 0.01=0.01 
    //toSting of 0.0010=0.0010 <== need to trim off this extra zero 
    //toSting of 1.0E-4=1.0E-4 
    //toSting of 1.0E-5=1.0E-5 
    //toSting of 1.0E-6=1.0E-6 
    //toSting of 1.0E-7=1.0E-7 
    //toSting of 1.0E-8=1.0E-8 
    //toSting of 1.0E-9=1.0E-9 
    //toSting of 1.0E-10=1.0E-10 
    //toSting of 1.0E-11=1.0E-11 
    double dbl = increment.doubleValue(); 
    String str = Double.toString (dbl); 
// System.out.println ("NumberBoxDefaultPatternCalculator: toSting of " + dbl + "=" + str); 
    if (str.contains ("E")) 
    { 
    return Integer.parseInt (str.substring (str.indexOf ("E") + 1)); 
    } 
    if (str.endsWith (".0")) 
    { 
    return str.length() - 3; 
    } 
    while (str.endsWith ("0")) 
    { 
    str = str.substring (0, str.length() - 1); 
    } 
    return - (str.length() - str.indexOf (".") - 1); 
} 
Verwandte Themen