Rundung auf eine beliebige Anzahl von signifikanten Stellen


83

Wie können Sie eine beliebige Zahl (nicht nur ganze Zahlen> 0) auf N signifikante Stellen runden ?

Wenn ich beispielsweise auf drei signifikante Stellen runden möchte, suche ich nach einer Formel, die Folgendes annehmen könnte:

1.239.451 und geben 1.240.000 zurück

12.1257 und zurück 12.1

.0681 und zurück .0681

5 und zurück 5

Natürlich sollte der Algorithmus nicht fest codiert sein, um nur N von 3 zu handhaben, obwohl dies ein Anfang wäre.


Scheint Frage zu allgemein. Verschiedene Programmiersprachen haben dazu unterschiedliche Standardfunktionen. Nicht geeignet, um das Rad neu zu erfinden.
Johnny Wong

Antworten:


106

Hier ist der gleiche Code in Java ohne den 12.100000000000001-Fehler, den andere Antworten haben

Ich habe auch wiederholten Code entfernt, powerin eine Ganzzahl geändert , um schwebende Probleme zu vermeiden, wenn n - ddies erledigt ist, und das lange Zwischenprodukt klarer gemacht

Der Fehler wurde durch Multiplizieren einer großen Zahl mit einer kleinen Zahl verursacht. Stattdessen teile ich zwei Zahlen ähnlicher Größe.

BEARBEITEN
Weitere Fehler behoben. Überprüfung für 0 hinzugefügt, da dies zu NaN führen würde. Die Funktion funktioniert tatsächlich mit negativen Zahlen (Der ursprüngliche Code behandelt keine negativen Zahlen, da ein Protokoll einer negativen Zahl eine komplexe Zahl ist.)

public static double roundToSignificantFigures(double num, int n) {
    if(num == 0) {
        return 0;
    }

    final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
    final int power = n - (int) d;

    final double magnitude = Math.pow(10, power);
    final long shifted = Math.round(num*magnitude);
    return shifted/magnitude;
}

2
Vielen Dank, dass Sie meine Antwort akzeptiert haben. Ich habe gerade festgestellt, dass meine Antwort mehr als ein Jahr nach der Frage ist. Dies ist einer der Gründe, warum der Stapelüberlauf so cool ist. Hier finden Sie nützliche Informationen!
Pyrolistical

2
Beachten Sie, dass dies bei Werten nahe der Rundengrenze leicht fehlschlagen kann. Zum Beispiel sollte das Runden von 1,255 auf 3 signifikante Stellen 1,26 zurückgeben, aber 1,25 zurückgeben. Das liegt daran, dass 1,255 * 100,0 125,499999 ist ... Aber dies ist zu erwarten, wenn Sie mit Doppel arbeiten
cquezel

Wow, ich weiß, dass das alt ist, aber ich versuche es zu benutzen. Ich habe einen Schwimmer, den ich 3 signifikanten Zahlen anzeigen möchte. Wenn der Gleitkommawert 1,0 ist, rufe ich Ihre Methode auf, aber sie gibt immer noch 1,0 zurück, selbst wenn ich den Gleitkommawert als Double umwandle. Ich möchte, dass es als 1 zurückkehrt. Irgendwelche Ideen?
Steve W

3
Dieses Java-Snippet landet im offiziellen Android-Beispiel android.googlesource.com/platform/development/+/fcf4286/samples/…
Curious Sam

1
Nicht perfekt. Für num = -7999999.999999992 und n = 2 wird -7999999.999999999 zurückgegeben, sollte aber -8000000 sein.
Duncan Calvert

16

Hier ist eine kurze und süße JavaScript-Implementierung:

function sigFigs(n, sig) {
    var mult = Math.pow(10, sig - Math.floor(Math.log(n) / Math.LN10) - 1);
    return Math.round(n * mult) / mult;
}

alert(sigFigs(1234567, 3)); // Gives 1230000
alert(sigFigs(0.06805, 3)); // Gives 0.0681
alert(sigFigs(5, 3)); // Gives 5

aber 12.1257 gibt 12.126
Pyrolistical

1
Schöne Antwort Ates. Vielleicht einen Trigger hinzufügen, um 0 zurückzugeben, wenn n==0:)
sscirrus

Gibt es einen Grund dafür Math.log(n) / Math.LN10und nicht Math.log10(n)?
Lee

1
@Lee developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… "Dies ist eine neue Technologie, die Teil des ECMAScript 2015 (ES6) -Standards ist." Also im Grunde Kompatibilitätsprobleme.
Ates Goral

