J'ai une variable de type decimal
et je veux vérifier le nombre de chiffres avant la virgule décimale. Que devrais-je faire? Par exemple, 467.45
devrait renvoyer 3
.
Réponses
Trop de publicités?Solution sans avoir à les convertir string
(ce qui peut être dangereux dans le 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 valeurs, par exemple: 999999999999998
, 999999999999999
, 9999999999999939
...
De toute évidence, les opérations mathématiques avec double
ne sont pas assez précis pour cette tâche.
Lors de la recherche des valeurs erronées j'ai tendance à utiliser string
solutions de rechange proposées dans cette rubrique. Comme pour moi, c'est la preuve qu'ils sont plus fiables et faciles à utiliser (mais il faut être conscient des cultures). Boucle de solutions peut être plus rapide.
Merci aux commentateurs, honte sur moi, leçon pour vous.
Au lieu de la conversion en chaîne de caractères, vous pouvez aussi diviser le nombre par 10 jusqu'à ce qu'il est égal à 0. Intéressant, c'est que les opérations mathématiques sur les décimales sont beaucoup plus lent que la conversion du décimal à une chaîne et le retour de la longueur (voir les repères ci-dessous).
Cette solution ne permet pas d'utiliser les Mathématiques-méthodes qui prennent un double comme entrée; de sorte que toutes les opérations sont effectuées sur des décimales et pas de casting 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;
}
}
La sortie est - 29
. Pour exécuter cet exemple, visitez ce lien.
Remarque: 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 décimale-opérations: 3ms
- BigInt (voir la réponse de @Heinzi): 2ms
Aussi à l'aide de nombres aléatoires au lieu de toujours la même valeur (pour éviter la mise en cache de la virgule à la conversion de chaîne de caractères) a montré que la chaîne méthodes sont beaucoup plus rapides.
Je voudrais essayer 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 nombres décimaux négatifs, vous feriez mieux d'utiliser ceci:
var myDecimal = 467.45m;
Math.Truncate(Math.Abs(myDecimal)).ToString(CultureInfo.InvariantCulture).Length
decimal d = 467.45M;
int i = (int)d;
Console.WriteLine(i.ToString(CultureInfo.InvariantCulture).Length); //3
En tant que méthode;
public static int GetDigitsLength(decimal d)
{
int i = int(d);
return i.ToString(CultureInfo.InvariantCulture).Length;
}
Remarque: bien sûr, vous devriez d'abord vérifier votre décimales est plus grand que Int32.MaxValue
ou pas. Parce que si c'est, vous obtenez un OverflowException
.
Dans ce cas, l'aide d' long
au lieu de int
pouvez une meilleure approche. Cependant , même un long
(System.Int64
) n'est pas assez grande pour contenir tous les possibles decimal
de la valeur.
Comme Rawling mentionné, votre partie peut tenir le séparateur des milliers et mon code sera brisé dans un tel cas. Parce que de cette manière, il ignore totalement mon numéro contient NumberFormatInfo.NumberGroupSeparator
ou pas.
C'est pourquoi obtenir seulement des nombres est une meilleure approche. Comme;
i.ToString().Where(c => Char.IsDigit(c)).ToArray()