У меня есть переменная типа decimal
, и я хочу проверить количество цифр до десятичной точки в ней.
Что мне делать? Например, 467.45
должен возвращать 3
.
Получить число цифр до десятичной точки
Ответ 1
Решение без преобразования в string
(что может быть опасно в случае экзотических культур):
static int GetNumberOfDigits(decimal d)
{
decimal abs = Math.Abs(d);
return abs < 1 ? 0 : (int)(Math.Log10(decimal.ToDouble(abs)) + 1);
}
Обратите внимание, что это решение допустимо для всех десятичных значений
UPDATE
Фактически это решение не работает с некоторыми большими значениями, например: 999999999999998
, 999999999999999
, 9999999999999939
...
Очевидно, что математические операции с double
недостаточно точны для этой задачи.
При поиске неправильных значений я склонен использовать альтернативы на основе string
, предложенные в этом разделе. Что касается меня, то это свидетельствует о том, что они более надежны и просты в использовании (но имейте в виду культуры). Решения на основе циклов могут быть быстрее, хотя.
Спасибо комментаторам, стыдно за меня, урок для вас.
Ответ 2
Вместо преобразования в строку вы также можете разделить число на 10, пока оно не станет равным 0. Интересно, что математические операции над десятичными знаками намного медленнее, чем преобразование десятичной строки в строку и возврат длины (см..
Это решение не использует математические методы, которые берут двойной вход; поэтому все операции выполняются с десятичными знаками, и не задействовано литье.
using System;
public class Test
{
public static void Main()
{
decimal dec = -12345678912345678912345678912.456m;
int digits = GetDigits(dec);
Console.WriteLine(digits.ToString());
}
static int GetDigits(decimal dec)
{
decimal d = decimal.Floor(dec < 0 ? decimal.Negate(dec) : dec);
// As stated in the comments of the question,
// 0.xyz should return 0, therefore a special case
if (d == 0m)
return 0;
int cnt = 1;
while ((d = decimal.Floor(d / 10m)) != 0m)
cnt++;
return cnt;
}
}
Вывод 29
. Чтобы запустить этот образец, перейдите на страницу .
Боковое примечание: некоторые тесты показывают удивительные результаты (10 000 прогонов):
-
while ((d = decimal.Floor(d / 10m)) != 0m)
: 25ms -
while ((d = d / 10m) > 1m)
: 32 мс - ToString с Math-double-operations: 3ms
- ToString с десятичными операциями: 3ms
- BigInt (см. ответ @Heinzi): 2ms
Также, используя случайные числа, а не всегда одно и то же значение (чтобы избежать возможного кэширования преобразования десятичной строки в строку), было показано, что строковые методы выполняются намного быстрее.
Ответ 3
Я бы попробовал это:
Math.Truncate(467.45).ToString().Length
Если вы хотите быть уверенным, что не имеете каких-то странных результатов для разных культур и с отрицательными десятичными знаками, вам лучше использовать это:
var myDecimal = 467.45m;
Math.Truncate(Math.Abs(myDecimal)).ToString(CultureInfo.InvariantCulture).Length
Ответ 4
Я бы предпочел следующее вместо того, чтобы выполнить int
, чтобы гарантировать, что вы также можете обрабатывать большие числа (например, decimal.MaxValue
):
Math.Truncate ( Math.Abs ( decValue ) ).ToString( "####" ).Length
Ответ 5
Здесь рекурсивный пример (в основном для удовольствия).
void Main()
{
digitCount(0.123M); //0
digitCount(493854289.543354345M); //10
digitCount(4937854345454545435549.543354345M); //22
digitCount(-4937854345454545435549.543354345M); //22
digitCount(1.0M); //1
//approximately the biggest number you can pass to the function that works.
digitCount(Decimal.MaxValue + 0.4999999M); //29
}
int digitCount(decimal num, int count = 0)
{
//divided down to last digit, return how many times that happened
if(Math.Abs(num) < 1)
return count;
return digitCount(num/10, ++count); //increment the count and divide by 10 to 'remove' a digit
}
Ответ 6
decimal d = 467.45M;
int i = (int)d;
Console.WriteLine(i.ToString(CultureInfo.InvariantCulture).Length); //3
Как метод;
public static int GetDigitsLength(decimal d)
{
int i = int(d);
return i.ToString(CultureInfo.InvariantCulture).Length;
}
Примечание. Конечно, вы должны сначала проверить, что ваша десятичная запятая больше, чем Int32.MaxValue
или нет, Потому что, если это так, вы получаете OverflowException
.
Является ли такой случай, используя long
вместо int
, может быть лучше. Однако даже long
(System.Int64
) недостаточно велик, чтобы сохранить все возможные значения decimal
.
Как указано в Rawling , ваша полная часть может содержать разделитель тысяч, и мой код будет разбит в таком случае. Поскольку таким образом он полностью игнорирует мой номер, он содержит NumberFormatInfo.NumberGroupSeparator
или нет.
Вот почему получение чисел - это лучший подход. Как;
i.ToString().Where(c => Char.IsDigit(c)).ToArray()
Ответ 7
Math.Floor(Math.Log10((double)n) + 1);
- путь.
Преобразование в int
равно BAD, потому что decimal
может быть больше int
:
Decimal.MaxValue = 79,228,162,514,264,337,593,543,950,335;
Int32.MaxValue = 2,147,483,647; //that is, hexadecimal 0x7FFFFFFF;
Math.Floor(n).ToString().Count();
плохо, потому что он может включать в себя тысячи разделителей.
Ответ 8
var sep = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
var count = d.ToString().TakeWhile(c => c != sep).Count();
Ответ 9
Если у вас есть предвзятость к меньшим числам, вы можете использовать что-то более простое, как это.
Он разбивается на два метода, поэтому первый метод меньше и может быть встроен.
Производительность примерно такая же, как и решение с Log10, но без ошибок округления. Метод, использующий Log10, по-прежнему является самым быстрым (бит) специально для чисел > 1 млн.
public static int CountNrOfDigitsIfs(decimal d) {
var absD = Math.Abs(d);
// 1
if (absD < 10M) return 1;
// 2
if (absD < 100M) return 2;
// 3
if (absD < 1000M) return 3;
// 4
if (absD < 10000M) return 4;
return CountNrOfDigitsIfsLarge(d);
}
private static int CountNrOfDigitsIfsLarge(decimal d) {
// 5
if (d < 100000M) return 5;
// 6
if (d < 1000000M) return 6;
// 7
if (d < 10000000M) return 7;
// 8
if (d < 100000000M) return 8;
// 9
if (d < 1000000000M) return 9;
// 10
if (d < 10000000000M) return 10;
// 11
if (d < 100000000000M) return 11;
// 12
if (d < 1000000000000M) return 12;
// 13
if (d < 10000000000000M) return 13;
// 14
if (d < 100000000000000M) return 14;
// 15
if (d < 1000000000000000M) return 15;
// 16
if (d < 10000000000000000M) return 16;
// 17
if (d < 100000000000000000M) return 17;
// 18
if (d < 1000000000000000000M) return 18;
// 19
if (d < 10000000000000000000M) return 19;
// 20
if (d < 100000000000000000000M) return 20;
// 21
if (d < 1000000000000000000000M) return 21;
// 22
if (d < 10000000000000000000000M) return 22;
// 23
if (d < 100000000000000000000000M) return 23;
// 24
if (d < 1000000000000000000000000M) return 24;
// 25
if (d < 10000000000000000000000000M) return 25;
// 26
if (d < 100000000000000000000000000M) return 26;
// 27
if (d < 1000000000000000000000000000M) return 27;
// 28
if (d < 10000000000000000000000000000M) return 28;
return 29; // Max nr of digits in decimal
}
Этот код генерируется с использованием следующего шаблона T4:
<#
const int SIGNIFICANT_DECIMALS = 29;
const int SPLIT = 5;
#>
namespace Study.NrOfDigits {
static partial class DigitCounter {
public static int CountNrOfDigitsIfs(decimal d) {
var absD = Math.Abs(d);
<#
for (int i = 1; i < SPLIT; i++) { // Only 29 significant digits
var zeroes = new String('0', i);
#>
// <#= i #>
if (absD < 1<#= zeroes #>M) return <#= i #>;
<#
}
#>
return CountNrOfDigitsIfsLarge(d);
}
private static int CountNrOfDigitsIfsLarge(decimal d) {
<#
for (int i = SPLIT; i < SIGNIFICANT_DECIMALS; i++) { // Only 29 significant digits
var zeroes = new String('0', i);
#>
// <#= i #>
if (d < 1<#= zeroes #>M) return <#= i #>;
<#
}
#>
return <#= SIGNIFICANT_DECIMALS #>; // Max nr of digits in decimal
}
}
}
Ответ 10
Это будет сделано, если вы действительно не хотите использовать метод журнала (наиболее подходящим для ИМО). Это самый ясный способ сделать это с помощью ToString():
Math.Abs(val).ToString("f0", CultureInfo.InvariantCulture).Length
Или, наоборот, если вы не хотите считать 0.123M
как имеющую одну цифру:
Math.Abs(val).ToString("#", CultureInfo.InvariantCulture).Length
Ответ 11
Вы можете использовать функцию ToString с настраиваемым форматом.
Decimal value = 467.45m;
int count = Math.Abs(value).ToString("#", System.Globalization.CultureInfo.InvariantCulture).Length;
#
указывает, что вам нужны только символы перед .
System.Globalization.CultureInfo.InvariantCulture
гарантирует, что вы не получите никакого форматирования из опции Region.
Ответ 12
TL;DR все остальные ответы. Я написал это в PHP, и математика была бы такой же. (Если бы я знал С#, я бы написал на этом языке.)
$input=21689584.999;
$input=abs($input);
$exp=0;
do{
$test=pow(10,$exp);
if($test > $input){
$digits=$exp;
}
if($test == $input){
$digits=$exp+1;
}
$exp++;
}while(!$digits);
if($input < 1){$digits=0;}
echo $digits;
Я не сомневаюсь, что там лучший способ, но я хотел бросить $.02
EDIT:
Я php-ized код, который я упомянул в своих комментариях, но покончил с преобразованием int.
function digitCount($input){
$digits=0;
$input=abs($input);
while ($input >= 1) {
$digits++;
$input=$input/10;
//echo $input."<br>";
}
return $digits;
}
$big=(float)(PHP_INT_MAX * 1.1);
echo digitCount($big);
Ответ 13
Математический способ сделать это (и, вероятно, самый быстрый) - получить логарифм базы 10 абсолютного значения этого числа и округлить его вверх.
Math.Floor(Math.Log10(Math.Abs(val)) + 1);
Ответ 14
Итак, я столкнулся с этим раньше и решил его с помощью этого кода:
SqlDecimal d = new SqlDecimal(467.45M);
int digits = d.Precision - d.Scale;
SqlDecimal
является частью пространства имен System.Data.SqlTypes
. "Точность" - это общее количество значимых цифр, а "Масштаб" - это количество цифр после десятичной точки.
Теперь я знаю, что одно возражение против этого маршрута состоит в том, что SqlDecimal
является частью кода, специфичного для SQL Server. Это действительная точка, но я бы также отметил, что она является частью самой платформы .NET, и с тех пор, как минимум, с версией 1.1, поэтому кажется, что она будет по-прежнему применяться независимо от того, что делает код вокруг нее.
Я посмотрел под капот с декомпилятором ( "dotPeek" JetBrains в этом случае), чтобы увидеть, может быть, код для вычисления точности и масштаба можно легко извлечь и просто использовать, не втягивая SqlDecimal
. Код для расчета масштаба очень прост, но метод вычисления точности нетривиальен, поэтому, если бы это был я, я бы прошел через SqlDecimal
.
Ответ 15
Это будет решение Java
public class test {
public static void main(String args[]) {
float f = 1.123f;
int a = (int) f;
int digits = 0;
while (a > 0) {
digits++;
a=a/10;
}
System.out.println("No Of digits before decimal="+digits);
}
}
Ответ 16
Если вы обрабатываете нули или отсутствие нулей в качестве 1 числа, это нормально. Если вы хотите, чтобы ноль возвращал ноль или отсутствовал нуль, чтобы вернуть ноль, тогда есть несколько краевых случаев, которые не должны быть слишком сложными для добавления. Кроме того, должно Абсолютное значение обрабатывать отрицательные числа. Добавлен этот тестовый пример.
const decimal d = 123.45m;
const decimal d1 = 0.123m;
const decimal d2 = .567m;
const decimal d3 = .333m;
const decimal d4 = -123.45m;
NumberFormatInfo currentProvider = NumberFormatInfo.InvariantInfo;
var newProvider = (NumberFormatInfo) currentProvider.Clone();
newProvider.NumberDecimalDigits = 0;
string number = d.ToString("N", newProvider); //returns 123 = .Length = 3
string number1 = d1.ToString("N", newProvider); //returns 0 = .Length = 1
string number2 = d2.ToString("N", newProvider); //returns 1 = .Length = 1
string number3 = d3.ToString("N", newProvider); //returns 0 = .Length = 1
string number4 = Math.Abs(d4).ToString("N", newProvider); //returns 123 = .Length = 3
Вот несколько окончательное решение, если вы найдете тестовый пример, который не работает, сообщите мне. Он должен вернуть 3,0,0,0,3 для предоставленных тестовых случаев.
public static int NumbersInFrontOfDecimal(decimal input)
{
NumberFormatInfo currentProvider = NumberFormatInfo.InvariantInfo;
var newProvider = (NumberFormatInfo)currentProvider.Clone();
newProvider.NumberDecimalDigits = 0;
var absInput = Math.Abs(input);
var numbers = absInput.ToString("N", newProvider);
//Handle Zero and < 1
if (numbers.Length == 1 && input < 1.0m)
{
return 0;
}
return numbers.Length;
}
Ответ 17
Этот ответ в значительной степени снят с Calculate System.Decimal Precision and Scale, но с небольшим изменением, чтобы соответствовать заданному вопросу.
class Program
{
static void Main()
{
decimal dec = 467.45m;
Console.WriteLine(dec.GetNumberOfDigitsBeforeDecimalPlace());
}
}
public static class DecimalEx
{
public static int GetNumberOfDigitsBeforeDecimalPlace(this decimal dec)
{
var x = new System.Data.SqlTypes.SqlDecimal(dec);
return x.Precision - x.Scale;
}
}
Также, если вы хотите сделать это, не используя класс SqlDecimal, проверьте Jon Skeet ответ на тот же вопрос.
Ответ 18
Используйте modulo, я не программист на С#, но я уверен, что это решение работает:
double i = 1;
int numberOfDecimals = 0;
while (varDouble % i != varDouble)
{
numberOfDecimals++;
i*=10;
}
return numberOfDecimals;
Ответ 19
Другие решения потеряют цифры, если число слишком велико.
public int Digits(Decimal i)
{
NumberFormatInfo format = CultureInfo.CurrentCulture.NumberFormat;
var str = Math.Abs(i).ToString().Replace(format.NumberGroupSeparator, "");
var index = str.IndexOf(format.NumberDecimalSeparator);
var digits = index == -1 ? str.Length : index;
}
Ответ 20
Вот моя оптимизированная версия кода, вдохновленная Grey:
static int GetNumOfDigits(decimal dTest)
{
int nAnswer = 0;
dTest = Math.Abs(dTest);
//For loop version
for (nAnswer = 0; nAnswer < 29 && dTest > 1; ++nAnswer)
{
dTest *= 0.1M;
}
//While loop version
//while (dTest > 1)
//{
// nAnswer++;
// dTest *= 0.1M;
//}
return (nAnswer);
}
Если вы не хотите, чтобы Math.Abs вызывается внутри этой функции, обязательно используйте его вне функции над параметром перед вызовом GetNumOfDigits.
Я решил удалить другие коды, чтобы уменьшить беспорядок в моем ответе, хотя они помогли мне добраться до этой точки...
Если есть какие-либо улучшения, дайте мне знать, и я обновлю его:).
Ответ 21
Чтобы получить точный и культурно агностический ответ, я делаю следующее:
- Используйте
System.Numerics.BigInteger
, конструктор которого принимает десятичное число и, похоже, не создает ошибок округления. - Используйте
BigInteger.Abs()
, чтобы удалить любой знак. - Используйте
BigInteger.ToString()
с форматом "#" для подавления любых разделителей, которые могут возникнуть.
код
decimal num = 123213123.123123M;
int length = BigInteger.Abs((BigInteger)num).ToString("#").Length;
Ответ 22
Вы можете сделать это, округляя число, затем получив длину нового номера. Вы можете сделать это следующим образом:
var number = 476.43;
var newNumber = Math.round(number);
//get the length of the rounded number, and subtract 1 if the
//number is negative (remove the negative sign from the count)
int digits = newNumber.ToString().Length - (number < 0 ? 1 : 0);
Ответ 23
просто:
string value = "467.45";
int count = value.split('.')[0] == "0" ? 0 : value.split('.')[0].ToString().Replace("-","").Count();
Ответ 24
Алгоритм:
- Преобразовать
|decimal|
в строку. - Если
"."
существует в десятичной дроби, разрезайте перед ним, в противном случае учитывайте все число. - Длина строки возврата.
Пример:
3.14 --> 3.14 --> "3.14" --> "3.14".Substring(0,1) --> "3".Length --> 1
-1024 --> 1024 --> "1024" --> IndexOf(".") = -1 --> "1024" --> 4
Код:
static int getNumOfDigits (decimal num)
{
string d = Math.Abs(num).ToString();
if (d.IndexOf(".") > -1)
{
d = d.Substring(0, d.IndexOf("."));
}
return d.Length;
}
Ответ 25
Я не тестировал это, но я бы сохранил это прямо и сделал:
decimal value = 467.45;
string str = Convert.toString(value); // convert your decimal type to a string
string[] splitStr = str.split('.'); // split it into an array (use comma separator assuming you know your cultural context)
Console.WriteLine(splitStr[0].Count); // get the first element. You can also get the number of figures after the point by indexing the next value in the array.
Это не обрабатывает отрицательные числа. Если вы заботитесь о тех, кто думает об абсолютном значении. Кроме того, если вы хотите, чтобы 0 до десятичного разряда не учитывалось, вы можете использовать простой оператор if, чтобы проверить его.
Ответ 26
Использование:
var value=467.45;
var length=((int)value).ToString().Length