Arrondir un double à x chiffres significatifs

Si j’ai un double (234.004223), etc., je voudrais arrondir ce chiffre à x chiffres significatifs en C #.

Jusqu’à présent, je ne peux trouver que des moyens d’arrondir à x décimales, mais cela supprime simplement la précision s’il y a des 0 dans le nombre.

Par exemple, 0,086 à une décimale devient 0,1, mais j’aimerais qu’elle rest à 0,08.

Le framework n’a pas de fonction intégrée pour arrondir (ou tronquer, comme dans votre exemple) un nombre de chiffres significatifs. Une façon de le faire est de mettre votre chiffre à l’échelle de manière à ce que votre premier chiffre significatif soit juste après le point décimal, arrondi (ou tronqué), puis réduit. Le code suivant devrait faire l’affaire:

static double RoundToSignificantDigits(this double d, int digits){ if(d == 0) return 0; double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1); return scale * Math.Round(d / scale, digits); } 

Si, comme dans votre exemple, vous voulez vraiment tronquer, alors vous voulez:

 static double TruncateToSignificantDigits(this double d, int digits){ if(d == 0) return 0; double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1 - digits); return scale * Math.Truncate(d / scale); } 

J’utilise la fonction sigfig de pDaddy depuis quelques mois et j’ai trouvé un bogue. Vous ne pouvez pas prendre le journal d’un nombre négatif, donc si d est négatif, le résultat est NaN.

Ce qui suit corrige le bogue:

 public static double SetSigFigs(double d, int digits) { if(d == 0) return 0; decimal scale = (decimal)Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1); return (double) (scale * Math.Round((decimal)d / scale, digits)); } 

Il me semble que vous ne voulez pas du tout arrondir à x décimales – vous voulez arrondir à x chiffres significatifs. Donc, dans votre exemple, vous voulez arrondir 0,086 à un chiffre significatif, pas une décimale.

Maintenant, utiliser un double et arrondir à un certain nombre de chiffres significatifs est problématique au départ, en raison de la façon dont les doubles sont stockés. Par exemple, vous pouvez arrondir 0.12 à quelque chose près de 0.1, mais 0.1 n’est pas exactement représentable en double. Êtes-vous sûr de ne pas utiliser de décimales? Sinon, est-ce réellement à des fins d’affichage? Si c’est à des fins d’affichage, je pense que vous devriez convertir le double directement en une chaîne avec le nombre approprié de chiffres significatifs.

Si vous pouvez répondre à ces points, je peux essayer de trouver un code approprié. Aussi grave que cela puisse paraître, la conversion en nombre de chiffres significatifs sous forme de chaîne en convertissant le nombre en une chaîne “complète”, puis trouver le premier chiffre significatif (et en prenant ensuite une action d’arrondi appropriée) pourrait bien être la meilleure solution. .

Si c’est à des fins d’affichage (comme vous le dites dans le commentaire de la réponse de Jon Skeet), vous devez utiliser un spécificateur de format GN . Où n est le nombre de chiffres significatifs – exactement ce que vous recherchez.

Voici l’exemple d’utilisation si vous voulez 3 chiffres significatifs (la sortie imprimée est dans le commentaire de chaque ligne):

  Console.WriteLine(1.2345e-10.ToSsortingng("G3"));//1.23E-10 Console.WriteLine(1.2345e-5.ToSsortingng("G3")); //1.23E-05 Console.WriteLine(1.2345e-4.ToSsortingng("G3")); //0.000123 Console.WriteLine(1.2345e-3.ToSsortingng("G3")); //0.00123 Console.WriteLine(1.2345e-2.ToSsortingng("G3")); //0.0123 Console.WriteLine(1.2345e-1.ToSsortingng("G3")); //0.123 Console.WriteLine(1.2345e2.ToSsortingng("G3")); //123 Console.WriteLine(1.2345e3.ToSsortingng("G3")); //1.23E+03 Console.WriteLine(1.2345e4.ToSsortingng("G3")); //1.23E+04 Console.WriteLine(1.2345e5.ToSsortingng("G3")); //1.23E+05 Console.WriteLine(1.2345e10.ToSsortingng("G3")); //1.23E+10 

