web-dev-qa-db-fra.com

Arrondir à un nombre arbitraire de chiffres significatifs

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.

78
DougN

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;
}
100
Pyrolistical

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!

15
Claudiu

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
15
Ates Goral

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
10
Justin Wignall

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;
}
7
Thomas Becker

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 (); 
} 
6
wolfgang grinfeld

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;
 }
3
Jason Swank

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);
2
JackDev

[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).

1
David R Tribble

JavaScript:

Number( my_number.toPrecision(3) );

La fonction Number changera la sortie du formulaire "8.143e+5" en "814300".

1
Zaz

Avez-vous essayé de le coder comme vous le feriez à la main?

  1. Convertir le nombre en chaîne
  2. À partir du début de la chaîne , Comptez le nombre de chiffres - les zéros non significatifs ne sont pas Significatifs, tout le reste.
  3. Lorsque vous atteignez le "nième" chiffre, Regardez le chiffre suivant et, si .__ est 5 ou plus, arrondissez.
  4. Remplacez tous les derniers chiffres par des zéros.
1
Mark Bessey

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);
}
1
Valeri Shibaev

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
0
SomeGuy

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
}
0
Michael Hampton