Получить количество цифр до десятичной точки


у меня есть переменная decimal введите и я хочу проверить количество цифр перед десятичной точкой в нем. Что же мне делать? Например, 467.45 должен возвратить 3.

26 61

26 ответов:

решение без преобразования в string (что может быть опасно в случае экзотических культур):

static int GetNumberOfDigits(decimal d)
{
    decimal abs = Math.Abs(d);

    return abs < 1 ? 0 : (int)(Math.Log10(decimal.ToDouble(abs)) + 1);
}

обратите внимание, что это решение действительно для всех десятичных значений

обновление

на самом деле это решение не работать с некоторыми большими значениями, например: 999999999999998,999999999999999,9999999999999939...

очевидно, математические операции с double недостаточно точны для этой задачи.

пока поиск неправильных значений, которые я обычно использую string-альтернативные варианты, предложенные в этом разделе. Что касается меня, то это свидетельствует о том, что они более надежны и просты в использовании (но имейте в виду культуры). Однако решения на основе циклов могут быть быстрее.

спасибо комментаторам, позор мне, урок вам.

вместо преобразования в строку, вы также можете разделить число на 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. К запустите этот пример, посетите этот ссылке.


Примечание стороны: некоторые тесты показывают удивительные результаты (10k работает):

  • while ((d = decimal.Floor(d / 10m)) != 0m): 25МС
  • while ((d = d / 10m) > 1m): 32ms
  • ToString с математикой-двойные операции: 3ms
  • ToString с десятичными операциями: 3ms
  • BigInt (см. ответ of @Heinzi): 2ms

также используя случайные числа вместо всегда одного и того же значения (чтобы избежать возможного кэширования десятичного преобразования в строку) показал, что строковые методы намного быстрее.

Я бы попробовал это:

Math.Truncate(467.45).ToString().Length

Если вы хотите быть уверены, что не имеете каких-то странных результатов для разных культур и с отрицательными десятичными знаками, вам лучше использовать это:

var myDecimal = 467.45m;
Math.Truncate(Math.Abs(myDecimal)).ToString(CultureInfo.InvariantCulture).Length

Я бы предпочел следующее вместо приведения к int чтобы убедиться, что вы также можете обрабатывать большие числа (например,decimal.MaxValue):

Math.Truncate ( Math.Abs ( decValue ) ).ToString( "####" ).Length

вот рекурсивный пример (в основном для удовольствия).

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
}
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 может лучше подойти. даже a long (System.Int64) не достаточно большой, чтобы держать все возможные decimal значение.

Как Роулинг указано, ваша полная часть может содержать разделитель тысяч, и мой код будет нарушен в таком случае. Потому что таким образом, он полностью игнорирует мой номер содержит NumberFormatInfo.NumberGroupSeparator или нет.

вот почему получение чисел только лучший подход. Например;

i.ToString().Where(c => Char.IsDigit(c)).ToArray()

Math.Floor(Math.Log10((double)n) + 1); - это путь.

преобразование int это плохо, потому что 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(); это плохо, потому что он может включать в себя тысячи сепараторы.

Если у вас есть уклон в сторону меньших количествах, вы можете использовать что-то более простое, как это.

Он разделен на два метода, поэтому первый метод меньше и может быть встроен.

производительность примерно такая же, как у решения с 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
        }

    }
}
var sep = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
var count = d.ToString().TakeWhile(c => c != sep).Count();

Это будет сделано, если вы действительно не хотите использовать метод журнала (который IMO является лучшим способом). Это о самом ясном способе, который я могу придумать, чтобы сделать это с помощью ToString ():

Math.Abs(val).ToString("f0", CultureInfo.InvariantCulture).Length

или альтернативно, если вы не хотите считать 0.123M как имеющие одну цифру:

Math.Abs(val).ToString("#", CultureInfo.InvariantCulture).Length

вы можете использовать функцию ToString с пользовательским форматом.

Decimal value = 467.45m;
int count = Math.Abs(value).ToString("#", System.Globalization.CultureInfo.InvariantCulture).Length;

The # укажите, что вы хотите только символы перед .

The System.Globalization.CultureInfo.InvariantCulture убедитесь, что вы не получите никакого форматирования из опции региона.

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

Math.Floor(Math.Log10(Math.Abs(val)) + 1);

TLDR все остальные ответы. Я написал это на PHP, и математика будет такой же. (Если бы я знал C#, я бы написал на этом языке.)

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

Итак, я столкнулся с этим раньше, и решил его с помощью этого кода:

SqlDecimal d = new SqlDecimal(467.45M);
int digits = d.Precision - d.Scale;

SqlDecimal является частью System.Data.SqlTypes пространство имен. "Точность" - это общее количество значащих цифр, а" масштаб " - количество цифр после десятичной точки.

теперь я знаю, что одно возражение против этого маршрута заключается в том, что SqlDecimal является частью кода, специфичного для SQL Server. Это допустимый момент, но я бы также отметил, что он является частью самой платформы .NET framework и с тех пор был по крайней мере, версия 1.1, поэтому кажется, что она все равно будет применима независимо от того, что делает код вокруг нее.

Я заглянул под капот с декомпилятором (JetBrains' dotPeek в этом случае), чтобы увидеть, может ли код для вычисления точности и масштаба быть легко извлечен и просто использован, не втягивая SqlDecimal. Код для вычисления масштаба очень прост, но метод вычисления точности нетривиален, поэтому, если бы это был я, я бы просто прошел SqlDecimal.

Это будет решение 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);
    }
}

Если вы рассматриваете нули или отсутствие нулей как 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;
        }

этот ответ в значительной степени снят с Система Расчета.Десятичная точность и масштаб но с небольшим изменением, чтобы соответствовать вопрос.

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, проверьте ответ Джона Скита на тот же вопрос.

используйте по модулю, я не программист C#, но я уверен, что это решение работает:

double i = 1;
int numberOfDecimals = 0;


while (varDouble % i != varDouble)
{
numberOfDecimals++;
i*=10;
}

return numberOfDecimals;

другие решения потеряют цифры, если число слишком велико.

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

вот моя оптимизированная версия кода, вдохновленная ответом Грея:

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

Если вы не хотите математику.Abs для вызова внутри этой функции, то не забудьте использовать его вне функции на параметре перед вызовом GetNumOfDigits.

Я решил удалить другие коды, чтобы уменьшить беспорядок в моем ответе, хотя они помогли мне добраться до этого момента...

Если есть какие-либо улучшения, то дайте мне знать, и я буду обновлять его :).

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

  1. использовать System.Numerics.BigInteger, конструктор которого принимает десятичное число и не кажется для получения ошибок округления.
  2. использовать BigInteger.Abs() удалить какой-либо знак.
  3. использовать BigInteger.ToString() с форматом " # " для подавления любых разделителей, которые могут возникнуть.

код

decimal num = 123213123.123123M;
int length = BigInteger.Abs((BigInteger)num).ToString("#").Length;

вы можете сделать это путем округления числа, а затем получить длину нового числа. Вы могли бы сделать это так:

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

:

  • преобразование |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;
}

Я не проверял это, но я бы держал это прямо и делал:

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 для его проверки.

просто :

string value = "467.45";
int count =  value.split('.')[0] == "0" ? 0 : value.split('.')[0].ToString().Replace("-","").Count();

использование:

var value=467.45;
var length=((int)value).ToString().Length