Frage

Wenn ich ein Double (234.004223) usw. habe, möchte ich dies in C# auf x signifikante Ziffern runden.

Bisher kann ich nur auf x Dezimalstellen runden, aber dadurch wird einfach die Genauigkeit entfernt, wenn die Zahl Nullen enthält.

Beispielsweise wird 0,086 auf eine Dezimalstelle zu 0,1, aber ich möchte, dass der Wert bei 0,08 bleibt.

War es hilfreich?

Lösung

Der Rahmen hat keine eingebaute Funktion abzurunden (oder gestutzt, wie in Ihrem Beispiel) auf eine Reihe von signifikanten Stellen. Eine Möglichkeit, dies zu tun, obwohl, ist Ihre Nummer zu skalieren, so dass Ihre erste signifikante Stelle befindet sich direkt hinter dem Komma, rund (oder gestutzt), dann zurück skalieren. Der folgende Code sollte es tun:

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

Wenn, wie in Ihrem Beispiel, die Sie wirklich abschneiden wollen, dann sind Sie wollen:

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

Andere Tipps

Ich bin pDaddy der sigfig Funktion für ein paar Monate verwenden und einen Fehler darin gefunden. Sie können die Log einer negativen Zahl nehmen, so dass, wenn d negativ ist das Ergebnis NaN.

In der folgenden korrigiert den Fehler:

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

Für mich hört es sich so an, als ob Sie überhaupt nicht auf x Dezimalstellen runden möchten, sondern auf x signifikante Stellen.In Ihrem Beispiel möchten Sie also 0,086 auf eine signifikante Ziffer und nicht auf eine Dezimalstelle runden.

Nun ist die Verwendung eines Doubles und das Runden auf mehrere signifikante Ziffern aufgrund der Art und Weise, wie Doubles gespeichert werden, zunächst problematisch.Sie könnten beispielsweise 0,12 auf etwas runden schließen auf 0,1, aber 0,1 ist nicht genau als Doppel darstellbar.Sind Sie sicher, dass Sie eigentlich keine Dezimalzahl verwenden sollten?Ist dies alternativ tatsächlich zu Anzeigezwecken gedacht?Wenn es zu Anzeigezwecken dient, sollten Sie das Double vermutlich direkt in eine Zeichenfolge mit der entsprechenden Anzahl signifikanter Ziffern umwandeln.

Wenn Sie diese Punkte beantworten können, kann ich versuchen, einen geeigneten Code zu finden.So schrecklich es auch klingen mag, die Konvertierung in eine Reihe signifikanter Ziffern als Zeichenfolge, indem man die Zahl in eine „vollständige“ Zeichenfolge umwandelt und dann die erste signifikante Ziffer findet (und danach die entsprechenden Rundungsmaßnahmen durchführt), ist möglicherweise der beste Weg .

Wenn es für die Anzeige (wie Sie in dem Kommentar zu Jon Skeet Antwort angeben), sollten Sie Gn Formatspezifizierer . Wo n ist die Anzahl der signifikanten Stellen - genau das, was Sie nach.

Hier ist das das Beispiel der Verwendung, wenn Sie 3 signifikante Stellen wollen (gedruckte Ausgabe ist in dem Kommentar jeder Zeile):

    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

fand ich zwei Fehler in den Methoden der P Papa und Eric. Dies löst zum Beispiel die Präzision Fehler, der von Andrew Hancox in diesem Q & A präsentiert wurde. Es gab auch ein Problem mit runden Richtungen. 1050 mit zwei signifikanten Ziffern ist nicht 1000.0, es ist 1100.0. Die Rundung wurde mit MidpointRounding.AwayFromZero festgelegt.

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

Wie Jon Skeet erwähnt: besser handhaben dies in der textuellen Domäne. In der Regel: für Zwecke Anzeige, versuchen Sie nicht, Ihre Gleitkommawerte abzurunden / ändern, es funktioniert nie ganz 100%. Anzeige ist zweitrangig, und Sie sollten keine spezielle Formatierung Anforderungen handhaben wie diese Arbeit mit Strings.

Meine Lösung unter ich vor einigen Jahren und hat sich als sehr zuverlässig umgesetzt. Es wurde ausgiebig getestet und es führt durchaus auch gut. Etwa 5 mal länger in der Ausführungszeit als P Daddy / Eric-Lösung.

Beispiele für Eingabe + Ausgang nachstehend 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 () auf Doppel fehlerhaft ist (Hinweise zu Aufrufern in seiner Dokumentation ). Der spätere Schritt der gerundete Zahl wieder nach oben durch den dezimalen Exponenten Multiplikation wird einzuführen weitere Gleitkommafehler in den abfallenden Ziffern. Unter Verwendung eines anderen Round () als @Rowanto tut nicht zuverlässig helfen und leidet unter anderen Problemen. Allerdings, wenn Sie bereit sind, über dezimal geht dann Math.Round () ist zuverlässig, wie Multiplikation und Division durch Potenzen von 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));
        }
    }
}

Diese Frage ist ähnlich zu dem einen Du fragst:

Formatieren von Zahlen mit signifikanten Zahlen in C #

So können Sie wie folgt vor:

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

gerundet auf 1 signifikante Stelle.

Lassen Sie inputNumber eingegeben wird, die mit significantDigitsRequired Nachkommastelle umgewandelt werden muss, dann significantDigitsResult ist die Antwort auf den folgenden Pseudo-Code.

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

}

Ich stimme dem Geist von zu Jons Einschätzung:

So schrecklich es auch klingen mag, die Konvertierung in eine Reihe signifikanter Ziffern als Zeichenfolge, indem man die Zahl in eine „vollständige“ Zeichenfolge umwandelt und dann die erste signifikante Ziffer findet (und danach die entsprechenden Rundungsmaßnahmen durchführt), ist möglicherweise der beste Weg .

Ich brauchte eine Rundung der signifikanten Ziffer für ungefähr Und nicht leistungskritisch Für Rechenzwecke ist der Format-Parse-Roundtrip durch das „G“-Format gut genug:

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

Ich habe gerade:

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

Hier ist etwas, was ich in C ++ tat

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

Hoffentlich habe ich nichts ändern es zu formatieren.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top