Question

Si j'ai un double (234,004223), etc., je voudrais compléter ce à x chiffres significatifs en C #.

Jusqu'à présent, je ne peux trouver des moyens pour arrondir x décimales, mais cela supprime simplement la précision s'il y a des 0s du nombre.

Par exemple, 0,086 à une décimale devient 0.1, mais je voudrais qu'il reste à 0,08.

Était-ce utile?

La solution

Le cadre ne dispose pas d'une fonction intégrée pour arrondir (ou tronquer, comme dans votre exemple) à un certain nombre de chiffres significatifs. Une façon vous pouvez faire cela, cependant, est à l'échelle de votre numéro afin que votre premier chiffre significatif est juste après la virgule, rond (ou tronquer), puis ramenons. Le code suivant devrait faire l'affaire:

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, comme dans votre exemple, vous voulez vraiment tronquer, alors vous voulez:

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

Autres conseils

Je suis en utilisant la fonction de SigFig pDaddy pendant quelques mois et a trouvé un bogue. Vous ne pouvez pas prendre le journal d'un nombre négatif, donc si d est négatif des résultats est NaN.

Ce qui suit corrige le 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));
}

Il me semble que vous ne voulez pas arrondir à des endroits x décimaux du tout - vous voulez arrondir à x chiffres significatifs. Donc, dans votre exemple, vous voulez arrondir 0,086 à un chiffre significatif, pas une décimale.

Maintenant, en utilisant un double et arrondi à un certain nombre de chiffres significatifs est problématique pour commencer, en raison de la double voie sont stockés. Par exemple, vous pouvez arrondir 0,12 à quelque chose Fermer à 0,1, mais 0,1 est pas exactement représentable comme un double. Etes-vous sûr vous ne devriez pas réellement utiliser une décimale? Sinon, est-ce réellement à des fins d'affichage? Si elle est à des fins d'affichage, je pense que vous devriez effectivement convertir la double directement à une chaîne avec le nombre approprié de chiffres significatifs.

Si vous pouvez répondre à ces points, je peux essayer de trouver un code approprié. Awful que cela puisse paraître, la conversion à un certain nombre de chiffres significatifs sous forme de chaîne en convertissant le nombre à une chaîne « plein », puis trouver le premier chiffre significatif (puis prendre des mesures d'arrondi approprié après) pourrait bien être la meilleure façon d'aller .

Si elle est à des fins d'affichage (comme vous l'avez dans le commentaire à la réponse de Jon Skeet), vous devez utiliser Gn Format spécificateur . Où n est le nombre de chiffres significatifs - exactement ce que vous recherchez.

Voici le l'exemple d'utilisation si vous souhaitez 3 chiffres significatifs (sortie imprimée est dans le commentaire de chaque ligne):

    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

J'ai trouvé deux bugs dans les méthodes de P Papa et Eric. Cela résout par exemple l'erreur de précision qui a été présenté par Andrew Hancox dans ce Q & A. Il y avait aussi un problème avec les directions autour. 1050 avec deux chiffres significatifs n'est pas 1000.0, il est 1100,0. L'arrondi a été fixé avec 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);
    }
  }
}

Comme Jon Skeet mentionne: mieux gérer cela dans le domaine textuel. En règle générale: à des fins d'affichage, ne pas essayer d'arrondir / modifier vos valeurs à virgule flottante, ça ne marche jamais tout à fait à 100%. L'affichage est une préoccupation secondaire et vous devez gérer toutes les exigences de formatage spéciales comme celles-ci travaillant avec des chaînes.

Ma solution ci-dessous j'ai mis il y a plusieurs années et a prouvé très fiable. Il a été soigneusement testé et il exécute très bien aussi. Environ 5 fois plus dans le temps d'exécution que la solution de P Papa / Eric.

Des exemples d'entrée + sortie donnés ci-dessous dans le code.

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 () sur des doubles est défectueux (voir Notes aux personnes qui appellent dans son documentation ). L'étape ultérieure de multiplier le nombre arrondi vers le haut par son exposant décimal présentera d'autres erreurs à virgule flottante dans les chiffres de fin. En utilisant une autre série () comme @Rowanto ne va pas aider de manière fiable et souffre d'autres problèmes. Toutefois, si vous êtes prêt à passer par décimale alors Math.Round () est fiable, comme la multiplication et la division par des puissances 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));
        }
    }
}

Cette question est similaire à celui que vous vous demandez:

Mise en forme des nombres avec des chiffres significatifs en C #

Ainsi, vous pouvez faire ce qui suit:

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

Arrondi à 1 chiffre significatif.

Soit inputNumber être entrée qui doit être converti avec significantDigitsRequired après la virgule, puis significantDigitsResult est la réponse au pseudo-code suivant.

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

}

Je suis d'accord avec l'esprit de évaluation de Jon :

  

Awful que cela puisse paraître, la conversion à un certain nombre de chiffres significatifs sous forme de chaîne en convertissant le nombre à une chaîne « plein », puis trouver le premier chiffre significatif (puis prendre des mesures d'arrondi approprié après) pourrait bien être le meilleur chemin à parcourir.

Je avais besoin important chiffres arrondis à des fins approximatives et non-exécution critiques de calcul, et le format-parse aller-retour à travers le format "G" est assez bon :

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

Je viens de le faire:

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

est quelque chose que je fait ici 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;
}

Si tout va bien je ne change rien formater.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top