Obtenir le nombre de chiffres avant le point décimal

j'ai une variable de "151900920 de type" et je veux vérifier le nombre de chiffres avant le point décimal. Que dois-je faire? Par exemple, 467.45 doit renvoyer 3 .

61
demandé sur mavis 2014-02-04 11:51:55

26 réponses

Solution sans conversion en string (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);
}

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

mise à JOUR

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

évidemment, le les opérations mathématiques avec double ne sont pas assez précises pour cette tâche.

tout en cherchant des valeurs erronées, j'ai tendance à utiliser des alternatives basées sur string proposées dans ce sujet. Quant à 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.

80
répondu astef 2014-02-06 07:24:59

au lieu de convertir en chaîne, vous pouvez aussi 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 la conversion de la décimale en chaîne et le retour de la longueur (voir les repères ci-dessous).

Cette solution n'utilise pas les méthodes mathématiques qui prennent un double comme entrée; de sorte que toutes les opérations sont effectuées sur les décimales et aucun casting n'est impliqué.

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

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


note de Côté: certains tests montrent des résultats surprenants (10k pistes):

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

aussi en utilisant des 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 de caractères) a montré que les méthodes basées sur les chaînes de caractères sont beaucoup plus rapides.

32
répondu Markus 2017-05-23 12:17:25

je voudrais essayer ceci:

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

si vous voulez être sûr de ne pas avoir de résultats bizarres 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
24
répondu Kevin Brechbühl 2014-02-04 11:14:21

au lieu de mouler int pour vous assurer que vous pouvez aussi manipuler de grands nombres (par exemple decimal.MaxValue ) je préférerais ce qui suit:

Math.Truncate ( Math.Abs ( decValue ) ).ToString( "####" ).Length
13
répondu Stephan Bauer 2014-02-04 08:07:22

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
}
7
répondu Gray 2014-02-04 17:38:18
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;
}

Note : bien sûr, vous devez d'abord vérifier que vos décimales sont plus grandes que Int32.MaxValue ou non. Parce que si c'est le cas, OverflowException .

est un tel cas, en utilisant long au lieu de int peut une meilleure approche. cependant même un long ( System.Int64 ) n'est pas assez grande pour contenir toutes les valeurs decimal possibles.

comme Rawling mentionné , votre partie complète 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 pas.

c'est pourquoi obtenir des nombres seulement est une meilleure approche. Comme;

i.ToString().Where(c => Char.IsDigit(c)).ToArray()
6
répondu Soner Gönül 2017-05-23 12:00:35

Math.Floor(Math.Log10((double)n) + 1); est le chemin à parcourir.

convertir en int est mauvais parce que 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 parce qu'il peut inclure des milliers de séparateurs.

5
répondu Nahum 2014-02-05 12:07:31

Si vous avez un penchant pour les petits nombres, vous pouvez utiliser quelque chose de plus simple comme ça.

il est divisé en deux méthodes, de sorte que la première méthode est plus petite et peut être inlined.

La Performance

est à peu près la même que la solution avec le Log10, mais sans les erreurs d'arrondi. La méthode utilisant Log10, est encore 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é en utilisant le modèle de feuillet 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
répondu GvS 2014-02-05 14:54:28
var sep = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
var count = d.ToString().TakeWhile(c => c != sep).Count();
5
répondu w.b 2014-05-04 08:04:27

cela fera si vous ne voulez vraiment pas utiliser la méthode Log (IMO qui est le meilleur moyen). Il s'agit de la façon la plus claire dont je peux penser à faire cela en utilisant ToString ():

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

ou alternativement, si vous ne voulez pas compter 0.123M comme ayant un chiffre:

Math.Abs(val).ToString("#", CultureInfo.InvariantCulture).Length
4
répondu Luaan 2014-02-04 11:14:17

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écifie que vous voulez seulement les caractères avant le .

le System.Globalization.CultureInfo.InvariantCulture assurez-vous que vous n'obtiendrez aucun formatage de L'Option Région.

4
répondu Stephan 2014-02-04 17:27:45

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

Math.Floor(Math.Log10(Math.Abs(val)) + 1);
3
répondu Kubuxu 2014-02-04 15:17:30

TLDR toutes les autres réponses. J'ai écrit ça en PHP, et le calcul serait le même. (Si j'avais su 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 un meilleur moyen, mais je voulais jeter dans mon $.02

EDIT:

