Question

Comment pouvez-vous arrondir un nombre (pas seulement 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 retourner 1 240 000

12.1257 et retournez 12.1

.0681 et retourner .0681

5 et retournez 5

Naturellement, l’algorithme ne doit pas être codé en dur pour ne traiter que le N de 3, bien que ce soit un début.

Était-ce utile?

La solution

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é, changé power en un entier de type pour éviter les problèmes de flottement lorsque n - d est terminé, et rendu le long intermédiaire plus clair

Le problème a été causé par la multiplication d’un nombre élevé par un nombre réduit. Au lieu de cela, je divise deux nombres de taille similaire.

MODIFIER
Correction de plus de bugs. Ajout d'une vérification 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 le 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;
}

Autres conseils

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

SOMMAIRE:

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 le journal 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 & Lt; 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 annulez le pow.

roundednum = pow(roundedrest, -(power))

Où puissance est la puissance calculée ci-dessus.

À propos de la précision: la réponse de Pyrolistical est en réalité 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!

N'est-ce pas & "Court and Sweet &"; Implémentation JavaScript

Number(n).toPrecision(sig)

par exemple

alert(Number(12345).toPrecision(3)

?

Désolé, je ne suis pas facétieux ici, c'est simplement que vous utilisez le & "roundit &"; Les fonctions de Claudiu et de .toPrecision en JavaScript me donnent 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 (très sympa!) de Pyrolistical a toujours un problème. La valeur maximale double 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 rencontrer des problèmes lorsque vous appliquez la fonction roundToSignificantFigures à quelque chose qui se situe à quelques puissances de moins 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 sera tout bousiller à partir de maintenant. Heureusement, ce n’est pas un problème insurmontable: c’est seulement le facteur num * magnitude qui déborde. Ce qui compte vraiment, c’est le produit magintude, et cela ne déborde pas. Une façon de résoudre ce problème consiste à scinder la multiplication par le facteur <=> 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;
}

Qu'en est-il 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 code 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 côté code. Voir Méthodes intégrées d'affichage de chiffres significatifs pour plus d'informations. info.

C’est le cas si vous souhaitez simplement 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;
}

En 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?

  1. Convertissez le nombre en chaîne
  2. À partir du début de la chaîne, nombre de chiffres - les zéros au début ne sont pas significatif, tout le reste est.
  3. Quand vous arrivez à la " nième " chiffre, coup d'oeil devant le prochain chiffre et si 5 ou plus, arrondissez.
  4. Remplacez tous les derniers chiffres par des zéros.

[Corrigé, 2009-10-26]

Essentiellement, pour N chiffres fractionnaires significatifs:

& # 8226; Multipliez le nombre par 10 N
& # 8226; Ajouter 0.5
& # 8226; Tronquer les fractions de chiffres (c.-à-d. Tronquer le résultat en un entier)

& # 8226; Diviser par 10 N

Pour N intégrale significative (non fractionnelle):

& # 8226; Divisez le nombre par 10 N
& # 8226; Ajouter 0.5
& # 8226; Tronquer les fractions de chiffres (c.-à-d. Tronquer le résultat en un entier)

& # 8226; Multiplier par 10 N

Vous pouvez le faire sur n'importe quelle calculatrice, par exemple, qui a un & "INT &"; (troncature d’entier).

/**
 * 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 (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

JavaScript:

Number( my_number.toPrecision(3) );

La fonction Number changera la sortie du formulaire "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'avais besoin de cela dans 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 dû préparer ça aussi. 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 devient une fonction d'arrondi

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top