Arredondar um duplo para x números significativos

Se eu tiver um duplo (234,004223), etc, gostaria de arredondar isso para x dígitos significativos em c #.

Até agora eu só posso encontrar maneiras de arredondar x casas decimais, mas isso simplesmente remove a precisão se houver algum 0 no número.

Por exemplo, 0.086 para uma casa decimal se torna 0.1, mas eu gostaria que ele ficasse em 0.08.

O framework não tem uma function interna para arredondar (ou truncar, como no seu exemplo) para um número de dígitos significativos. Uma maneira de fazer isso, no entanto, é dimensionar seu número para que seu primeiro dígito significativo seja logo após o ponto decimal, arredondar (ou truncar) e, em seguida, resize. O código a seguir deve fazer o truque:

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, como no seu exemplo, você realmente quiser truncar, então você quer:

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

Eu tenho usado a function sigfig do pDaddy por alguns meses e descobri um bug nela. Você não pode pegar o log de um número negativo, então se d é negativo, o resultado é NaN.

O seguinte corrige o erro:

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

Parece-me que você não quer arredondar para x casas decimais – você quer arredondar para x dígitos significativos. Portanto, no seu exemplo, você deseja arredondar 0,086 para um dígito significativo, não uma casa decimal.

Agora, usar um double e um arredondamento para um número de dígitos significativos é problemático para começar, devido à forma como as duplas são armazenadas. Por exemplo, você poderia arredondar 0,12 para algo próximo a 0,1, mas 0,1 não é exatamente representável como um duplo. Tem certeza de que você não deveria estar usando um decimal? Alternativamente, isso é realmente para fins de exibição? Se for para fins de exibição, suspeito que você deve converter o double diretamente em uma string com o número relevante de dígitos significativos.

Se você puder responder a esses pontos, posso tentar criar algum código apropriado. Por incrível que pareça, converter um número de dígitos significativos como uma string convertendo o número em uma string “cheia” e depois encontrar o primeiro dígito significativo (e depois tomar a ação de arredondamento apropriada depois disso) pode ser o melhor caminho a seguir .

Se é para fins de exibição (como você afirma no comentário à resposta de Jon Skeet), você deve usar o especificador de formato Gn. Onde n é o número de dígitos significativos – exatamente o que você está procurando.

Aqui está o exemplo de uso se você quiser 3 dígitos significativos (a saída impressa está no comentário de cada linha):

  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 

Eu encontrei dois erros nos methods de P Daddy e Eric. Isso resolve, por exemplo, o erro de precisão apresentado por Andrew Hancox nesta session de perguntas e respostas. Houve também um problema com direções redondas. 1050 com dois números significativos não é 1000.0, é 1100.0. O arredondamento foi corrigido com 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); } } } 

Como Jon Skeet menciona: melhor lidar com isso no domínio textual. Como regra: para fins de exibição, não tente arredondar / alterar seus valores de ponto flutuante, ele nunca funciona 100%. A exibição é uma preocupação secundária e você deve manipular quaisquer requisitos especiais de formatação, como esses que funcionam com strings.

Minha solução abaixo eu implementei há vários anos e provou ser muito confiável. Foi completamente testado e tem um bom desempenho também. Cerca de 5 vezes mais tempo de execução do que a solução de P Daddy / Eric.

Exemplos de input + saída dada abaixo no código.

 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 () em duplas é falho (veja Notas para os chamadores em sua documentação ). A etapa posterior de multiplicar o número arredondado pelo seu expoente decimal introduzirá mais erros de ponto flutuante nos dígitos finais. Usar outra Rodada () como o @Rowanto não ajuda de forma confiável e sofre com outros problemas. No entanto, se você estiver disposto a usar o método decimal, Math.Round () é confiável, assim como multiplicar e dividir por potências 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)); } } } 

Esta pergunta é semelhante àquela que você está perguntando:

Formatando números com números significativos em c #

Assim você poderia fazer o seguinte:

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

Arredondado para 1 dígito significativo.

Seja inputNumber input que precisa ser convertida com significantDigitsRequired após o ponto decimal, então, significantDigitsResult é a resposta para o pseudo código a seguir.

 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 } 

Aqui está algo que fiz em 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= " < 

Espero não ter mudado nada para formatá-lo.

Eu apenas fiz:

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