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.

DougN
quelle
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;
}
Pyrolistisch
quelle
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
Ates Goral
quelle
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!

Claudiu
quelle
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
Justin Wignall
quelle
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;
}

Thomas Becker
quelle
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 (); 
}}
wolfgang grinfeld
quelle
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;
 }
Jason Swank
quelle
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);
JackDev
quelle
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".

Zaz
quelle
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.
Mark Bessey
quelle
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.

David R Tribble
quelle
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);
}
Valeri Shibaev
quelle
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
Michael Zlatkovsky - Microsoft
quelle
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
Irgendein Typ
quelle
0

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

Duncan Calvert
quelle
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
}
Michael Hampton
quelle
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@[email protected]

George Soden-Freeth
quelle
-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

Harikrishnan
quelle