Удалить конечные нули

177

У меня есть несколько полей, возвращаемых коллекцией как

2.4200
2.0044
2.0000

Я хочу результаты как

2.42
2.0044
2

Я пытался с String.Format, но он возвращает 2.0000и установка его N0округляет и другие значения.

Зо имеет
источник
3
изначально тип записи это строка ??
Синглтон
2
Смотрите мой ответ: String.Format()с «G» должен получить то, что вы хотите .. я обновил свой ответ со ссылкой на стандартные числовые форматы. Очень удобно.
Уши собаки
3
Десятичная дробь может быть приведена к double, и значение ToStringпо умолчанию для двойного испускает завершающие нули. Прочитайте это
Shimmy Weitzhandler
2
И это, вероятно, будет стоить меньше производительности (в промежутках от очень большого количества записей), чем передача функции G в виде строкового формата ToString.
Шимми Вайцхандлер
4
Вы не должны преобразовывать десятичное число в двойное, оно потеряет значение и может привести к появлению двух неточностей округления.
Кристианп

Ответы:

167

Разве это не так просто, если вход является строкой? Вы можете использовать один из них:

string.Format("{0:G29}", decimal.Parse("2.0044"))

decimal.Parse("2.0044").ToString("G29")

2.0m.ToString("G29")

Это должно работать для всех входных данных.

Обновление Проверьте стандартные числовые форматы. Мне пришлось явно установить спецификатор точности равным 29, поскольку документы четко указывают:

Однако, если число является десятичным, а спецификатор точности опущен, запись с фиксированной запятой всегда используется и конечные нули сохраняются

Обновление Конрада указано в комментариях:

Следите за значениями, такими как 0,000001. Формат G29 представит их в кратчайшем виде, поэтому он переключится на экспоненциальную запись. string.Format("{0:G29}", decimal.Parse("0.00000001",System.Globalization.CultureInfo.GetCultureInfo("en-US")))даст "1E-08" в результате.

Уши собаки
источник
1
Из всех ответов ваши имели меньше всего шагов. Спасибо, Собачьи Уши.
Зо
4
var d = 2,0 м; d.ToString ( "G");
Дэйв Хиллиер,
3
@ Дэйв Хиллиер - я вижу, я исправил свой ответ. Приветствия. [добавлен явный «спецификатор точности»]
Уши собаки
16
Следите за значениями, такими как 0,000001. Формат G29 представит их в кратчайшем виде, поэтому он переключится на экспоненциальную запись. string.Format ("{0: G29}", decimal.Parse ("0.00000001", System.Globalization.CultureInfo.GetCultureInfo ("en-US"))) в результате даст "1E-08"
Конрад,
15
@ Конрад - есть ли способ избежать научного обозначения для чисел, которые имеют 5 или 6 десятичных знаков?
Джилл
202

