Лучший способ отображения десятичной дроби без конечных нулей

108

Есть ли средство форматирования дисплея, которое будет выводить десятичные числа в качестве этих строковых представлений в C # без округления?

// 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. #} будет округляться, и использование некоторых функций типа Trim не будет работать с привязанным числовым столбцом в сетке.

JC.
источник

Ответы:

147

У вас есть максимальное количество десятичных знаков, которое вам когда-либо понадобится? (В ваших примерах не более 5).

Если это так, я думаю, что форматирование с помощью «0. #####» будет делать то, что вы хотите.

    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
И вы всегда можете накинуть на формат непомерное * количество символов #. * не научный
Энтони Пеграм
3
+1 - кстати, ведущий # не нужен. «0. #####» работает идентично. И в любом случае, это округление от нуля (просто к сведению).
TrueWill
14
@TrueWill На самом деле это имеет значение. d == 0.1m, затем "0. #" отображает "0.1", а "#. #" отображает ".1" без начального 0. Ни то, ни другое не является неправильным или правильным, просто зависит от того, что вы хотите.
AaronLS
4
Мне всегда нужно было отображать один десятичный знак, поэтому я использовал строку формата "0.0#".
Codes with Hammer
1
См. Ответ Эрвина Майера ниже.
шлгуг
32

Я только что научился правильно использовать Gспецификатор формата. См. Документацию MSDN . Немного ниже есть примечание, в котором говорится, что конечные нули будут сохраняться для десятичных типов, если точность не указана. Я не знаю, зачем они это сделали, но указание максимального количества цифр для нашей точности должно решить эту проблему. Так что для форматирования десятичных знаков G29это лучший выбор.

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
Schmalls
источник
21
Это приведет к отображению 0,00001 как 1E-05
см.
17

Этот строковый формат должен сделать ваш день: «0. ############################». Имейте в виду, что десятичные дроби могут иметь не более 29 значащих цифр.

Примеры:

? (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
Это самый общий ответ на проблему. Не уверен, почему это не формат "G" по умолчанию для десятичных чисел. Нули в конце не добавляют информации. В документации Microsoft есть странное предостережение для десятичных чисел : docs.microsoft.com/en-us/dotnet/standard/base-types/ ... Однако, если число является десятичным, а спецификатор точности опущен, всегда используется запись с фиксированной точкой. и конечные нули сохраняются.
Эрик Роллер
10

Это еще один вариант того, что я видел выше. В моем случае мне нужно сохранить все значащие цифры справа от десятичной точки, что означает удаление всех нулей после самой значащей цифры. Просто подумал, что было бы неплохо поделиться. Я не могу ручаться за эффективность этого, но когда вы пытаетесь достичь эстетики, вы уже в значительной степени прокляты в неэффективности.

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

Вот и все, просто хотел бросить свои два цента.

дислексиканабоко
источник
3
Это однозначно лучшее решение, оно не взрывается и на «0,0». Я добавил флаг по умолчанию, чтобы целые числа отображались как «1.0», и это только что заставило все мои десятичные дроби выровняться. Не могу понять, почему это не лучший ответ, + лоты.
Стив Хибберт,
2
Это особенно хорошо, если кто-то уже передал вам строку. Вы можете просто изменить его на ToTrimmedString (эта строка strValue) и удалить первую строку.
PRMan
Неплохо подмечено. Думаю, когда я писал это, я имел дело со значениями, которые уже были десятичными или что-то в этом роде, поэтому я хотел охватить все это целиком. Вместо этого вы могли бы иметь тело этого метода внутри другого перегруженного метода, который вместо этого принимает строку.
дислексиканабоко
Это хорошее решение, когда вы можете вызвать ToTrimmedString (), но во многих случаях вы ограничены строкой формата.
Майк Мариновски
1
Хотя десятичный разделитель должен зависеть от культуры, я думаю, что это хорошее решение. Небольшой вопрос, а почему бы просто не strValue.TrimEnd('0').TrimEnd('.')вместо EndsWithчека?
nawfal
5

Другое решение, основанное на ответе дислексиканабоко , но не зависящее от текущей культуры:

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);
}
Давид Досталь
источник
Отлично. За исключением SqlDecimal, это CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator, несмотря на региональные настройки.
user2091150
4

Способ расширения:

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

Пример кода:

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

Вывод:

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» Я считаю это плохим поведением. Чтобы исправить это, вызовите TrimEnd два раза: TrimEnd ('0'); TrimEnd (',');
Вадим
2
НЕ ИСПОЛЬЗУЙТЕ код, написанный выше. @VadymK прав, что его поведение некорректно. Он обрежет конечные нули, которые стоят ПЕРЕД точкой, если точки нет.
Sevin7,
2
Ответы на stackoverflow не предназначены для копирования и вставки. Они используются как источник обучения . Этот пример кода научит вас использовать IndexOfдля поиска символов в строке. В примере довольно легко настроить решение проблемы без десятичных знаков.
jgauffin
2

Я не думаю, что это возможно из коробки, но такой простой метод должен сделать это

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

Сделать "из коробки" довольно просто:

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

это удалит все конечные нули из вашего десятичного числа.

Единственное, что вам нужно сделать, это добавить .ToString().TrimEnd('0','.');к десятичной переменной, чтобы преобразовать a Decimalв Stringбез завершающих нулей, как в примере выше.

В некоторых регионах это должно быть .ToString().TrimEnd('0',',');(где вместо точки используется запятая, но вы также можете добавить точку и запятую в качестве параметров, чтобы быть уверенным).

(вы также можете добавить оба параметра в качестве параметров)

Мервин
источник
Вы можете этого не осознавать, но при работе с paramsаргументом вам не нужно явно создавать массив. Таким образом, вместо подробного TrimEnd("0".ToCharArray())вы можете просто написать TrimEnd('0')(примечание: передается single charвместо a char[]).
Дэн Тао
@ Дэн Тао: Спасибо, я забыл об этом. Я давно не использовал C #. Хотя обе версии работают, я отредактировал свой пост.
Мервин
@Mervin: вам нужно передать a char, а не a string(так: одинарные кавычки, а не двойные кавычки). Я починил это для вас - надеюсь, вы не против.
Дэн Тао
1
Ваше решение выдаст результат типа «2». когда были только нули.
jgauffin,
2
Тогда я бы предложил вызвать .ToString (). TrimEnd ('0'). TrimEnd ('.'). Также вместо "." лучше иметь CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator, чтобы он работал в разных культурах.
Ε Г И І И О
1
decimal val = 0.000000000100m;
string result = val == 0 ? "0" : val.ToString().TrimEnd('0').TrimEnd('.');
Джерри Лян
источник
0

Я получил следующий код:

    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

У меня получился такой вариант:

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

Преимущества:

вы можете указать максимальное количество значащих цифр после точки для отображения во время выполнения;

вы можете явно указать метод раунда;

вы можете явно контролировать культуру.

lilo0
источник
0

Вы можете создать метод расширения

public static class ExtensionMethod {
  public static decimal simplify_decimal(this decimal value) => decimal.Parse($"{this:0.############}");
}
KevinBui
источник
0

Я сделал следующие методы расширения для себя, чтобы они соответствовали одному из моих проектов, но, возможно, они будут полезны кому-то другому.

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

Хотя для этого потребуется ссылка на System.Numerics.

Джереми МакКрори
источник