web-dev-qa-db-fra.com

Obtenir le nombre de chiffres avant le point décimal

J'ai une variable de type decimal et je veux vérifier le nombre de chiffres avant le point décimal. Que devrais-je faire? Par exemple, 467.45 devrait retourner 3.

63
fasadat

Solution sans conversion en string (ce qui peut être dangereux en cas de cultures exotiques):

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

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

Notez que cette solution est valable pour toutes les valeurs décimales

MISE À JOUR

En fait, cette solution ne fonctionne pas avec certaines grandes valeurs, par exemple: 999999999999998, 999999999999999, 9999999999999939...

De toute évidence, les opérations mathématiques avec double ne sont pas suffisamment précises pour cette tâche.

Lors de la recherche de valeurs erronées, j'ai tendance à utiliser les alternatives basées sur string proposées dans cette rubrique. Pour moi, c'est la preuve qu'ils sont plus fiables et faciles à utiliser (mais attention aux cultures). Les solutions basées sur les boucles peuvent cependant être plus rapides.

Merci aux commentateurs, honte à moi, leçon à vous.

82
astef

Au lieu de convertir en chaîne, vous pouvez également diviser le nombre par 10 jusqu'à ce qu'il soit égal à 0. Il est intéressant de noter que les opérations mathématiques sur les décimales sont beaucoup plus lentes que de convertir la décimale en chaîne et de renvoyer la longueur (voir les repères ci-dessous).
Cette solution n'utilise pas les méthodes mathématiques qui prennent un double en entrée; donc toutes les opérations sont effectuées sur des décimales et aucune conversion n'est impliquée.

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

La sortie est 29. Pour exécuter cet exemple, visitez ce lien .


Note latérale: certains benchmarks montrent des résultats surprenants (10 000 courses):

  • while ((d = decimal.Floor(d / 10m)) != 0m): 25 ms
  • while ((d = d / 10m) > 1m): 32 ms
  • ToString avec Math-double-opérations: 3 ms
  • ToString avec opérations décimales: 3 ms
  • BigInt (voir réponse de @Heinzi): 2 ms

L'utilisation de nombres aléatoires au lieu de toujours la même valeur (pour éviter la mise en cache possible de la conversion décimale en chaîne) a montré que les méthodes basées sur les chaînes sont beaucoup plus rapides.

32
Markus

J'essaierais ceci:

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

Si vous voulez être sûr de ne pas avoir de résultats étranges pour différentes cultures et avec des décimales négatives, vous feriez mieux d'utiliser ceci:

var myDecimal = 467.45m;
Math.Truncate(Math.Abs(myDecimal)).ToString(CultureInfo.InvariantCulture).Length
26
Kevin Brechbühl

Je préférerais ce qui suit au lieu de convertir en int pour vous assurer que vous pouvez également gérer les grands nombres (par exemple decimal.MaxValue):

Math.Truncate ( Math.Abs ( decValue ) ).ToString( "####" ).Length
13
Stephan Bauer
decimal d = 467.45M;
int i = (int)d;
Console.WriteLine(i.ToString(CultureInfo.InvariantCulture).Length); //3

Comme méthode;

public static int GetDigitsLength(decimal d)
{
  int i = int(d);
  return i.ToString(CultureInfo.InvariantCulture).Length;
}

Remarque : Bien sûr, vous devez d'abord vérifier que la partie entière de vos décimales est plus grande que Int32.MaxValue ou non. Parce que si c'est le cas, vous obtenez un OverflowException .

Dans ce cas, l'utilisation de long au lieu de int peut être une meilleure approche. Cependant même un long (System.Int64) n'est pas assez grand pour contenir toutes les valeurs possibles de decimal.

Comme Rawling mentionné, votre partie entière peut contenir le séparateur de milliers et mon code sera cassé dans un tel cas. Parce que de cette façon, il ignore totalement mon numéro contient NumberFormatInfo.NumberGroupSeparator ou non.

C'est pourquoi obtenir des chiffres uniquement est une meilleure approche. Comme;

i.ToString().Where(c => Char.IsDigit(c)).ToArray()
7
Soner Gönül

Voici un exemple récursif (surtout pour le plaisir).

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
Gray

Si vous avez un parti pris pour les petits nombres, vous pouvez utiliser quelque chose de plus simple comme celui-ci.

Il est divisé en deux méthodes, de sorte que la première méthode est plus petite et peut être intégrée.

Les performances sont à peu près les mêmes que celles de la solution avec le Log10, mais sans les erreurs d'arrondi. La méthode utilisant Log10 est toujours la plus rapide (un peu) spécialement pour les nombres> 1 million.

    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
    }

Ce code est généré à l'aide du modèle T4 suivant:

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

    }
}
5
GvS

Math.Floor(Math.Log10((double)n) + 1); est la voie à suivre.

Convertir en int est MAUVAIS car decimal peut être plus grand que 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(); est mauvais car il peut inclure des milliers de séparateurs.

5
Nahum

Cela fonctionnera si vous ne voulez vraiment pas utiliser la méthode Log (quelle IMO est la meilleure façon). Il s'agit de la façon la plus claire de penser à cela en utilisant ToString ():

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

Ou bien, si vous ne voulez pas compter 0.123M comme ayant un chiffre:

Math.Abs(val).ToString("#", CultureInfo.InvariantCulture).Length
3
Luaan
var sep = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
var count = d.ToString().TakeWhile(c => c != sep).Count();
3
w.b

