Wie können Sie beliebige Zahl (nicht nur ganze Zahlen> 0) auf N signifikante Ziffern runden?
Wenn ich zum Beispiel auf drei signifikante Stellen runden möchte, suche ich nach einer Formel, die Folgendes annehmen könnte:
1 239 451 und Rückgabe 1 240 000
12.1257 und zurück 12.1
.0681 und zurück .0681
5 und zurück 5
Natürlich sollte der Algorithmus nicht hart codiert sein, um nur N von 3 zu handhaben, obwohl dies ein Anfang wäre.
Hier ist derselbe Code in Java ohne den 12.100000000000001-Fehler, den andere Antworten haben
Ich habe auch wiederholten Code entfernt, power
in einen Typ Integer geändert, um Floating-Probleme zu verhindern, wenn n - d
ausgeführt wurde, und das lange Intermediat klarer gemacht
Der Fehler wurde durch Multiplikation einer großen Zahl mit einer kleinen Zahl verursacht. Stattdessen teile ich zwei Zahlen von ähnlicher Größe.
EDIT
Weitere Fehler behoben. Check für 0 hinzugefügt, da dies zu NaN führen würde. Funktion mit negativen Zahlen funktionieren (Der ursprüngliche Code behandelt negative Zahlen nicht, weil 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;
}
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 Nicht-Null-Ziffer ermitteln, die nächsten N-1-Ziffern speichern und dann die N-te Stelle auf Basis der restlichen Zahlen 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 Decke des Protokolls. Bei Zahlen <0 nehmen Sie den Boden des Protokolls ein.
Jetzt haben wir die Ziffer d
: 7 im ersten Fall, 2 im 2., -2 im 3..
Wir müssen die (d-N)
th Ziffer runden. So 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 Standard-Rundungssache:
roundedrest = (int)(roundedrest + 0.5);
Und mach den Pow los.
roundednum = pow(roundedrest, -(power))
Wo Leistung die oben berechnete Leistung ist.
Über die Genauigkeit: Die Antwort von Pyrolistical liegt tatsächlich näher am tatsächlichen Ergebnis. Beachten Sie jedoch, dass Sie 12.1 auf keinen Fall exakt darstellen können. Wenn Sie die Antworten wie folgt ausdrucken:
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 die Antwort von Pyro!
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
Ist nicht die "kurze und süße" JavaScript-Implementierung
Number(n).toPrecision(sig)
z.B.
alert(Number(12345).toPrecision(3)
?
Tut mir leid, ich bin hier nicht verrückt, es ist nur so, dass die Verwendung der "roundit" -Funktion von Claudiu und der .toPrecision-Funktion in JavaScript andere Ergebnisse liefert, jedoch nur in der Rundung der letzten Ziffer.
JavaScript:
Number(8.14301).toPrecision(4) == 8.143
.NETZ
roundit(8.14301,4) == 8.144
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 kann es zu Problemen kommen, wenn die Funktion roundToSignificantFigures
auf etwas angewendet wird, das nur wenige Zehnerpotenzen von Double.MIN_VALUE
besitzt. Zum Beispiel, wenn Sie anrufen
roundToSignificantFigures(1.234E-310, 3);
dann hat die Variable power
den Wert 3 - (-309) = 312. Folglich wird die Variable magnitude
Infinity
, und von da an ist alles Müll. Glücklicherweise ist dies kein unüberwindbares Problem: Nur der Faktor magnitude
läuft über. Was wirklich zählt, ist das Produkt num * magnitude
, und das läuft nicht über. Eine Möglichkeit zur Lösung dieses Problems besteht darin, die Multiplikation mit dem Faktor magintude
in 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;
}
Wie wäre es mit dieser Java-Lösung:
double roundToSignificantFigure (doppelte Anzahl, int-Genauigkeit) { return new BigDecimal (num) .round (neuer MathContext (Genauigkeit, RoundingMode.HALF_EVEN)) .doubleValue (); }
Hier ist eine modifizierte Version von Ates 'JavaScript, die negative Zahlen behandelt.
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;
}
Dies kam fünf Jahre zu spät, aber ich teile es mit anderen, die immer noch das gleiche Problem haben. Ich mag es, weil es einfach ist und keine Berechnungen auf der Codeseite. Siehe Eingebaute Methoden zum Anzeigen von signifikanten Zahlen für weitere Informationen.
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 davon in Aktion:
BigDecimal bd = toSignificantFigures(BigDecimal.valueOf(0.0681), 2);
[korrigiert, 2009-10-26]
Im Wesentlichen für N signifikante gebrochene Ziffern:
• Multiplizieren Sie die Zahl mit 10N
• Addiere 0,5
• Die Nachkommastellen abschneiden (d. H. Das Ergebnis in eine ganze Zahl abschneiden)
• Durch 10 teilenN
Für N signifikante integrale (nicht gebrochene) Ziffern:
• Teilen Sie die Zahl durch 10N
• Addiere 0,5
• Die Nachkommastellen abschneiden (d. H. Das Ergebnis in eine ganze Zahl abschneiden)
• Mit 10 multiplizierenN
Sie können dies auf jedem Rechner tun, der beispielsweise einen Operator "INT" (Integer-Kürzung) hat.
Number( my_number.toPrecision(3) );
Die Funktion Number
ändert die Ausgabe der Form "8.143e+5"
in "814300"
.
Haben Sie versucht, es so zu programmieren, wie Sie es von Hand machen würden?
Hier ist der Pyrolistical-Code (derzeit die Top-Antwort) in Visual Basic.NET, falls es jemand 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
/**
* 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);
}
return new BigDecimal(value, new MathContext(significantFigures, RoundingMode.HALF_UP)).doubleValue();
Dies ist einer, den ich in VB gefunden 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
Ich brauchte dies in Go, was ein wenig kompliziert war, weil die Standardbibliothek der Go-Bibliothek math.Round()
(vor go1.10) fehlte. Also musste ich das auch noch putzen. Hier ist meine Übersetzung von Pyrolisticals ausgezeichnete Antwort :
// 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
}