J’ai trouvé deux bugs dans les méthodes de P Daddy et Eric. Cela résout par exemple l’erreur de précision qui a été présentée par Andrew Hancox dans cette Q & R. Il y avait aussi un problème avec les directions rondes. 1050 avec deux chiffres significatifs n’est pas 1000.0, c’est 1100.0. L’arrondi a été corrigé avec MidpointRounding.AwayFromZero.

 static void Main(ssortingng[] args) { double x = RoundToSignificantDigits(1050, 2); // Old = 1000.0, New = 1100.0 double y = RoundToSignificantDigits(5084611353.0, 4); // Old = 5084999999.999999, New = 5085000000.0 double z = RoundToSignificantDigits(50.846, 4); // Old = 50.849999999999994, New = 50.85 } static double RoundToSignificantDigits(double d, int digits) { if (d == 0.0) { return 0.0; } else { double leftSideNumbers = Math.Floor(Math.Log10(Math.Abs(d))) + 1; double scale = Math.Pow(10, leftSideNumbers); double result = scale * Math.Round(d / scale, digits, MidpointRounding.AwayFromZero); // Clean possible precision error. if ((int)leftSideNumbers >= digits) { return Math.Round(result, 0, MidpointRounding.AwayFromZero); } else { return Math.Round(result, digits - (int)leftSideNumbers, MidpointRounding.AwayFromZero); } } } 

Comme le mentionne Jon Skeet: mieux gérer cela dans le domaine textuel. En règle générale: à des fins d’affichage, n’essayez pas d’arrondir / modifier vos valeurs en virgule flottante, cela ne fonctionne jamais à 100%. L’affichage est une préoccupation secondaire et vous devez gérer toutes les exigences de formatage spéciales, telles que celles travaillant avec des chaînes.

Ma solution ci-dessous, que j’ai implémentée il y a plusieurs années, s’est avérée très fiable. Il a été minutieusement testé et fonctionne très bien également. Environ 5 fois plus de temps d’exécution que la solution de P Daddy / Eric.

Exemples d’entrée + sortie donnés ci-dessous dans le code.

 using System; using System.Text; namespace KZ.SigDig { public static class SignificantDigits { public static ssortingng DecimalSeparator; static SignificantDigits() { System.Globalization.CultureInfo ci = System.Threading.Thread.CurrentThread.CurrentCulture; DecimalSeparator = ci.NumberFormat.NumberDecimalSeparator; } ///  /// Format a double to a given number of significant digits. ///  ///  /// 0.086 -> "0.09" (digits = 1) /// 0.00030908 -> "0.00031" (digits = 2) /// 1239451.0 -> "1240000" (digits = 3) /// 5084611353.0 -> "5085000000" (digits = 4) /// 0.00000000000000000846113537656557 -> "0.00000000000000000846114" (digits = 6) /// 50.8437 -> "50.84" (digits = 4) /// 50.846 -> "50.85" (digits = 4) /// 990.0 -> "1000" (digits = 1) /// -5488.0 -> "-5000" (digits = 1) /// -990.0 -> "-1000" (digits = 1) /// 0.0000789 -> "0.000079" (digits = 2) ///  public static ssortingng Format(double number, int digits, bool showTrailingZeros = true, bool alwaysShowDecimalSeparator = false) { if (Double.IsNaN(number) || Double.IsInfinity(number)) { return number.ToSsortingng(); } ssortingng sSign = ""; ssortingng sBefore = "0"; // Before the decimal separator ssortingng sAfter = ""; // After the decimal separator if (number != 0d) { if (digits < 1) { throw new ArgumentException("The digits parameter must be greater than zero."); } if (number < 0d) { sSign = "-"; number = Math.Abs(number); } // Use scientific formatting as an intermediate step string sFormatString = "{0:" + new String('#', digits) + "E0}"; string sScientific = String.Format(sFormatString, number); string sSignificand = sScientific.Substring(0, digits); int exponent = Int32.Parse(sScientific.Substring(digits + 1)); // (the significand now already contains the requested number of digits with no decimal separator in it) StringBuilder sFractionalBreakup = new StringBuilder(sSignificand); if (!showTrailingZeros) { while (sFractionalBreakup[sFractionalBreakup.Length - 1] == '0') { sFractionalBreakup.Length--; exponent++; } } // Place decimal separator (insert zeros if necessary) int separatorPosition = 0; if ((sFractionalBreakup.Length + exponent) < 1) { sFractionalBreakup.Insert(0, "0", 1 - sFractionalBreakup.Length - exponent); separatorPosition = 1; } else if (exponent > 0) { sFractionalBreakup.Append('0', exponent); separatorPosition = sFractionalBreakup.Length; } else { separatorPosition = sFractionalBreakup.Length + exponent; } sBefore = sFractionalBreakup.ToSsortingng(); if (separatorPosition < sBefore.Length) { sAfter = sBefore.Substring(separatorPosition); sBefore = sBefore.Remove(separatorPosition); } } string sReturnValue = sSign + sBefore; if (sAfter == "") { if (alwaysShowDecimalSeparator) { sReturnValue += DecimalSeparator + "0"; } } else { sReturnValue += DecimalSeparator + sAfter; } return sReturnValue; } } } 

Math.Round () sur les doubles est imparfait (voir Notes aux appelants dans sa documentation ). L’étape ultérieure consistant à multiplier le nombre arrondi par son exposant décimal introduira d’autres erreurs à virgule flottante dans les chiffres de fin. Utiliser un autre Round () comme @Rowanto n’aide pas de manière fiable et souffre d’autres problèmes. Cependant, si vous êtes prêt à passer par décimal, Math.Round () est fiable, tout comme multiplier et diviser par des puissances de 10:

 static ClassName() { powersOf10 = new decimal[28 + 1 + 28]; powersOf10[28] = 1; decimal pup = 1, pdown = 1; for (int i = 1; i < 29; i++) { pup *= 10; powersOf10[i + 28] = pup; pdown /= 10; powersOf10[28 - i] = pdown; } } /// Powers of 10 indexed by power+28. These are all the powers /// of 10 that can be represented using decimal. static decimal[] powersOf10; static double RoundToSignificantDigits(double v, int digits) { if (v == 0.0 || Double.IsNaN(v) || Double.IsInfinity(v)) { return v; } else { int decimal_exponent = (int)Math.Floor(Math.Log10(Math.Abs(v))) + 1; if (decimal_exponent < -28 + digits || decimal_exponent > 28 - digits) { // Decimals won't help outside their range of representation. // Insert flawed Double solutions here if you like. return v; } else { decimal d = (decimal)v; decimal scale = powersOf10[decimal_exponent + 28]; return (double)(scale * Math.Round(d / scale, digits, MidpointRounding.AwayFromZero)); } } } 

Cette question est similaire à celle que vous posez:

Formatage des nombres avec des chiffres significatifs en C #

Ainsi, vous pouvez faire ce qui suit:

 double Input2 = 234.004223; ssortingng Result2 = Math.Floor(Input2) + Convert.ToDouble(Ssortingng.Format("{0:G1}", Input2 - Math.Floor(Input2))).ToSsortingng("R6"); 

Arrondi à 1 chiffre significatif.

Laissez inputNumber être l’entrée qui doit être convertie avec significantDigitsRequired après le point décimal, alors significantDigitsResult est la réponse au pseudo-code suivant.

 integerPortion = Math.truncate(**inputNumber**) decimalPortion = myNumber-IntegerPortion if( decimalPortion <> 0 ) { significantDigitsStartFrom = Math.Ceil(-log10(decimalPortion)) scaleRequiredForTruncation= Math.Pow(10,significantDigitsStartFrom-1+**significantDigitsRequired**) **siginficantDigitsResult** = integerPortion + ( Math.Truncate (decimalPortion*scaleRequiredForTruncation))/scaleRequiredForTruncation } else { **siginficantDigitsResult** = integerPortion } 

Voici quelque chose que j’ai fait en C ++

 /* I had this same problem I was writing a design sheet and the standard values were rounded. So not to give my values an advantage in a later comparison I need the number rounded, so I wrote this bit of code. It will round any double to a given number of significant figures. But I have a limited range written into the subroutine. This is to save time as my numbers were not very large or very small. But you can easily change that to the full double range, but it will take more time. Ross Mckinstray rmckinstray01@gmail.com */ #include  #include  #include  #include  #include  #include  #using namespace std; double round_off(double input, int places) { double roundA; double range = pow(10, 10); // This limits the range of the rounder to 10/10^10 - 10*10^10 if you want more change range; for (double j = 10/range; j< 10*range;) { if (input >= j && input < j*10){ double figures = pow(10, places)/10; roundA = roundf(input/(j/figures))*(j/figures); } j = j*10; } cout << "\n in sub after loop"; if (input <= 10/(10*10) && input >= 10*10) { roundA = input; cout << "\nDID NOT ROUND change range"; } return roundA; } int main() { double number, sig_fig; do { cout << "\nEnter number "; cin >> number; cout << "\nEnter sig_fig "; cin >> sig_fig; double output = round_off(number, sig_fig); cout << setprecision(10); cout << "\n I= " << number; cout << "\nr= " < 

J'espère que je n'ai rien changé en le formatant.

Je viens de faire:

 int integer1 = Math.Round(double you want to round, significant figures you want to round to)