Redondear un doble a x figuras significativas

Si tengo un doble (234.004223), etc., me gustaría redondear esto a x dígitos significativos en C #.

Hasta ahora solo puedo encontrar formas de redondear a x decimales, pero esto simplemente elimina la precisión si hay 0 en el número.

Por ejemplo, 0.086 a un lugar decimal se convierte en 0.1, pero me gustaría que permanezca en 0.08.

El marco no tiene una función incorporada para redondear (o truncar, como en su ejemplo) a una cantidad de dígitos significativos. Sin embargo, una manera de hacerlo es escalar su número de modo que su primer dígito significativo esté justo después del punto decimal, redondee (o trunque), luego reduzca la escala. El siguiente código debería hacer el truco:

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, como en su ejemplo, realmente quiere truncar, quiere:

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

He estado usando la función sigfig de pDaddy durante unos meses y he encontrado un error. No puede tomar el registro de un número negativo, por lo que si d es negativo, el resultado es NaN.

Lo siguiente corrige el error:

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

Me parece que no desea redondear a x decimales en absoluto; desea redondear a x dígitos significativos. Entonces, en su ejemplo, quiere redondear 0.086 a un dígito significativo, no a un decimal.

Ahora, usar un doble y redondear a una cantidad de dígitos significantes es problemático para empezar, debido a la forma en que se almacenan los dobles. Por ejemplo, podrías redondear 0.12 a algo cercano a 0.1, pero 0.1 no es exactamente representable como un doble. ¿Estás seguro de que no deberías estar usando un decimal? Alternativamente, ¿esto es realmente para fines de visualización? Si es para fines de visualización, sospecho que debes convertir el doble directamente a una cadena con el número relevante de dígitos significativos.

Si puede responder esos puntos, puedo tratar de encontrar un código apropiado. Aunque suene horrible, convertir una cantidad de dígitos significativos en una cadena convirtiendo el número en una cadena “completa” y luego encontrar el primer dígito significativo (y luego tomar la acción de redondeo adecuada después de eso) bien puede ser la mejor manera de hacerlo. .

Si es para fines de visualización (como lo indica en el comentario a la respuesta de Jon Skeet), debe usar el especificador de formato Gn. Donde n es la cantidad de dígitos significativos, exactamente lo que busca.

Aquí está el ejemplo de uso si quiere 3 dígitos significativos (la salida impresa está en el comentario de cada línea):

  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 

Encontré dos errores en los métodos de P Daddy y Eric. Esto resuelve, por ejemplo, el error de precisión presentado por Andrew Hancox en esta sesión de preguntas y respuestas. También hubo un problema con las instrucciones circulares. 1050 con dos cifras significativas no es 1000.0, es 1100.0. El redondeo se corrigió 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); } } } 

Como lo menciona Jon Skeet: maneja mejor esto en el dominio textual. Como regla: para fines de visualización, no intente redondear / cambiar sus valores de coma flotante, nunca funciona del todo al 100%. La visualización es una preocupación secundaria y debe manejar cualquier requisito especial de formato como estos que funcionan con cadenas.

Mi solución a continuación la implementé hace varios años y ha demostrado ser muy confiable. Ha sido probado exhaustivamente y también funciona bastante bien. Aproximadamente 5 veces más tiempo de ejecución que la solución de P Daddy / Eric.

Ejemplos de entrada + salida dada a continuación en el 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 () en dobles tiene fallas (ver Notas para personas que llaman en su documentación ). El paso posterior de multiplicar el número redondeado por su exponente decimal introducirá más errores de punto flotante en los dígitos finales. Usar otra ronda () como @Rowanto does no ayudará confiablemente y sufrirá otros problemas. Sin embargo, si está dispuesto a ir vía decimal, entonces Math.Round () es confiable, ya que se está multiplicando y dividiendo por potencias 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 pregunta es similar a la que está preguntando:

Formatear números con cifras significativas en C #

Por lo tanto, podrías hacer lo siguiente:

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

Redondeado a 1 dígito significativo.

inputNumber que se debe convertir con significantDigitsRequired después del punto decimal, entonces significantDigitsResult es la respuesta al siguiente pseudo código.

 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 } 

Aquí hay algo que hice 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= " < 

Espero no haber cambiado nada formateándolo.

Lo acabo de hacer:

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