j'ai php-isé 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);
3
répondu TecBrat 2014-02-04 15:45:13

donc, j'ai déjà vu ça, et je l'ai résolu avec ce code:

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

SqlDecimal fait partie de l'espace de noms System.Data.SqlTypes . "Précision" est le nombre total de chiffres significatifs, tout en "Échelle" est le nombre de chiffres après le point décimal.

maintenant, je sais qu'une objection à suivre cette route est que SqlDecimal fait partie du code spécifique au serveur SQL. C'est un point valable, mais je voudrais également souligner qu'il fait partie de la Le.net framework lui-même, et l'a été depuis au moins la version 1.1, il semble donc qu'il serait encore applicable, peu importe ce que le code autour de lui fait.

j'ai regardé sous le capot avec un décomposeur ( dotPeek de JetBrains dans ce cas), pour voir si peut-être le code pour calculer la précision et l'échelle pourrait être facilement extrait et juste utilisé, sans tirer dans SqlDecimal . Le code pour calculer l'échelle est très simple, mais la méthode pour calculer la précision est non-trivial, donc si c'était moi, je passerais par SqlDecimal .

3
répondu nateirvin 2014-02-05 06:10:06

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);
    }
}
2
répondu Vinay Wadhwa 2014-02-04 14:13:38

si vous traitez les zéros ou l'absence de zéros comme un nombre 1, C'est OK. Si vous voulez zéro retour zéro ou le manque de zéro retour zéro, alors il ya quelques cas de travail qui ne devrait pas être trop dur à ajouter. Aussi, devrait valeur absolue pour traiter les nombres négatifs. Ajouté que les cas de test.

        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 un peu définitive, si vous trouvez un cas d'essai qui ne fonctionne pas, faites-le moi savoir. Il doit retourner 3, 0, 0, 3 pour les cas d'essai 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;
        }
2
répondu Jon Raynor 2014-02-04 19:44:11

cette réponse est assez bien levée de système de calcul.Précision décimale et échelle mais avec un changement mineur pour correspondre à 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.

2
répondu Robert 2017-05-23 12:10:13

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

double i = 1;
int numberOfDecimals = 0;


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

return numberOfDecimals;
2
répondu max890 2014-02-05 14:35:43

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;
}
1
répondu hsun324 2014-02-04 08:06:27

voici ma version optimisée du code inspirée 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 le calcul.Abs d'être appelée à l'intérieur de cette fonction, assurez-vous d'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 le désordre dans ma réponse, même s'ils m'ont aidé à en arriver là...

S'il y a des améliorations nécessaires, alors laissez - moi savoir et je vais le mettre à jour :).

1
répondu John Odom 2014-02-04 21:09:43

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

  1. utiliser System.Numerics.BigInteger , dont le constructeur accepte une décimale et ne semble pas produire des erreurs d'arrondissement.
  2. Utiliser BigInteger.Abs() pour supprimer tout signe.
  3. utiliser BigInteger.ToString() avec le format " # " pour supprimer tout séparateur qui pourrait se produire.

Code

decimal num = 123213123.123123M;
int length = BigInteger.Abs((BigInteger)num).ToString("#").Length;
1
répondu Daniel Gimenez 2014-02-05 18:09:29

vous pouvez faire cela en arrondissant le nombre, puis en obtenant la longueur du nouveau nombre. Vous pourriez 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
répondu Jojodmo 2016-01-08 23:59:57

algorithme:

  • Convertissez |decimal| en chaîne.
  • si "." existent dans la décimale, coupez avant elle, autrement considérer le nombre entier.
  • Longueur de la chaîne de retour.

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
répondu Khaled.K 2014-02-05 07:27:18

Je n'ai pas testé cela, mais je voudrais garder simple et faire:

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.

ceci ne gère pas les nombres négatifs. Si vous vous souciez de ceux d'en envisager de prendre la valeur absolue. En outre, si vous voulez 0 avant la virgule à pas comptés, alors vous pouvez simplement utiliser une instruction if pour vérifier.

0
répondu rex 2014-02-05 16:31:58

simple:

string value = "467.45";
int count =  value.split('.')[0] == "0" ? 0 : value.split('.')[0].ToString().Replace("-","").Count();
0
répondu Sebastien H. 2014-02-06 11:21:25

utiliser:

var value=467.45;
var length=((int)value).ToString().Length
-1
répondu Shahrooz Jafari 2014-02-04 08:02:28