Vermisse ich etwas oder geht diese Antwort davon aus Math.floor(x) == Math.ceil(x) - 1? Weil es nicht ist, wenn xes eine ganze Zahl ist. Ich denke, das zweite Argument der powFunktion sollte sein sig - Math.ceil(Math.log(n) / Math.LN10)(oder nur verwenden Math.log10)
Paul

15

ZUSAMMENFASSUNG:

double roundit(double num, double N)
{
    double d = log10(num);
    double power;
    if (num > 0)
    {
        d = ceil(d);
        power = -(d-N);
    }
    else
    {
        d = floor(d); 
        power = -(d-N);
    }

    return (int)(num * pow(10.0, power) + 0.5) * pow(10.0, -power);
}

Sie müssen also die Dezimalstelle der ersten Ziffer ungleich Null ermitteln, dann die nächsten N-1-Ziffern speichern und dann die N-te Ziffer basierend auf dem Rest runden.

Wir können log verwenden, um das erste zu tun.

log 1239451 = 6.09
log 12.1257 = 1.08
log 0.0681  = -1.16

Nehmen Sie für Zahlen> 0 die Obergrenze des Protokolls. Nehmen Sie für Zahlen <0 das Wort des Protokolls.

Jetzt haben wir die Ziffer d: 7 im ersten Fall, 2 im 2., -2 im 3 ..

Wir müssen die (d-N)dritte Ziffer runden . Etwas wie:

double roundedrest = num * pow(10, -(d-N));

pow(1239451, -4) = 123.9451
pow(12.1257, 1)  = 121.257
pow(0.0681, 4)   = 681

Dann machen Sie die Standardrundung:

roundedrest = (int)(roundedrest + 0.5);

Und mache den Powder rückgängig.

roundednum = pow(roundedrest, -(power))

Wobei Leistung die oben berechnete Leistung ist.


Über Genauigkeit: Die Antwort von Pyrolistical ist in der Tat näher am tatsächlichen Ergebnis. Beachten Sie jedoch, dass Sie 12.1 auf keinen Fall genau darstellen können. Wenn Sie die Antworten wie folgt drucken:

System.out.println(new BigDecimal(n));

Die Antworten sind:

Pyro's: 12.0999999999999996447286321199499070644378662109375
Mine: 12.10000000000000142108547152020037174224853515625
Printing 12.1 directly: 12.0999999999999996447286321199499070644378662109375

Verwenden Sie also Pyros Antwort!


1
Dieser Algorithmus scheint anfällig für Gleitkommafehler zu sein. Bei der Implementierung mit JavaScript erhalte ich: 0.06805 -> 0.06810000000000001 und 12.1 -> 12.100000000000001
Ates Goral

12.1 allein kann mit Gleitkomma nicht genau dargestellt werden - es ist kein Ergebnis dieses Algorithmus.
Claudiu

1
Dieser Code in Java erzeugt 12.100000000000001 und verwendet 64-Bit-Doubles, die 12.1 genau darstellen können.
Pyrolistical

4
Es spielt keine Rolle, ob es 64-Bit oder 128-Bit ist. Sie können den Bruch 1/10 nicht mit einer endlichen Summe von Potenzen von 2 darstellen, und so werden Gleitkommazahlen dargestellt
Claudiu

2
Für diejenigen, die sich einschalten, ist die Antwort von Pyrolistical im Grunde präziser als meine, so dass der Gleitkommazahl-Druckalgorithmus '12 .1 'anstelle von '12 .100000000000001' druckt. Seine Antwort ist besser, obwohl ich technisch korrekt war, dass Sie '12 .1 'nicht genau darstellen können.
Claudiu

10

Ist nicht die "kurze und süße" JavaScript-Implementierung

Number(n).toPrecision(sig)

z.B