Я столкнулся с той же проблемой, но в случае, когда у меня нет контроля над выводом в строку, о котором позаботилась библиотека. Изучив детали реализации типа Decimal (см. Http://msdn.microsoft.com/en-us/library/system.decimal.getbits.aspx ), я придумал хитрый трюк (здесь как расширение метод):

public static decimal Normalize(this decimal value)
{
    return value/1.000000000000000000000000000000000m;
}

Экспонента десятичной части сводится к тому, что нужно. Вызов ToString () для выходного десятичного числа запишет число без запаздывания 0. Например

1.200m.Normalize().ToString();
Томас Матерна
источник
29
Этот ответ является собственностью, потому что в отличие от любого другого ответа на этот вопрос (и даже целого предмета) на самом деле РАБОТАЕТ и выполняет то, что задает ОП.
Coxy
2
Является ли число 0 точным значением здесь или просто для покрытия большинства ожидаемых значений?
Simon_Weaver
3
MSDN гласит: «Коэффициент масштабирования - это неявное число 10, возведенное в степень в диапазоне от 0 до 28», что, как я понимаю, десятичное число будет иметь не более 28 цифр после запятой. Любое количество нулей> = 28 должно работать.
Томас Матерна
2
Это лучший ответ! Число в ответе содержит 34 цифры, за 1которыми следуют 33 0, но это создает точно такой же decimalэкземпляр, как число с 29 цифрами, одной 1и 28-ми 0. Так же, как @ThomasMaterna сказал в своем комментарии. Не System.Decimalможет содержать более 29 цифр (цифры с начальными цифрами больше, чем 79228...могут иметь только 28 цифр). Если вы хотите больше конечных нулей, умножьте на 1.000...mвместо деления. Также Math.Roundможно отрубить несколько нулей, например, Math.Round(27.40000m, 2)дает 27.40m, так что остался только один ноль.
Джеппе Стиг Нильсен
2
Отличный трюк, но он НЕ работает на Mono и не гарантирует работу с функциональными версиями .NET
Bigjim
88

На мой взгляд, безопаснее использовать строки произвольного числового формата .

decimal d = 0.00000000000010000000000m;
string custom = d.ToString("0.#########################");
// gives: 0,0000000000001
string general = d.ToString("G29");
// gives: 1E-13
Михал Повага
источник
25
+1 за единственный ответ, основанный на стабильном, документированном поведении.
Синелав
3
Вы должны поместить туда 28 символов '#', см. Blackwasp.co.uk/DecimalTrailingZeroes.aspx .
Хайме
32

Я использую этот код, чтобы избежать научной нотации "G29":

public static string DecimalToString(this decimal dec)
{
    string strdec = dec.ToString(CultureInfo.InvariantCulture);
    return strdec.Contains(".") ? strdec.TrimEnd('0').TrimEnd('.') : strdec;
}

РЕДАКТИРОВАТЬ: используя систему CultureInfo.NumberFormat.NumberDecimalSeparator:

public static string DecimalToString(this decimal dec)
{
    string sep = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
    string strdec = dec.ToString(CultureInfo.CurrentCulture);
    return strdec.Contains(sep) ? strdec.TrimEnd('0').TrimEnd(sep.ToCharArray()) : strdec;
}
x7BiT
источник
1
Это прекрасный ответ. Это очень просто, и вы можете точно видеть, что происходит, вместо того, чтобы использовать магический формататор строк.
Тимоти Гонсалес
9
Хорошо, но это не будет работать для культур, которые используют запятую для десятичного разделителя. Вы должны были бы использоватьCulture.NumberFormat.NumberDecimalSeparator
blearyeye
1
Лучший подход Все остальное излишне сложно. Просто не забывайте всегда проверять, содержит ли ваш номер десятичную точку.
RRM
1
В расширении отсутствует параметр «this» перед параметром: public static string DecimalToString (this decimal dec)
João Antunes
Большое спасибо за комментарии. Я обновил ответ новой версией с советами.
x7BiT
19

Используйте #символ hash ( ), чтобы отображать только завершающие нули, когда это необходимо. Смотрите тесты ниже.

decimal num1 = 13.1534545765;
decimal num2 = 49.100145;
decimal num3 = 30.000235;

num1.ToString("0.##");       //13.15%
num2.ToString("0.##");       //49.1%
num3.ToString("0.##");       //30%
Fizzix
источник
7
Не ответ Вы не удаляете конечные нули, вы удаляете точность. Ваш предложенный метод num1.ToString("0.##")должен вернуть 13.1534545765(без изменений), потому что нет никаких завершающих нулей.
Ян 'сплит' К.
И именно поэтому я показываю ответы на три предложенных мне ввода, чтобы пользователи могли видеть, как работает мое решение.
Fizzix
Он не отвечает на вопрос, даже если вы перечислите предлагаемые материалы.
Дейв Фридель
2
Я бы проголосовал за это 10 раз, если бы мог. Именно то, что мне было нужно!
SubqueryCrunch
1
Идеальный ответ на вопрос. ЛУЧШИЙ ответ на мой взгляд. Комментарий, что это удаляет точность, является правильным, но это не было вопросом. Самое главное - это то, что мне нужно. Мои пользователи обычно вводят целые числа, возможно, что-то вроде 2,5, но мне нужно поддерживать 3 цифры после запятой. Поэтому я хочу дать им то, что они вошли, а не с кучей нулей, в которые они не вошли. например, Console.Write ($ "num3 = {num3: 0. ##}"); => num3 = 30
bobwki
13

Я нашел элегантное решение от http://dobrzanski.net/2009/05/14/c-decimaltostring-and-how-to-get-rid-of-trailing-zeros/

В принципе

decimal v=2.4200M;

v.ToString("#.######"); // Will return 2.42. The number of # is how many decimal digits you support.
DHornpout
источник
1
Обратите внимание, что если vесть 0m, результатом вашего кода будет пустая строка, а не "0".
Сэм
2
Да. Это должно быть "0. ########".
PRMan
2

Подход очень низкого уровня, но я верю, что это был бы наиболее эффективный способ, используя только быстрые целочисленные вычисления (без медленного анализа строк и чувствительных к культуре методов):

public static decimal Normalize(this decimal d)
{
    int[] bits = decimal.GetBits(d);

    int sign = bits[3] & (1 << 31);
    int exp = (bits[3] >> 16) & 0x1f;

    uint a = (uint)bits[2]; // Top bits
    uint b = (uint)bits[1]; // Middle bits
    uint c = (uint)bits[0]; // Bottom bits

    while (exp > 0 && ((a % 5) * 6 + (b % 5) * 6 + c) % 10 == 0)
    {
        uint r;
        a = DivideBy10((uint)0, a, out r);
        b = DivideBy10(r, b, out r);
        c = DivideBy10(r, c, out r);
        exp--;
    }

    bits[0] = (int)c;
    bits[1] = (int)b;
    bits[2] = (int)a;
    bits[3] = (exp << 16) | sign;
    return new decimal(bits);
}

private static uint DivideBy10(uint highBits, uint lowBits, out uint remainder)
{
    ulong total = highBits;
    total <<= 32;
    total = total | (ulong)lowBits;

    remainder = (uint)(total % 10L);
    return (uint)(total / 10L);
}
Bigjim
источник
1
Я попытался пойти по этому пути, но оптимизировано его дальше , так что есть на самом деле гораздо быстрее , чем у вас, к примеру , не деля привет и средних Интс (ваш aи b) в каждой итерации , а если они все равны нулю , так или иначе. Однако затем я нашел это удивительно простое решение, которое также легко превосходит то, что мы с вами придумали в отношении производительности .
Евгений Бересовский
2

Это будет работать:

decimal source = 2.4200m;
string output = ((double)source).ToString();

Или если ваше начальное значение string:

string source = "2.4200";
string output = double.Parse(source).ToString();

Обратите внимание на этот комментарий .

Шимми Вайцхандлер
источник
2
@Damien, да, и обратите внимание, что для этих целей (вы ничего не почувствуете, если не сделаете миллиард записей), во-первых, потому что double быстрее, во-вторых, потому что передача формата строки в функцию ToString требует большей производительности, чем не прохождение параметров. Опять же, вы ничего не почувствуете, если не будете работать с миллиардами записей.
Шимми Вайцхандлер
1
Вам не нужно указывать G, потому что double.ToStringпо умолчанию удаляет завершающие нули.
Shimmy Weitzhandler
4
Следите за значениями, такими как 0,000001. Они будут представлены в экспоненциальной записи. double.Parse ("0.00000001", System.Globalization.CultureInfo.GetCultureInfo ("en-US")). ToString () даст в результате "1E-08"
Конрад
17
Никогда не делай этого. Обычно причина, по которой вы используете десятичную дробь, заключается в том, что вы представляете число точно (не примерно так, как двойное число). Конечно, эта ошибка с плавающей запятой, вероятно, довольно мала, но может привести к отображению неправильных чисел, тем не менее
Адам Теген
2
О, преобразование 128-битного типа данных (десятичного) в 64-битный тип данных (double) для форматирования не является хорошей идеей!
avl_sweden
2

Это просто

decimal decNumber = Convert.ToDecimal(value);
        return decNumber.ToString("0.####");

Проверено.

Ура :)

