Pregunta

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

Hasta el momento sólo puedo encontrar la manera de ida y vuelta a lugares decimales x, pero esto simplemente elimina la precisión si hay algún 0s en el número.

Por ejemplo, 0,086 a una cifra decimal se convierte en 0,1, pero me gustaría que se quede en 0,08.

¿Fue útil?

Solución

El marco no tiene una función incorporada para redondear (o truncar, como en el ejemplo) a un número de dígitos significativos. Una manera de hacer esto, sin embargo, es a escala en número de manera que su primer dígito significativo es justo después de la coma decimal, redondo (o truncar), y luego retroceden. El siguiente código debe 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 el ejemplo, que realmente desea truncar, entonces usted 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);
}

Otros consejos

He estado usando la función de SigFig pDaddy durante unos meses y encontrado un fallo en el mismo. No se puede tomar el logaritmo de un número negativo, por lo que si d es negativo el resultado es NaN.

A continuación se 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 suena como que no desea redondear a lugares decimales x en absoluto - que desea redondear a x dígitos significativos. Así que en su ejemplo, que desea redondear 0,086 a un dígito significativo, no un decimal.

Ahora, utilizando un doble y el redondeo a un número de dígitos significativos es problemático para empezar, debido a la forma de dobles se almacenan. Por ejemplo, usted podría redondear 0,12 a algo cerrar a 0,1, pero no es exactamente 0,1 representable como un doble. ¿Seguro que no debe en realidad a utilizar un número decimal? Por otra parte, es en realidad esto con fines de exhibición? Si es para fines de visualización, sospecho que en realidad se debe convertir el doble directamente a una cadena con el número correspondiente de dígitos significativos.

Si usted puede responder a los puntos, puedo tratar de llegar a algún código apropiado. Horrible como suena, la conversión a un número de dígitos significativos como una cadena mediante la conversión del número en una cadena "completa" y luego encontrar el primer dígito significativo (y luego tomar acción de redondeo apropiado después de eso) bien puede ser el mejor camino a seguir .

Si es para fines de visualización (como se afirma en el comentario a la respuesta de Jon Skeet), se debe utilizar Gn especificador de formato . Donde n es el número de dígitos significativos - exactamente lo que está después.

Aquí está el ejemplo de uso si desea 3 dígitos significativos (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 papá y Eric. Esto resuelve por ejemplo, el error de precisión que fue presentada por Andrew Hancox en este Q & A. También hubo un problema con las direcciones redondas. 1050 con dos cifras significativas no es 1000.0, es 1100.0. El redondeo se fijó 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 se menciona Jon Skeet: mejor manejo de esto en el dominio textual. Como regla: para fines de presentación, no tratar de redondear / cambiar sus valores de punto flotante, que nunca funciona al 100%. Display es una preocupación secundaria y se debe manejar cualquier requisito especial de formato como estos trabajar con cadenas.

Mi solución a continuación he implementado hace varios años y ha demostrado ser muy fiable. Se ha probado a fondo y se lleva a cabo bastante bien también. Aproximadamente 5 veces más largas en el tiempo de ejecución de la solución P papá / de Eric.

Ejemplos de entrada + de salida dan 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;
        }

        /// <summary>
        /// Format a double to a given number of significant digits.
        /// </summary>
        /// <example>
        /// 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)
        /// </example>
        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 se imperfectos (ver Notas para los llamadores en su documentación). El paso posterior de multiplicar el número redondeado de nuevo por su exponente decimal introducirá errores de punto más que flotan en los dígitos de cola. El uso de otro Ronda () como @Rowanto hace no ayudará de manera fiable y sufre de otros problemas. Sin embargo, si usted está dispuesto a ir a través de decimal a continuación Math.Round () es fiable, al igual que la multiplicación y división 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;
    }
}

/// <summary>Powers of 10 indexed by power+28.  These are all the powers
/// of 10 that can be represented using decimal.</summary>
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 usted está pidiendo:

números

formato con cifras significativas en C #

De este modo se podría 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.

Let inputNumber sea de entrada que necesita ser convertida con significantDigitsRequired después del punto decimal, entonces significantDigitsResult es la respuesta a la 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

}

Estoy de acuerdo con el espíritu de evaluación de Jon:

  

horrible como suena, la conversión a un número de dígitos significativos como una cadena mediante la conversión del número en una cadena "completa" y luego encontrar el primer dígito significativo (y luego tomar acción de redondeo apropiado después de eso) puede muy bien ser el mejor camino a seguir.

que necesitaba significativa dígitos redondeo de y fines de cálculo incumplimiento críticos aproximados, y el formato de análisis sintáctico de ida y vuelta a través de "G" formato es lo suficientemente bueno :

public static double RoundToSignificantDigits(this double value, int numberOfSignificantDigits)
{
    return double.Parse(value.ToString("G" + numberOfSignificantDigits));
}

Me acabo de hacer:

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

Aquí es 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 <iostream>
#include <fstream>
#include <string>
#include <math.h>
#include <cmath>
#include <iomanip>

#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 << "\n r= " <<output;
        cout << "\nEnter 0 as number to exit loop";
    }
    while (number != 0);

    return 0;
}

Con suerte no cambiaba nada formatearlo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top