alert(Number(12345).toPrecision(3)

?

Entschuldigung, ich bin hier nicht scherzhaft, es ist nur so, dass die Verwendung der "roundit" -Funktion von Claudiu und der .toPrecision in JavaScript zu unterschiedlichen Ergebnissen führt, jedoch nur bei der Rundung der letzten Ziffer.

JavaScript:

Number(8.14301).toPrecision(4) == 8.143

.NETZ

roundit(8.14301,4) == 8.144

1
Number(814301).toPrecision(4) == "8.143e+5". Im Allgemeinen nicht das, was Sie wollen, wenn Sie dies Benutzern zeigen.
Zaz

Sehr wahr Josh ja, ich würde .toPrecision () generell nur für Dezimalzahlen empfehlen und die akzeptierte Antwort (mit Bearbeitung) sollte gemäß Ihren individuellen Anforderungen verwendet / überprüft werden.
Justin Wignall

8

Die (sehr schöne!) Lösung von Pyrolistical hat immer noch ein Problem. Der maximale Doppelwert in Java liegt in der Größenordnung von 10 ^ 308, während der minimale Wert in der Größenordnung von 10 ^ -324 liegt. Daher können Probleme auftreten, wenn Sie die Funktion roundToSignificantFiguresauf etwas anwenden , das innerhalb weniger Zehnerpotenzen liegt Double.MIN_VALUE. Zum Beispiel, wenn Sie anrufen

roundToSignificantFigures(1.234E-310, 3);

dann wird die Variable powerwird den Wert 3 - (-309) = 312. Folglich wird der Variable magnitudewird worden Infinity, und es ist alles Müll von da an aus. Glücklicherweise ist dies kein unüberwindbares Problem: Es ist nur der Faktor magnitude , der überläuft. Was wirklich zählt, ist das Produkt num * magnitude , und das läuft nicht über. Eine Möglichkeit, dies zu beheben, besteht darin, die Multiplikation mit dem Faktor magintudein zwei Schritte aufzuteilen:


 public static double roundToNumberOfSignificantDigits(double num, int n) {

    final double maxPowerOfTen = Math.floor(Math.log10(Double.MAX_VALUE));

    if(num == 0) {
        return 0;
    }

    final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
    final int power = n - (int) d;

    double firstMagnitudeFactor = 1.0;
    double secondMagnitudeFactor = 1.0;
    if (power > maxPowerOfTen) {
        firstMagnitudeFactor = Math.pow(10.0, maxPowerOfTen);
        secondMagnitudeFactor = Math.pow(10.0, (double) power - maxPowerOfTen);
    } else {
        firstMagnitudeFactor = Math.pow(10.0, (double) power);
    }

    double toBeRounded = num * firstMagnitudeFactor;
    toBeRounded *= secondMagnitudeFactor;

    final long shifted = Math.round(toBeRounded);
    double rounded = ((double) shifted) / firstMagnitudeFactor;
    rounded /= secondMagnitudeFactor;
    return rounded;
}


6

Wie wäre es mit dieser Java-Lösung:

double roundToSignificantFigure (doppelte Anzahl, int Genauigkeit) {
 neues BigDecimal zurückgeben (num)
            .round (neuer MathContext (Genauigkeit, RoundingMode.HALF_EVEN))
            .doubleValue (); 
}}

3

Hier ist eine modifizierte Version von Ates 'JavaScript, die negative Zahlen verarbeitet.

function sigFigs(n, sig) {
    if ( n === 0 )
        return 0
    var mult = Math.pow(10,
        sig - Math.floor(Math.log(n < 0 ? -n: n) / Math.LN10) - 1);
    return Math.round(n * mult) / mult;
 }

2

Dies kam 5 Jahre zu spät, aber obwohl ich für andere teilen werde, die immer noch das gleiche Problem haben. Ich mag es, weil es einfach ist und keine Berechnungen auf der Codeseite. Weitere Informationen finden Sie unter Integrierte Methoden zum Anzeigen wichtiger Zahlen .

Dies ist, wenn Sie es nur ausdrucken möchten.

public String toSignificantFiguresString(BigDecimal bd, int significantFigures){
    return String.format("%."+significantFigures+"G", bd);
}

Dies ist, wenn Sie es konvertieren möchten:

public BigDecimal toSignificantFigures(BigDecimal bd, int significantFigures){
    String s = String.format("%."+significantFigures+"G", bd);
    BigDecimal result = new BigDecimal(s);
    return result;
}

Hier ist ein Beispiel dafür in Aktion:

BigDecimal bd = toSignificantFigures(BigDecimal.valueOf(0.0681), 2);

Dies zeigt "große" Zahlen in wissenschaftlicher Notation an, z. B. 15k als 1.5e04.
Matt

2

JavaScript:

Number( my_number.toPrecision(3) );

Die NumberFunktion ändert die Ausgabe des Formulars "8.143e+5"in "814300".


1

Haben Sie versucht, es so zu codieren, wie Sie es von Hand tun würden?

  1. Konvertieren Sie die Zahl in eine Zeichenfolge
  2. Zählen Sie am Anfang der Zeichenfolge die Ziffern - führende Nullen sind nicht signifikant, alles andere ist es.
  3. Wenn Sie zur "n-ten" Ziffer gelangen, schauen Sie auf die nächste Ziffer und runden Sie auf, wenn sie 5 oder höher ist.
  4. Ersetzen Sie alle nachfolgenden Ziffern durch Nullen.

1

[Korrigiert am 26.10.2009]

Im Wesentlichen für N signifikante gebrochene Zahlen:

• Multiplizieren Sie die Zahl mit 10 N
• Addieren Sie 0,5
• Schneiden Sie die Bruchziffern ab (dh schneiden Sie das Ergebnis in eine ganze Zahl ab)
• Teilen Sie durch 10 N.