Ракеш
источник
1

Зависит от того, что представляет ваше число и как вы хотите управлять значениями: это валюта, вам нужно округление или усечение, вам нужно это округление только для отображения?

Если для отображения рассмотрим форматирование чисел x.ToString ("")

http://msdn.microsoft.com/en-us/library/dwhawy9k.aspx и

http://msdn.microsoft.com/en-us/library/0c899ak8.aspx

Если это просто округление, используйте перегрузку Math.Round, для которой требуется перегрузка MidPointRounding.

http://msdn.microsoft.com/en-us/library/ms131274.aspx )

Если вы получаете свое значение из базы данных, рассмотрите приведение вместо преобразования: double value = (decimal) myRecord ["columnName"];

флорин
источник
0

Вы можете просто установить как:

decimal decNumber = 23.45600000m;
Console.WriteLine(decNumber.ToString("0.##"));
Данило Феррейра
источник
1
Чтобы это работало, вы должны поставить двадцать восемь символов «#» вместо двух.
Хайме
да, я неправильно понял его вопрос ... = /
Данило Феррейра
0

Если вы хотите сохранить десятичное число, попробуйте следующий пример:

number = Math.Floor(number * 100000000) / 100000000;
phuongnd
источник
0

Попытка сделать более дружественное решение DecimalToString ( https://stackoverflow.com/a/34486763/3852139 ):

private static decimal Trim(this decimal value)
{
    var s = value.ToString(CultureInfo.InvariantCulture);
    return s.Contains(CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator)
        ? Decimal.Parse(s.TrimEnd('0'), CultureInfo.InvariantCulture)
        : value;
}

private static decimal? Trim(this decimal? value)
{
    return value.HasValue ? (decimal?) value.Value.Trim() : null;
}

private static void Main(string[] args)
{
    Console.WriteLine("=>{0}", 1.0000m.Trim());
    Console.WriteLine("=>{0}", 1.000000023000m.Trim());
    Console.WriteLine("=>{0}", ((decimal?) 1.000000023000m).Trim());
    Console.WriteLine("=>{0}", ((decimal?) null).Trim());
}

Вывод:

=>1
=>1.000000023
=>1.000000023
=>
Дмитрий
источник
-1

Очень простой ответ - использовать TrimEnd (). Вот результат,

double value = 1.00;
string output = value.ToString().TrimEnd('0');

Выходное значение равно 1 Если мое значение равно 1,01, то выходное значение будет 1,01

Радж Де Инно
источник
4
Как насчет если value = 100?
Стивен Дрю
4
@Raj De Inno Даже если значение равно 1,00, вывод, который он выдает, равен «1». не «1»
Симба
-1

Следующий код может быть использован, чтобы не использовать строковый тип:

int decimalResult = 789.500
while (decimalResult>0 && decimalResult % 10 == 0)
{
    decimalResult = decimalResult / 10;
}
return decimalResult;

Возвращает 789,5

Рауль Минон
источник
-2

Обрезать конечные нули очень просто, решите с помощью дуплексного приведения:

        decimal mydecimal = decimal.Parse("1,45000000"); //(I)
        decimal truncate = (decimal)(double)mydecimal;   //(II)

(I) -> Разобрать десятичное значение из любого источника строки.

(II) -> Во-первых: приведение к удвоению удаляет завершающие нули. Второе: другие приводятся к десятичному типу, потому что не существует неявного преобразования из десятичного в двойное и наоборот)

