Comment pouvez-vous arrondir un nombre quelconque (pas uniquement des entiers> 0) à N chiffres significatifs?
Par exemple, si je veux arrondir à trois chiffres significatifs, je cherche 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, l’algorithme ne doit pas être codé en dur pour ne traiter que N sur 3, bien que ce soit un début.
Voici le même code en Java sans le bogue 12.100000000000001 que les autres réponses ont
J'ai également supprimé le code répété, modifié power
en un entier de type pour éviter les problèmes flottants lorsque n - d
est terminé, et rendu le long intermédiaire plus clair
Le bug a été provoqué par la multiplication d'un grand nombre par un petit nombre. Au lieu de cela, je divise deux nombres de taille similaire.
MODIFIER
Correction de plus de bugs. Ajout du contrôle pour 0 car cela donnerait NaN. Fait en sorte que la fonction fonctionne réellement avec des nombres négatifs (le code d'origine ne gère pas les nombres négatifs car 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;
}
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 rechercher la décimale du premier chiffre différent de zéro, puis enregistrer les N-1 chiffres suivants, puis arrondir le Nième chiffre en fonction du reste.
Nous pouvons utiliser log pour faire le premier.
log 1239451 = 6.09
log 12.1257 = 1.08
log 0.0681 = -1.16
Donc, pour les nombres> 0, prenez le plafond du journal. Pour les nombres <0, prenez la parole du journal.
Nous avons maintenant le chiffre d
: 7 dans le premier cas, 2 dans le deuxième, -2 dans le troisième.
Nous devons arrondir le (d-N)
ème chiffre. 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
Ensuite, effectuez l’arrondi standard:
roundedrest = (int)(roundedrest + 0.5);
Et défaire le pow.
roundednum = pow(roundedrest, -(power))
Où le pouvoir est le pouvoir calculé 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 dans tous les 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
Alors, utilisez la réponse de Pyro!
Voici une courte et douce implémentation de JavaScript:
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
La mise en oeuvre de JavaScript "courte et douce" n'est-elle pas
Number(n).toPrecision(sig)
par exemple.
alert(Number(12345).toPrecision(3)
?
Désolé, je ne suis pas facétieux ici, mais l'utilisation de la fonction "roundit" de Claudiu et du paramètre .toPrecision en JavaScript me permet d'obtenir des résultats différents, mais uniquement en arrondissant le dernier chiffre.
JavaScript:
Number(8.14301).toPrecision(4) == 8.143
.NET
roundit(8.14301,4) == 8.144
La solution de Pyrolistical (très agréable!) A toujours un problème. La valeur double maximale en Java est de l’ordre de 10 ^ 308, tandis que la valeur minimale est de l’ordre de 10 ^ -324. Par conséquent, vous pouvez avoir des problèmes lorsque vous appliquez la fonction roundToSignificantFigures
à quelque chose qui se situe à quelques puissances de dix sur 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 ce ne sera plus qu'une ordure. Heureusement, ce n’est pas un problème insurmontable: c’est seulement le facteurmagnitude
qui déborde. Ce qui compte vraiment, c'est le produit num * magnitude
, qui ne déborde pas. Une façon de résoudre ce problème consiste à 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 diriez-vous de cette solution Java:
double roundToSignificantFigure (double num, int precision) { retourne le nouveau BigDecimal (num) .round (new MathContext (precision, RoundingMode.HALF_EVEN)) .doubleValue (); }
Voici une version modifiée de JavaScript d'Ates qui gère 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;
}
Cela est arrivé 5 ans en retard, mais je partagerai avec d'autres qui ont toujours le même problème J'aime ça parce que c'est simple et pas de calculs du côté du code. Voir Méthodes intégrées d'affichage de chiffres significatifs pour plus d'informations.
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 le 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);
[Corrigé, 2009-10-26]
Essentiellement, pour N significatif fractionnaire chiffres:
• Multipliez le nombre par 10N
• Ajouter 0,5
• Tronquer les fractions de chiffres (c’est-à-dire tronquer le résultat en un entier)
• Diviser par 10N
Pour N chiffres significatifs intégraux (non fractionnaires):
• Diviser le nombre par 10N
• Ajouter 0,5
• Tronquer les fractions de chiffres (c’est-à-dire tronquer le résultat en un entier)
• Multipliez par 10N
Vous pouvez le faire sur n'importe quelle calculatrice, par exemple, qui possède un opérateur "INT" (troncature d'entier).
Number( my_number.toPrecision(3) );
La fonction Number
changera la sortie du formulaire "8.143e+5"
en "814300"
.
Avez-vous essayé de le coder comme vous le feriez à la main?
Voici le code de Pyrolistical (actuellement la meilleure réponse) dans Visual Basic.NET, si quelqu'un en avait 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
/**
* 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);
}
C’est celui que j’ai trouvé en 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
J'avais besoin de cela dans Go, ce qui était un peu compliqué par le manque de math.Round()
de la bibliothèque standard Go (avant go1.10). J'ai donc dû préparer ça aussi. Voici ma traduction de l'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
}