L'arrondi à un nombre arbitraire de chiffres significatifs
Comment Pouvez-vous arrondir n'importe quel Nombre (pas seulement des entiers > 0) à N chiffres significatifs?
Par exemple, si je veux ronde à trois chiffres significatifs, je suis à la recherche d'une formule qui pourrait prendre:
1,239,451 et retour 1,240,000
12.1257 et retour 12.1
.0681 et retour .0681
5 et retour 5
naturellement le l'algorithme ne doit pas être codé de manière à ne traiter que N sur 3, bien que ce soit un début.
17 réponses
voici le même code en Java sans le bug 12.100000000000001 d'autres réponses ont
j'ai aussi supprimé le code répété, changé power
en un entier de type pour prévenir les problèmes flottants quand n - d
est fait, et a rendu le long Intermédiaire plus clair
le bogue a été causé en multipliant un grand nombre avec un petit nombre. Au lieu de diviser deux nombres de taille similaire.
EDIT
Correction d'autres bugs. Ajouté vérifier pour 0 car il en résulterait NaN. Fait la fonction fonctionne réellement avec des nombres négatifs (le code original ne traite pas les nombres négatifs parce qu'un journal d'un nombre négatif est un nombre complexe)
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;
}
voici une implémentation JavaScript courte et douce:
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
résumé:
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);
}
vous devez donc trouver la place décimale du premier chiffre non nul, puis sauvegarder les N-1 chiffres suivants, puis arrondir le nième chiffre basé sur le reste.
nous pouvons utiliser log pour faire la première.
log 1239451 = 6.09
log 12.1257 = 1.08
log 0.0681 = -1.16
donc pour les nombres > 0, prendre le ceil du journal. Pour les nombres < 0, prendre le plancher du rondin.
maintenant nous avons le chiffre d
: 7 en le premier cas, 2 dans le 2, -2 à la 3ème.
nous devons arrondir le chiffre 151980920. Quelque chose comme:
double roundedrest = num * pow(10, -(d-N));
pow(1239451, -4) = 123.9451
pow(12.1257, 1) = 121.257
pow(0.0681, 4) = 681
puis faire la chose d'arrondi standard:
roundedrest = (int)(roundedrest + 0.5);
et annulez le pow.
roundednum = pow(roundedrest, -(power))
Où la puissance est la puissance calculée ci-dessus.
à propos de la précision: la réponse de Pyrolistical est en effet plus proche du résultat réel. Mais notez que vous ne pouvez pas représenter 12.1 exactement, en tout cas. Si vous imprimez les réponses comme suit:
System.out.println(new BigDecimal(n));
les réponses sont:
Pyro's: 12.0999999999999996447286321199499070644378662109375
Mine: 12.10000000000000142108547152020037174224853515625
Printing 12.1 directly: 12.0999999999999996447286321199499070644378662109375
donc, utilisez la réponse de Pyro!
N'est pas L'implémentation JavaScript "courte et douce
Number(n).toPrecision(sig)
p.ex.
alert(Number(12345).toPrecision(3)
?
Désolé, Je ne suis pas facétieux ici, c'est juste qu'utiliser la fonction "roundit" de Claudiu et the .la décision en JavaScript me donne des résultats différents mais seulement dans l'arrondissement du dernier chiffre.
JavaScript:
Number(8.14301).toPrecision(4) == 8.143
.NET
roundit(8.14301,4) == 8.144
Pyrolistical (très agréable! la solution a encore un problème. La valeur double maximale en Java est de l'ordre de 10^308, alors que la valeur minimale est de l'ordre de 10^ - 324. Par conséquent, vous pouvez rencontrer des problèmes lors de l'application de la fonction roundToSignificantFigures
à quelque chose qui est à l'intérieur de quelques puissances de dix Double.MIN_VALUE
. Par exemple, lorsque vous appelez
roundToSignificantFigures(1.234E-310, 3);
alors la variable power
aura la valeur 3 - (-309) = 312. Par conséquent, la variable magnitude
deviendra Infinity
, et c'est des déchets à partir de là. Heureusement, ce n'est pas un problème insurmontable: c'est seulement la facteur magnitude
qui déborde. Ce qui importe vraiment est le produit "1519140920 num * magnitude
, et cela ne déborde pas. Une façon de résoudre ce problème est de diviser la multiplication par le facteur magintude
en deux étapes:
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;
}
Que pensez-vous de cette solution java:
double roundToSignificantFigure(double num, int precision){ return new BigDecimal(num) .round(new MathContext(precision, RoundingMode.HALF_EVEN)) .doubleValue(); }
Voici une version modifiée du JavaScript D'Ates qui traite les nombres négatifs.
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;
}
c'est arrivé avec 5 ans de retard, mais je partagerai avec d'autres le même problème. Je l'aime parce que c'est simple et aucun calcul du côté du code. Voir méthodes intégrées pour afficher les chiffres significatifs pour plus d'information.
C'est si vous voulez juste l'imprimer.
public String toSignificantFiguresString(BigDecimal bd, int significantFigures){
return String.format("%."+significantFigures+"G", bd);
}
C'est si vous voulez convertir:
public BigDecimal toSignificantFigures(BigDecimal bd, int significantFigures){
String s = String.format("%."+significantFigures+"G", bd);
BigDecimal result = new BigDecimal(s);
return result;
}
voici un exemple en action:
BigDecimal bd = toSignificantFigures(BigDecimal.valueOf(0.0681), 2);
avez-vous essayé de le coder comme vous le feriez à la main?
- convertissez le nombre en chaîne de caractères
- à partir du début du chaîne, le comte chiffres - zéros ne sont pas important, tout le reste est.
- quand vous arrivez au chiffre "nth" , regardez en avant au prochain chiffre et si c'est 5 ou plus, le round up.
- remplacer tous les chiffres de queue par des zéros.
[corrigé, 2009-10-26]
essentiellement, pour N significatif fractionnaire chiffres:
• multiplier le nombre par 10 N
"
* Ajouter 0.5
• Tronquer les chiffres de la fraction (c.-à-d. tronquer le résultat en un entier)
* Diviser par 10 N
Pour N significatif intégrale (non-fractionnée) chiffres:
• divisez le nombre par 10 N
* Ajouter 0.5
• Tronquer les chiffres de la fraction (c.-à-d. tronquer le résultat en un entier)
* Multiplier par 10 N
vous pouvez faire cela sur n'importe quelle calculatrice, par exemple, qui a un "INT" (entier troncature) opérateur.
/**
* 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);
}
voici le code de Pyrolistical (TOP answer) dans Visual Basic.NET, si quelqu'un en a besoin:
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
JavaScript:
Number( my_number.toPrecision(3) );
la fonction Number
modifiera la sortie de la forme "8.143e+5"
en "814300"
.
C'est celui que j'ai trouvé dans VB:
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
return new BigDecimal(value, new MathContext(significantFigures, RoundingMode.HALF_UP)).doubleValue();
j'en avais besoin à Go, ce qui était un peu compliqué par le manque de math.Round()
de la bibliothèque standard de Go (avant go1.10). J'ai donc eu à fouetter que trop. Voici ma traduction de excellente réponse de 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
}
public static double roundToSignificantDigits(double num, int n) {
return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString());
}
ce code utilise la fonction de formatage intégrée qui est transformée en une fonction d'arrondissement