amedriveroperez
источник
5
Вы принимаете все значения, которые будут соответствовать десятичному, в двойном. Это может вызвать OverflowExeception. Вы также можете потерять точность.
Мартин Малдер
-2

попробуйте этот код:

string value = "100";
value = value.Contains(".") ? value.TrimStart('0').TrimEnd('0').TrimEnd('.') : value.TrimStart('0');
Раджу
источник
Почему два ответа, друг мой?
Бешеный бык
2
А как насчет локалей, которые не используют '.'?
Мартин Малдер
не вариант вообще по сравнению с другими подходами в этом посте. Преобразование в строку и обратно всегда плохой вариант для манипулирования числами (хотя бы из-за локали)
Владимир Семашкин
-11

попробуй вот так

string s = "2.4200";

s = s.TrimStart('0').TrimEnd('0', '.');

а затем преобразовать это, чтобы плавать

одиночка
источник
1
Вы можете использовать subString()метод для этого, но в соответствии с вопросом, размещенным здесь, я не вижу каких-либо требований, как это =)
Singleton
2
Как насчет локалей, которые не используют '.'
Дэйв Хиллиер,
10
Это с треском проваливается для чисел, которые кратны 10,0.
Бен Фойгт
1
То, что сказал @BenVoigt, совершенно правильно, так как пример "12300.00"будет урезан "123". Другой эффект, который кажется нежелательным, заключается в том, что некоторые числа, подобные "0.00", обрезаются до пустой строки ""(хотя это число является особым случаем целочисленного значения, кратного 10,0).
Джеппе Стиг Нильсен