Donc, j'ai rencontré cela auparavant et l'ai résolu avec ce code:

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

SqlDecimal fait partie du System.Data.SqlTypes espace de noms. "Précision" est le nombre total de chiffres significatifs, tandis que "Echelle" est le nombre de chiffres après la virgule décimale.

Maintenant, je sais qu'une objection à cette voie est que SqlDecimal fait partie du code spécifique à SQL Server. C'est un point valide, mais je voudrais également souligner qu'il fait partie du framework .NET lui-même, et qu'il existe depuis au moins la version 1.1, il semble donc qu'il serait toujours applicable quel que soit le code qui l'entoure. .

J'ai regardé sous le capot avec un décompilateur ( dotPeek de JetBrains dans ce cas), pour voir si peut-être le code de calcul de la précision et de l'échelle pouvait être facilement extrait et simplement utilisé, sans tirer _ SqlDecimal. Le code pour calculer l'échelle est très simple, mais la méthode pour calculer la précision n'est pas triviale, donc si c'était moi, je passerais simplement par SqlDecimal.

3
nateirvin

Cette réponse est à peu près tirée de Calculer la précision et l'échelle System.Decimal mais avec un changement mineur pour s'adapter à la question posée.

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

Aussi, si vous voulez le faire sans utiliser la classe SqlDecimal, consultez la réponse de Jon Skeet pour la même question.

3
Robert

Vous pouvez utiliser la fonction ToString avec un format personnalisé.

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

Le # spécifiez que vous ne voulez que les caractères avant le .

Le System.Globalization.CultureInfo.InvariantCulture assurez-vous que vous ne recevrez aucun formatage de l'option Région.

3
Stephan

La façon mathématique de faire cela (et probablement la plus rapide) est d'obtenir un logarytm de base 10 d'une valeur absolue de ce nombre et de l'arrondir.

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

TLDR toutes les autres réponses. J'ai écrit cela en PHP, et les mathématiques seraient les mêmes. (Si je connaissais C #, j'aurais écrit dans cette langue.)

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

Je ne doute pas qu'il y ait une meilleure façon, mais je voulais jeter mon $ .02

MODIFIER:

J'ai php-ized le code que j'ai mentionné dans mes commentaires, mais j'ai supprimé la conversion 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);
2
TecBrat

Utilisez modulo, je ne suis pas un programmeur C #, mais je suis sûr que cette solution fonctionne:

double i = 1;
int numberOfDecimals = 0;


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

return numberOfDecimals;
2
max890

Vous pouvez le faire en arrondissant le nombre, puis en obtenant la longueur du nouveau numéro. Vous pouvez le faire comme ceci:

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

Si vous traitez les zéros ou le manque de zéros comme 1 chiffre, c'est OK. Si vous voulez que zéro retourne zéro ou l'absence de zéro pour retourner zéro, alors il y a quelques cas Edge à résoudre qui ne devraient pas être trop difficiles à ajouter. En outre, devrait valeur absolue pour gérer les nombres négatifs. Ajout de ce cas de test également.

        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

Voici une solution quelque peu définitive, si vous trouvez un cas de test qui ne fonctionne pas, faites-le moi savoir. Il doit renvoyer 3,0,0,0,3 pour les cas de test fournis.

        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;
        }
1
Jon Raynor

Voici ma version optimisée du code inspiré de la réponse de Gray:

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

Si vous ne voulez pas que Math.Abs ​​soit appelé à l'intérieur de cette fonction, assurez-vous de l'utiliser en dehors de la fonction sur le paramètre avant d'appeler GetNumOfDigits.

J'ai décidé de supprimer les autres codes pour réduire l'encombrement dans ma réponse, même s'ils m'ont aidé à arriver à ce point ...

Si des améliorations sont nécessaires, faites-le moi savoir et je le mettrai à jour :).

1
John Odom

Ce serait la solution 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
Vinay Wadhwa

Afin d'obtenir une réponse précise et culturellement agnostique, je fais ce qui suit:

  1. Utilisez System.Numerics.BigInteger, Dont le constructeur accepte une décimale et ne semble pour produire des erreurs d'arrondi.
  2. Utilisez BigInteger.Abs() pour supprimer tout signe.
  3. Utilisez BigInteger.ToString() avec le format "#" pour supprimer les séparateurs qui pourraient se produire.

Code

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

Je n'ai pas testé cela mais je le garderais simple et je ferais:

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.

Cela ne gère pas les nombres négatifs. Si vous vous souciez de ceux-ci, envisagez de prendre la valeur absolue. De plus, si vous voulez que 0 avant la décimale ne soit pas compté, vous pouvez utiliser une simple instruction if pour le vérifier.

0
rex

Algorithme:

  • Convertir |decimal| à String.
  • Si "." existe dans la décimale, coupez-la avant, sinon considérez le nombre entier.
  • Retourne la longueur de la chaîne.

Exemple:

3.14 --> 3.14 --> "3.14" --> "3.14".Substring(0,1) --> "3".Length --> 1

-1024 --> 1024 --> "1024" --> IndexOf(".") = -1 --> "1024" --> 4

Code:

static int getNumOfDigits (decimal num)
{
    string d = Math.Abs(num).ToString();

    if (d.IndexOf(".") > -1)
    {
        d = d.Substring(0, d.IndexOf("."));
    }

    return d.Length;
}
0
Khaled.K

Les autres solutions perdront des chiffres si le nombre est trop grand.

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