Arrotondare un doppio a x cifre significative

Se ho un doppio (234.004223), ecc., Vorrei arrotondarlo a x cifre significative in C #.

Finora posso trovare solo modi per arrotondare a x posizioni decimali, ma questo semplicemente rimuove la precisione se ci sono 0 nel numero.

Ad esempio, da 0,086 a un decimale diventa 0,1, ma mi piacerebbe che rimanesse a 0,08.

Il framework non ha una funzione built-in per arrotondare (o troncare, come nel tuo esempio) un numero di cifre significative. Un modo per farlo, tuttavia, è ridimensionare il numero in modo che la prima cifra significativa sia giusta dopo il punto decimale, arrotondata (o troncata), quindi ridimensionata. Il seguente codice dovrebbe fare il trucco:

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

Se, come nel tuo esempio, vuoi davvero troncare, allora vuoi:

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

Sto usando la funzione sigfig di pDaddy per alcuni mesi e ho trovato un bug in esso. Non puoi prendere il Log di un numero negativo, quindi se d è negativo il risultato è NaN.

Il seguente corregge il bug:

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

Mi sembra che tu non voglia affatto arrotondare a x i decimali – vuoi arrotondare a x cifre significative. Quindi, nel tuo esempio, vuoi arrotondare 0,086 a una cifra significativa, non a una cifra decimale.

Ora, usare un doppio e arrotondare a un numero di cifre significative è problematico per iniziare, a causa del modo in cui vengono memorizzati i doppi. Ad esempio, è ansible arrotondare 0,12 a qualcosa vicino a 0,1, ma 0,1 non è esattamente rappresentabile come un doppio. Sei sicuro di non dover utilizzare un decimale? In alternativa, questo è effettivamente per scopi di visualizzazione? Se è a scopo di visualizzazione, sospetto che dovresti effettivamente convertire il doppio direttamente in una stringa con il numero rilevante di cifre significative.

Se puoi rispondere a questi punti, posso provare a trovare un codice appropriato. Per quanto possa sembrare orribile, la conversione in un numero di cifre significative come una stringa convertendo il numero in una stringa “completa” e quindi trovando la prima cifra significativa (e poi prendendo l’appropriata azione di arrotondamento dopo quella) potrebbe essere il modo migliore per andare .

Se è a scopo di visualizzazione (come dichiari nel commento alla risposta di Jon Skeet), dovresti usare lo specificatore di formato Gn. Dove n è il numero di cifre significative – esattamente quello che stai cercando.

Ecco l’esempio di utilizzo se desideri 3 cifre significative (l’output stampato è nel commento di ogni riga):

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

Ho trovato due bug nei metodi di P Daddy e Eric. Questo risolve ad esempio l’errore di precisione che è stato presentato da Andrew Hancox in questo Q & A. C’era anche un problema con le direzioni rotonde. 1050 con due cifre significative non è 1000.0, è 1100.0. L’arrotondamento è stato risolto con MidpointRounding.AwayFromZero.

 static void Main(string[] 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); } } } 

Come afferma Jon Skeet: meglio gestirlo nel dominio testuale. Di regola: per scopi di visualizzazione, non provare a arrotondare / modificare i valori in virgola mobile, non funziona mai del tutto al 100%. La visualizzazione è una preoccupazione secondaria e dovresti gestire tutti i requisiti di formattazione speciali come questi che funzionano con le stringhe.

La mia soluzione di seguito ho implementato diversi anni fa e si è dimostrata molto affidabile. È stato testato a fondo e funziona anche molto bene. Circa 5 volte più lungo nei tempi di esecuzione rispetto alla soluzione di P Daddy / Eric.

Esempi di input + output indicati di seguito nel codice.

 using System; using System.Text; namespace KZ.SigDig { public static class SignificantDigits { public static string 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 string Format(double number, int digits, bool showTrailingZeros = true, bool alwaysShowDecimalSeparator = false) { if (Double.IsNaN(number) || Double.IsInfinity(number)) { return number.ToString(); } string sSign = ""; string sBefore = "0"; // Before the decimal separator string 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.ToString(); 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 () sui doppi è difettoso (vedi Note ai chiamanti nella sua documentazione ). Il passo successivo di moltiplicare il numero arrotondato per il suo esponente decimale introdurrà ulteriori errori in virgola mobile nelle cifre finali. Usando un altro Round () come @Rowanto non aiuterà in modo affidabile e soffre di altri problemi. Tuttavia se sei disposto ad andare via decimale, allora Math.Round () è affidabile, come si moltiplica e divide per poteri di 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)); } } } 

Questa domanda è simile a quella che stai chiedendo:

Formattazione di numeri con cifre significative in C #

Quindi potresti fare quanto segue:

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

Arrotondato a 1 cifra significativa.

Sia inputNumber essere input che deve essere convertito con significantDigitsRequired dopo il punto decimale, quindi significantDigitsResult è la risposta al seguente pseudo-codice.

 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 } 

Ecco qualcosa che ho fatto in 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 [email protected] */ #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= " < 

Spero di non aver cambiato nulla formattandolo.

Ho appena fatto:

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