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.

77
demandé sur Peter Mortensen 2008-10-14 22:37:17

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;
}
100
répondu Pyrolistical 2014-01-17 00:42:06

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
15
répondu Ates Goral 2008-10-15 04:09:16

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!

15
répondu Claudiu 2009-12-16 03:59:04

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
10
répondu Justin Wignall 2009-04-08 15:26:57

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

7
répondu Thomas Becker 2010-11-19 01:17:06

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(); 
}
6
répondu wolfgang grinfeld 2010-08-10 08:00:42

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;
 }
3
répondu Jason Swank 2010-06-04 16:18:40

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);
2
répondu JackDev 2017-05-23 12:18:08

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

  1. convertissez le nombre en chaîne de caractères
  2. à partir du début du chaîne, le comte chiffres - zéros ne sont pas important, tout le reste est.
  3. quand vous arrivez au chiffre "nth" , regardez en avant au prochain chiffre et si c'est 5 ou plus, le round up.
  4. remplacer tous les chiffres de queue par des zéros.
1
répondu Mark Bessey 2008-10-14 18:52:37

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

1
répondu David R Tribble 2009-10-26 21:52:47
/**
 * 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
répondu Valeri Shibaev 2010-06-22 00:25:30

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
1
répondu Michael Zlatkovsky - Microsoft 2011-06-21 16:10:45

JavaScript:

Number( my_number.toPrecision(3) );

la fonction Number modifiera la sortie de la forme "8.143e+5" en "814300" .

1
répondu Zaz 2015-05-04 22:56:25

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
0
répondu SomeGuy 2013-06-01 16:05:51

return new BigDecimal(value, new MathContext(significantFigures, RoundingMode.HALF_UP)).doubleValue();

0
répondu Duncan Calvert 2017-03-27 16:22:43

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
}
0
répondu Michael Hampton 2018-01-28 02:42:21
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

-1
répondu Harikrishnan 2013-05-19 05:51:47