Für N signifikante ganzzahlige (nicht gebrochene) Ziffern:

• Teilen Sie die Zahl durch 10 N
• Addieren Sie 0,5
• Schneiden Sie die Bruchziffern ab (dh schneiden Sie das Ergebnis in eine ganze Zahl ab)
• Multiplizieren Sie mit 10 N.

Sie können dies beispielsweise auf jedem Taschenrechner tun, der über einen Operator "INT" (Integer Truncation) verfügt.


Nee. Lesen Sie die Frage noch einmal. 1239451 mit 3 Sig Feigen unter Verwendung Ihres Algorithmus würde fälschlicherweise 123951
Pyrolistical

Ja, ich habe es korrigiert, um zwischen einer Rundung auf eine gebrochene Anzahl von Stellen (rechts vom Dezimalpunkt) und einer ganzzahligen Anzahl von Stellen (links) zu unterscheiden.
David R Tribble

1
/**
 * Set Significant Digits.
 * @param value value
 * @param digits digits
 * @return
 */
public static BigDecimal setSignificantDigits(BigDecimal value, int digits) {
    //# Start with the leftmost non-zero digit (e.g. the "1" in 1200, or the "2" in 0.0256).
    //# Keep n digits. Replace the rest with zeros.
    //# Round up by one if appropriate.
    int p = value.precision();
    int s = value.scale();
    if (p < digits) {
        value = value.setScale(s + digits - p); //, RoundingMode.HALF_UP
    }
    value = value.movePointRight(s).movePointLeft(p - digits).setScale(0, RoundingMode.HALF_UP)
        .movePointRight(p - digits).movePointLeft(s);
    s = (s > (p - digits)) ? (s - (p - digits)) : 0;
    return value.setScale(s);
}

1

Hier ist der Code von Pyrolistical (derzeit die beste Antwort) in Visual Basic.NET, falls jemand ihn benötigt:

Public Shared Function roundToSignificantDigits(ByVal num As Double, ByVal n As Integer) As Double
    If (num = 0) Then
        Return 0
    End If

    Dim d As Double = Math.Ceiling(Math.Log10(If(num < 0, -num, num)))
    Dim power As Integer = n - CInt(d)
    Dim magnitude As Double = Math.Pow(10, power)
    Dim shifted As Double = Math.Round(num * magnitude)
    Return shifted / magnitude
End Function

0

Dies ist eine, die ich mir in VB ausgedacht habe:

Function SF(n As Double, SigFigs As Integer)
    Dim l As Integer = n.ToString.Length
    n = n / 10 ^ (l - SigFigs)
    n = Math.Round(n)
    n = n * 10 ^ (l - SigFigs)
    Return n
End Function

0

return new BigDecimal(value, new MathContext(significantFigures, RoundingMode.HALF_UP)).doubleValue();


0

Ich brauchte dies in Go, was durch das Fehlen der Go-Standardbibliothek math.Round()(vor go1.10) etwas kompliziert wurde . Also musste ich das auch aufpeitschen. Hier ist meine Übersetzung der ausgezeichneten Antwort von Pyrolistical :

// TODO: replace in go1.10 with math.Round()
func round(x float64) float64 {
    return float64(int64(x + 0.5))
}

// SignificantDigits rounds a float64 to digits significant digits.
// Translated from Java at https://stackoverflow.com/a/1581007/1068283
func SignificantDigits(x float64, digits int) float64 {
    if x == 0 {
        return 0
    }

    power := digits - int(math.Ceil(math.Log10(math.Abs(x))))
    magnitude := math.Pow(10, float64(power))
    shifted := round(x * magnitude)
    return shifted / magnitude
}

Dies hat eine mysteriöse Ablehnung! Aber ich kann keinen Fehler oder kein Problem darin finden. Was ist denn hier los?
Michael Hampton

0

Sie können alle diese Berechnungen mit Potenzen von 10 usw. vermeiden, indem Sie einfach FloatToStrF verwenden.

Mit FloatToStrF können Sie (unter anderem) die Genauigkeit (Anzahl der signifikanten Ziffern) des ausgegebenen Werts (der eine Zeichenfolge sein wird) auswählen. Natürlich können Sie dann StrToFloat darauf anwenden, um Ihren gerundeten Wert als Float zu erhalten.

Siehe hier:

http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/SysUtils_FloatToStrF@Extended@TFloatFormat@Integer@Integer.html


-1
public static double roundToSignificantDigits(double num, int n) {
    return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString());
}

Dieser Code verwendet die integrierte Formatierungsfunktion, die in eine Rundungsfunktion umgewandelt wird

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.