Domanda

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

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

Ad esempio, 0,086 con una cifra decimale diventa 0,1, ma vorrei che rimanesse a 0,08.

È stato utile?

Soluzione

Il quadro non dispone di una funzione incorporata per arrotondare (o troncamento, come nel tuo esempio) per un numero di cifre significative. Un modo per fare questo, però, è quello di scalare il numero in modo che la vostra prima cifra significativa è subito dopo la virgola, rotondo (o troncamento), quindi ridimensionare. 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, si vuole veramente troncare, poi si desidera:

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

Altri suggerimenti

Sono stato utilizzando la funzione di sigfig pDaddy per qualche mese e ho trovato un bug in esso. Non si può prendere il registro di un numero negativo, quindi se d è negativo il risultato è NaN.

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

Sembra a me come se non si vuole arrotondare a luoghi x decimali a tutti - si vuole arrotondare per x cifre significative. Quindi nel tuo esempio, si desidera arrotondare 0,086 per una cifra significativa, non un decimale.

Ora, usando una doppia e arrotondamento a un certo numero di cifre significative è problematico per cominciare, a causa del modo in cui i doppi sono memorizzati. Per esempio, si potrebbe arrotondare 0,12 a qualcosa di close a 0,1, ma 0,1 non è esattamente rappresentabile come un doppio. Sei sicuro non si dovrebbe in realtà si stia utilizzando un decimale? In alternativa, è questo in realtà per scopi di visualizzazione? Se è per scopi di visualizzazione, ho il sospetto che si dovrebbe effettivamente convertire il doppio direttamente a una stringa con il relativo numero di cifre significative.

Se si riesce a rispondere su quei punti, posso cercare di trovare un qualche codice appropriato. Terribile come sembra, la conversione in un numero di cifre significative come una stringa convertendo il numero in una stringa "pieno" e poi trovare la prima cifra significativa (e poi prendendo le opportune azioni di arrotondamento dopo che) potrebbe essere il modo migliore per andare .

Se è per scopi di visualizzazione (come affermate nel commento alla risposta di Jon Skeet), è necessario utilizzare Gn di formato . Dove n è il numero di cifre significative - esattamente che cosa siete dopo.

Ecco l'esempio di utilizzo se si vuole 3 cifre significative (stampa è 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 insetti nei metodi di P papà 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 indicazioni Round. 1050 con due cifre significative non è 1000.0, è 1100.0. L'arrotondamento è stato fissato 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 cita Jon Skeet: meglio gestire questo nel dominio testuale. Come regola generale: per scopi di visualizzazione, non cercare di arrotondare / modificare i valori in virgola mobile, funziona mai del tutto al 100%. Display è una preoccupazione secondaria e si deve gestire esigenze particolari di formattazione come questi a lavorare con le stringhe.

La mia soluzione qui di seguito ho realizzato diversi anni fa e si è dimostrato molto affidabile. E 'stato accuratamente testato e si comporta molto bene anche. Circa 5 volte di più in tempo di esecuzione di soluzione P papà / di Eric.

Esempi di ingresso + uscita 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;
        }

        /// <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 () sulle doppie è difettoso (vedi note a chiamanti nella sua documentazione ). La fase successiva del moltiplicando il numero bombato dal suo esponente decimale introdurrà errori virgola ulteriormente galleggiante in cifre trascinamento. Utilizzando un altro giro () come @Rowanto fa non affidabile aiuto e soffre di altri problemi. Tuttavia, se siete disposti ad andare via decimale poi Math.round () è affidabile, come è moltiplicando e dividendo per potenze 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;
    }
}

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

Questa domanda è simile a quella che stai ponendo:

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 sia in ingresso che deve essere convertito con significantDigitsRequired dopo il punto decimale, quindi significantDigitsResult è la risposta alla 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

}

Sono d'accordo con lo spirito di di Jon valutazione :

  

terribile come sembra, la conversione in un numero di cifre significative come una stringa convertendo il numero in una stringa "pieno" e poi trovare la prima cifra significativa (e poi prendendo le opportune azioni di arrotondamento dopo che) può ben essere il migliore strada da percorrere.

mi serviva significativo cifre arrotondamento per approssimative e scopi inadempimento-critici di calcolo, e il formato-parse di andata e ritorno attraverso la "G" formato è abbastanza buono :

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

Ho appena fatto:

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

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

Speriamo che non ho cambiato niente formattazione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top