Najlepszy sposób wyświetlania liczb dziesiętnych bez końcowych zer


108

Czy istnieje program formatujący ekran, który wyprowadza liczby dziesiętne jako te reprezentacje ciągów w języku C # bez wykonywania żadnego zaokrąglania?

// decimal -> string

20 -> 20
20.00 -> 20
20.5 -> 20.5
20.5000 -> 20.5
20.125 -> 20.125
20.12500 -> 20.125
0.000 -> 0

{0. #} zaokrągli, a użycie niektórych funkcji przycinania nie będzie działać z powiązaną kolumną numeryczną w siatce.

Odpowiedzi:


147

Czy masz maksymalną liczbę miejsc dziesiętnych, które kiedykolwiek będziesz musiał wyświetlić? (Twoje przykłady mają maksymalnie 5).

Jeśli tak, myślę, że formatowanie za pomocą „0. #####” zrobi to, co chcesz.

    static void Main(string[] args)
    {
        var dList = new decimal[] { 20, 20.00m, 20.5m, 20.5000m, 20.125m, 20.12500m, 0.000m };

        foreach (var d in dList)
            Console.WriteLine(d.ToString("0.#####"));
    }

4
I zawsze możesz rzucić wygórowaną * liczbę symboli # w formacie. * nienaukowe
Anthony Pegram

3
+1 - nawiasem mówiąc, nie potrzebujesz wiodącego #. „0. #####” działa identycznie. I tak czy inaczej, to zaokrągla od zera (tylko FYI).
TrueWill

14
@TrueWill Właściwie to robi różnicę. d == 0,1m, a następnie "0. #" renderuje "0.1" i "#. #" renderuje ".1" bez wiodącego 0. Żadne z nich nie jest złe ani prawidłowe, zależy tylko od tego, czego chcesz.
AaronLS

4
Musiałem zawsze wyświetlać jedno miejsce dziesiętne, więc użyłem ciągu formatu "0.0#".
Kody z Hammerem

1
Zobacz odpowiedź Erwina Mayera poniżej.
shlgug

32

Właśnie nauczyłem się, jak prawidłowo używać specyfikatora Gformatu. Zobacz dokumentację MSDN . Trochę dalej znajduje się uwaga, która stwierdza, że ​​końcowe zera zostaną zachowane dla typów dziesiętnych, gdy nie określono dokładności. Dlaczego mieliby to zrobić, nie wiem, ale określenie maksymalnej liczby cyfr dla naszej dokładności powinno rozwiązać ten problem. Więc G29najlepiej jest formatować ułamki dziesiętne .

decimal test = 20.5000m;
test.ToString("G"); // outputs 20.5000 like the documentation says it should
test.ToString("G29"); // outputs 20.5 which is exactly what we want

21
Doprowadzi to do 0,00001 wyświetlanego jako 1E-05, choć
sehe

17

Ten format ciągu powinien stanowić Twój dzień: „0. ############################”. Pamiętaj jednak, że cyfry dziesiętne mogą mieć maksymalnie 29 cyfr znaczących.

Przykłady:

? (1000000.00000000000050000000000m).ToString("0.#############################")
-> 1000000.0000000000005

? (1000000.00000000000050000000001m).ToString("0.#############################")
-> 1000000.0000000000005

? (1000000.0000000000005000000001m).ToString("0.#############################")
-> 1000000.0000000000005000000001

? (9223372036854775807.0000000001m).ToString("0.#############################")
-> 9223372036854775807

? (9223372036854775807.000000001m).ToString("0.#############################")
-> 9223372036854775807.000000001

4
To jest najbardziej ogólna odpowiedź na problem. Nie wiem, dlaczego nie jest to domyślny format „G” dla liczb dziesiętnych. Końcowe zera nie dodają żadnych informacji. Dokumentacja firmy Microsoft zawiera dziwne zastrzeżenie dotyczące liczby dziesiętnej: docs.microsoft.com/en-us/dotnet/standard/base-types/… Jeśli jednak liczba jest liczbą dziesiętną, a specyfikator precyzji jest pominięty, zawsze używana jest notacja ze stałym punktem a końcowe zera są zachowywane.
Eric Roller

10

To kolejna odmiana tego, co widziałem powyżej. W moim przypadku muszę zachować wszystkie znaczące cyfry po prawej stronie przecinka, co oznacza usunięcie wszystkich zer po najbardziej znaczącej cyfrze. Pomyślałem, że fajnie byłoby się nimi podzielić. Nie mogę jednak ręczyć za skuteczność tego, ale próbując osiągnąć estetykę, już jesteś skazany na nieefektywność.

public static string ToTrimmedString(this decimal target)
{
    string strValue = target.ToString(); //Get the stock string

    //If there is a decimal point present
    if (strValue.Contains("."))
    {
        //Remove all trailing zeros
        strValue = strValue.TrimEnd('0');

        //If all we are left with is a decimal point
        if (strValue.EndsWith(".")) //then remove it
            strValue = strValue.TrimEnd('.');
    }

    return strValue;
}

To wszystko, chciałem tylko dorzucić swoje dwa centy.


3
Jest to bez wątpienia najlepsze rozwiązanie, nie wybucha też na „0,0”. Dodałem domyślną flagę, aby liczby całkowite były opcjonalnie wyświetlane jako „1.0”, a to właśnie spowodowało, że wszystkie moje liczby dziesiętne znalazły się w wierszu. Nie mogę zrozumieć, dlaczego to nie jest najlepsza odpowiedź, + dużo.
Steve Hibbert

2
Jest to szczególnie dobre, jeśli ktoś już przekazał Ci ciąg znaków. Możesz po prostu zmienić to na ToTrimmedString (ten ciąg strValue) i usunąć pierwszą linię.
PRMan

Trafne spostrzeżenie. Myślę, że kiedy to pisałem, miałem do czynienia z wartościami, które były już ułamkami dziesiętnymi lub czymś w tym rodzaju, więc chciałem objąć całość. Zamiast tego możesz mieć treść tej metody wewnątrz innej przeciążonej metody, która zamiast tego akceptuje ciąg.
dyslexicanaboko

Jest to dobre rozwiązanie, gdy możesz wywołać ToTrimmedString (), ale istnieje wiele przypadków, w których jesteś ograniczony do ciągu formatu.
Mike Marynowski

1
Chociaż separator dziesiętny musi być specyficzny dla kultury, myślę, że jest to dobre rozwiązanie. Małe pytanie, dlaczego nie strValue.TrimEnd('0').TrimEnd('.')zamiast EndsWithczeku?
nawfal

5

Inne rozwiązanie, oparte na odpowiedzi dysleksikanaboko , ale niezależne od aktualnej kultury:

public static string ToTrimmedString(this decimal num)
{
    string str = num.ToString();
    string decimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
    if (str.Contains(decimalSeparator))
    {
        str = str.TrimEnd('0');
        if(str.EndsWith(decimalSeparator))
        {
            str = str.RemoveFromEnd(1);
        }
    }
    return str;
}

public static string RemoveFromEnd(this string str, int characterCount)
{
    return str.Remove(str.Length - characterCount, characterCount);
}

Wspaniały. Z wyjątkiem SqlDecimal jest to CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator pomimo ustawień regionalnych.
user2091150

4

Metoda przedłużenia:

public static class Extensions
{
    public static string TrimDouble(this string temp)
    {
        var value = temp.IndexOf('.') == -1 ? temp : temp.TrimEnd('.', '0');
        return value == string.Empty ? "0" : value;
    }
}

Przykładowy kod:

double[] dvalues = {20, 20.00, 20.5, 20.5000, 20.125, 20.125000, 0.000};
foreach (var value in dvalues)
    Console.WriteLine(string.Format("{0} --> {1}", value, value.ToString().TrimDouble()));

Console.WriteLine("==================");

string[] svalues = {"20", "20.00", "20.5", "20.5000", "20.125", "20.125000", "0.000"};
foreach (var value in svalues)
    Console.WriteLine(string.Format("{0} --> {1}", value, value.TrimDouble()));

Wynik:

20 --> 20
20 --> 20
20,5 --> 20,5
20,5 --> 20,5
20,125 --> 20,125
20,125 --> 20,125
0 --> 0
==================
20 --> 20
20.00 --> 2
20.5 --> 20.5
20.5000 --> 20.5
20.125 --> 20.125
20.125000 --> 20.125
0.000 --> 0

1
„20.00 -> 2” Myślę, że to nie jest dobre zachowanie. Aby to naprawić, wywołaj TrimEnd dwa razy: TrimEnd ('0'); TrimEnd (',');
Vadim

2
NIE UŻYWAJ kodu opisanego powyżej. @VadymK ma rację, że jego zachowanie jest wadliwe. Obetnie końcowe zera, które są PRZED kropką, jeśli nie ma kropki.
Sevin7

2
Odpowiedzi na stackoverflow nie są przeznaczone do kopiowania i wklejania. Są wykorzystywane jako źródło wiedzy . Ten sampel kodu uczy, IndexOfjak znajdować znaki w ciągu. Bardzo łatwo jest dostosować przykład, aby rozwiązać problem bez miejsc po przecinku.
jgauffin

2

Nie sądzę, że jest to możliwe po wyjęciu z pudełka, ale prosta metoda, taka jak ta, powinna to zrobić

public static string TrimDecimal(decimal value)
{
    string result = value.ToString(System.Globalization.CultureInfo.InvariantCulture);
    if (result.IndexOf('.') == -1)
        return result;

    return result.TrimEnd('0', '.');
}

2

Jest to dość łatwe do zrobienia po wyjęciu z pudełka:

Decimal YourValue; //just as example   
String YourString = YourValue.ToString().TrimEnd('0','.');

to usunie wszystkie końcowe zera z twojego Decimal.

Jedyne, co musisz zrobić, to dodać .ToString().TrimEnd('0','.');do zmiennej dziesiętnej, aby przekonwertować a DecimalnaString bez końcowych zer, jak w powyższym przykładzie.

W niektórych regionach powinien to być plik .ToString().TrimEnd('0',','); (gdzie zamiast kropki używają przecinka, ale dla pewności można też dodać kropkę i przecinek jako parametry).

(możesz również dodać oba jako parametry)


Możesz nie zdawać sobie z tego sprawy, ale gdy paramsmasz do czynienia z argumentem, nie musisz jawnie konstruować tablicy. Więc zamiast rozwlekłych TrimEnd("0".ToCharArray())możesz po prostu napisać TrimEnd('0')(uwaga: przekazano pojedynczo charzamiast a char[]).
Dan Tao

@Dan Tao: Dzięki, zapomniałem o tym. Minęło trochę czasu, odkąd używałem C #. Chociaż obie wersje działają, zredagowałem swój post.
Mervin

@Mervin: Musisz podać a char, a nie a string(więc: pojedyncze cudzysłowy, a nie podwójne cudzysłowy). Naprawiłem to dla ciebie - mam nadzieję, że nie masz nic przeciwko.
Dan Tao

1
Twoje rozwiązanie wygeneruje wynik podobny do „2”. kiedy ma tylko zera.
jgauffin

2
Następnie sugerowałbym wywołanie .ToString (). TrimEnd ('0'). TrimEnd ('.'). Również zamiast „.” lepiej mieć CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator, aby działał w różnych kulturach.
Ε Г И І И О

1
decimal val = 0.000000000100m;
string result = val == 0 ? "0" : val.ToString().TrimEnd('0').TrimEnd('.');

0

Skończyło się na następującym kodzie:

    public static string DropTrailingZeros(string test)
    {
        if (test.Contains(CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator))
        {
            test = test.TrimEnd('0');
        }

        if (test.EndsWith(CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator))
        {
            test = test.Substring(0,
                test.Length - CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator.Length);
        }

        return test;
    }

0

Skończyło się na tym wariancie:

public static string Decimal2StringCompact(decimal value, int maxDigits)
    {
        if (maxDigits < 0) maxDigits = 0;
        else if (maxDigits > 28) maxDigits = 28;
        return Math.Round(value, maxDigits, MidpointRounding.ToEven).ToString("0.############################", CultureInfo.InvariantCulture);
    }

Zalety:

możesz określić maksymalną liczbę cyfr znaczących po punkcie do wyświetlenia w czasie wykonywania;

możesz jawnie określić metodę rundy;

możesz jawnie kontrolować kulturę.


0

Możesz utworzyć metodę rozszerzenia

public static class ExtensionMethod {
  public static decimal simplify_decimal(this decimal value) => decimal.Parse($"{this:0.############}");
}

0

Poniższe metody rozszerzeń wykonałem dla siebie, aby pasowały do ​​jednego z moich projektów, ale może przyniosą korzyści komuś innemu.

using System.Numerics;
using System.Text.RegularExpressions;
internal static class ExtensionMethod
{
    internal static string TrimDecimal(this BigInteger obj) => obj.ToString().TrimDecimal();
    internal static string TrimDecimal(this decimal obj) => new BigInteger(obj).ToString().TrimDecimal();
    internal static string TrimDecimal(this double obj) => new BigInteger(obj).ToString().TrimDecimal();
    internal static string TrimDecimal(this float obj) => new BigInteger(obj).ToString().TrimDecimal();
    internal static string TrimDecimal(this string obj)
    {
        if (string.IsNullOrWhiteSpace(obj) || !Regex.IsMatch(obj, @"^(\d+([.]\d*)?|[.]\d*)$")) return string.Empty;
        Regex regex = new Regex("^[0]*(?<pre>([0-9]+)?)(?<post>([.][0-9]*)?)$");
        MatchEvaluator matchEvaluator = m => string.Concat(m.Groups["pre"].Length > 0 ? m.Groups["pre"].Value : "0", m.Groups["post"].Value.TrimEnd(new[] { '.', '0' }));
        return regex.Replace(obj, matchEvaluator);
    }
}

Chociaż będzie to wymagało odniesienia do System.Numerics.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.