Encuentre el número de lugares decimales en valor decimal independientemente de la cultura

91

Me pregunto si hay una forma concisa y precisa de extraer el número de lugares decimales en un valor decimal (como un int) que sea seguro de usar en diferentes informaciones culturales.

Por ejemplo:
19.0 debería devolver 1,
27.5999 debería devolver 4,
19.12 debería devolver 2,
etc.

Escribí una consulta que dividió una cadena en un período para encontrar lugares decimales:

int priceDecimalPlaces = price.ToString().Split('.').Count() > 1 
                  ? price.ToString().Split('.').ToList().ElementAt(1).Length 
                  : 0;

Pero se me ocurre que esto solo funcionará en regiones que usan el '.' como separador decimal y, por lo tanto, es muy frágil en diferentes sistemas.

Jesse Carter
fuente
Un decimal según el título de la pregunta
Jesse Carter
¿Qué tal un patrón de coincidencia antes de Split? Básicamente \ d + (\ D) \ d + donde \ D devuelve el separador (., Etc)
Anshul
7
Esta no es una pregunta cerrada como puede parecer a primera vista. Solicitar la 19.0devolución 1es un detalle de implementación con respecto al almacenamiento interno del valor 19.0. El hecho es que es perfectamente legítimo que el programa almacene esto como 190×10⁻¹o 1900×10⁻²o 19000×10⁻³. Todos esos son iguales. El hecho de que use la primera representación cuando se le da un valor de 19.0My esto se expone cuando se usa ToStringsin un especificador de formato es solo una coincidencia, y algo feliz. Excepto que no es feliz cuando la gente confía en el exponente en los casos en que no debería.
ErikE
Si quieres un tipo que puede llevar a "número de decimales utilizado" cuando se crea, por lo que se puede distinguir de manera fiable 19Ma partir 19.0Mde 19.00M, tendrá que crear una nueva clase que agrupa el valor subyacente como una propiedad y el número de lugares decimales como otra propiedad.
ErikE
1
¿A pesar de que la clase Decimal puede "distinguir" 19 m, de 19,0 m desde 19,00 m? Los dígitos significativos son como uno de sus principales casos de uso. ¿Qué es 19,0 m * 1,0 m? Parece estar diciendo 19,00 m, aunque quizás los desarrolladores de C # estén haciendo mal las matemáticas: ¿P? Nuevamente, los dígitos significativos son algo real. Si no le gustan los dígitos significativos, probablemente no debería usar la clase Decimal.
Nicholi

Respuestas:

168

Solía manera de Joe para resolver este problema :)

decimal argument = 123.456m;
int count = BitConverter.GetBytes(decimal.GetBits(argument)[3])[2];
LEGION_quema
fuente
6
Después de analizar esto con más detalle y verlo en acción, lo estoy marcando como la respuesta porque, en mi opinión, este es el método más conciso y elegante para devolver lugares decimales que he visto aquí. Volvería a hacer +1 si pudiera: D
Jesse Carter
9
decimalmantiene el recuento dígito después del coma, es por eso que encuentra este "problema", tiene que convertir decimal a doble y a decimal nuevamente para corregirlo: BitConverter.GetBytes (decimal.GetBits ((decimal) (argumento doble)) [3]) [ 2];
burning_LEGION
3
Esto no funcionó para mí. El valor que proviene de SQL es 21,17 y dice 4 dígitos. El tipo de datos se define como DECIMAL (12,4), así que quizás sea eso (usando Entity Framework).
PeterX
11
@Nicholi: no, esto es excepcionalmente malo porque el método se basa en la ubicación de los bits subyacentes del decimal , algo que tiene muchas formas de representar el mismo número . No probarías una clase en función del estado de sus campos privados, ¿verdad?
m.edmondson
14
No estoy seguro de qué se supone que sea elegante o agradable en esto. Esto es tan confuso como parece. Quién sabe si incluso funciona en todos los casos. Imposible estar seguro.
usr
24

Dado que ninguna de las respuestas proporcionadas fue lo suficientemente buena para el número mágico "-0.01f" convertido a decimal ... es decir GetDecimal((decimal)-0.01f);
, solo puedo asumir que un colosal virus de pedo mental atacó a todos hace 3 años :)
Aquí está lo que parece ser un implementación a este malvado y monstruoso problema, el muy complicado problema de contar los lugares decimales después del punto - sin cadenas, sin culturas, sin necesidad de contar los bits y sin necesidad de leer foros de matemáticas ... simplemente matemáticas de tercer grado.

public static class MathDecimals
{
    public static int GetDecimalPlaces(decimal n)
    {
        n = Math.Abs(n); //make sure it is positive.
        n -= (int)n;     //remove the integer part of the number.
        var decimalPlaces = 0;
        while (n > 0)
        {
            decimalPlaces++;
            n *= 10;
            n -= (int)n;
        }
        return decimalPlaces;
    }
}

private static void Main(string[] args)
{
    Console.WriteLine(1/3m); //this is 0.3333333333333333333333333333
    Console.WriteLine(1/3f); //this is 0.3333333

    Console.WriteLine(MathDecimals.GetDecimalPlaces(0.0m));                  //0
    Console.WriteLine(MathDecimals.GetDecimalPlaces(1/3m));                  //28
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)(1 / 3f)));     //7
    Console.WriteLine(MathDecimals.GetDecimalPlaces(-1.123m));               //3
    Console.WriteLine(MathDecimals.GetDecimalPlaces(43.12345m));             //5
    Console.WriteLine(MathDecimals.GetDecimalPlaces(0));                     //0
    Console.WriteLine(MathDecimals.GetDecimalPlaces(0.01m));                 //2
    Console.WriteLine(MathDecimals.GetDecimalPlaces(-0.001m));               //3
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)-0.00000001f)); //8
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)0.0001234f));   //7
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)0.01f));        //2
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)-0.01f));       //2
}
GY
fuente
6
Su solución fallará para varios casos que contienen ceros finales y los dígitos son SIGNIFICATIVOS. 0,01 m * 2,0 m = 0,020 m. Debe ser de 3 dígitos, su método devuelve 2. Parece que no entiende correctamente lo que sucede cuando lanza 0.01f a Decimal. Los puntos flotantes no son intrínsecamente precisos, por lo que el valor binario real almacenado para 0.01f no es exacto. Cuando lanza a Decimal (una notación numérica muy estructurada), es posible que no obtenga 0.01m (en realidad obtiene 0.010m). La solución GetBits es realmente correcta para obtener el número de dígitos de un decimal. Cómo se convierte a decimal es clave.
Nicholi
2
@Nicholi 0.020m es igual a 0.02m. Los ceros finales no son significativos. OP está preguntando "independientemente de la cultura" en el título e incluso explica más específicamente "... eso será seguro de usar en diferentes informaciones culturales ..." - por lo tanto, creo que mi respuesta sigue siendo incluso más válida que otras.
GY
6
OP dijo específicamente: "19.0 debería devolver 1". Este código falla en ese caso.
daniloquio
9
tal vez esto no es lo que quería el OP, pero esta respuesta se adapta mejor a mis necesidades que la respuesta principal de esta pregunta
Arsen Zahray
2
Las dos primeras líneas deben reemplazarse con n = n % 1; if (n < 0) n = -n;porque un valor mayor que int.MaxValuecausará un OverflowException, por ejemplo 2147483648.12345.
Loathing
23

Probablemente usaría la solución en la respuesta de @ fixagon .

Sin embargo, aunque la estructura Decimal no tiene un método para obtener el número de decimales, puede llamar a Decimal.GetBits para extraer la representación binaria, luego usar el valor entero y la escala para calcular el número de decimales.

Esto probablemente sería más rápido que formatear como una cadena, aunque tendría que estar procesando una gran cantidad de decimales para notar la diferencia.

Dejaré la implementación como ejercicio.

Joe
fuente
1
Gracias @Joe, esa es una forma realmente elegante de abordarlo. Dependiendo de cómo se sienta mi jefe sobre el uso de la otra solución, echaré un vistazo a la implementación de su idea. Definitivamente sería un ejercicio divertido :)
Jesse Carter
17

Una de las mejores soluciones para encontrar el número de dígitos después del punto decimal se muestra en la publicación de burning_LEGION .

Aquí estoy usando partes de un artículo del foro de STSdb: Número de dígitos después del punto decimal .

En MSDN podemos leer la siguiente explicación:

"Un número decimal es un valor de coma flotante que consta de un signo, un valor numérico en el que cada dígito del valor varía de 0 a 9 y un factor de escala que indica la posición de un punto decimal flotante que separa la integral y la fraccional. partes del valor numérico ".

Y también:

"La representación binaria de un valor decimal consta de un signo de 1 bit, un número entero de 96 bits y un factor de escala que se utiliza para dividir el entero de 96 bits y especificar qué parte de él es una fracción decimal. El factor de escala es implícitamente el número 10, elevado a un exponente que va de 0 a 28 ".

A nivel interno, el valor decimal está representado por cuatro valores enteros.

Representación interna decimal

Hay una función GetBits disponible públicamente para obtener la representación interna. La función devuelve una matriz int []:

[__DynamicallyInvokable] 
public static int[] GetBits(decimal d)
{
    return new int[] { d.lo, d.mid, d.hi, d.flags };
}

El cuarto elemento de la matriz devuelta contiene un factor de escala y un signo. Y como dice el MSDN, el factor de escala es implícitamente el número 10, elevado a un exponente que va de 0 a 28. Esto es exactamente lo que necesitamos.

Por lo tanto, basándonos en todas las investigaciones anteriores, podemos construir nuestro método:

private const int SIGN_MASK = ~Int32.MinValue;

public static int GetDigits4(decimal value)
{
    return (Decimal.GetBits(value)[3] & SIGN_MASK) >> 16;
}

Aquí se usa un SIGN_MASK para ignorar el signo. Después de lógica y también hemos desplazado el resultado con 16 bits a la derecha para recibir el factor de escala real. Este valor, finalmente, indica el número de dígitos después del punto decimal.

Tenga en cuenta que aquí MSDN también dice que el factor de escala también conserva los ceros finales en un número decimal. Los ceros finales no afectan el valor de un número decimal en operaciones aritméticas o de comparación. Sin embargo, el método ToString puede revelar ceros finales si se aplica una cadena de formato adecuada.

Esta solución parece la mejor, pero espere, hay más. Al acceder a métodos privados en C # , podemos usar expresiones para crear un acceso directo al campo de banderas y evitar construir la matriz int:

public delegate int GetDigitsDelegate(ref Decimal value);

public class DecimalHelper
{
    public static readonly DecimalHelper Instance = new DecimalHelper();

    public readonly GetDigitsDelegate GetDigits;
    public readonly Expression<GetDigitsDelegate> GetDigitsLambda;

    public DecimalHelper()
    {
        GetDigitsLambda = CreateGetDigitsMethod();
        GetDigits = GetDigitsLambda.Compile();
    }

    private Expression<GetDigitsDelegate> CreateGetDigitsMethod()
    {
        var value = Expression.Parameter(typeof(Decimal).MakeByRefType(), "value");

        var digits = Expression.RightShift(
            Expression.And(Expression.Field(value, "flags"), Expression.Constant(~Int32.MinValue, typeof(int))), 
            Expression.Constant(16, typeof(int)));

        //return (value.flags & ~Int32.MinValue) >> 16

        return Expression.Lambda<GetDigitsDelegate>(digits, value);
    }
}

Este código compilado se asigna al campo GetDigits. Tenga en cuenta que la función recibe el valor decimal como ref, por lo que no se realiza ninguna copia real, solo una referencia al valor. Usar la función GetDigits de DecimalHelper es fácil:

decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);

Este es el método más rápido posible para obtener el número de dígitos después del punto decimal para valores decimales.

Kristiyan Dimitrov
fuente
3
decimal r = (decimal) -0.01f; y la solución falla. (en todas las respuestas que vi en esta página ...) :)
GY
5
NOTA: Acerca de todo (Decimal) 0.01f, estás lanzando un punto flotante, inherentemente NO PRECISO, a algo muy estructurado como un Decimal. Eche un vistazo a la salida de Console.WriteLine ((Decimal) 0.01f). El decimal que se forma en el reparto REALMENTE tiene 3 dígitos, por eso todas las soluciones proporcionadas dicen 3 en lugar de 2. Todo funciona como se esperaba, el "problema" es que espera que los valores de coma flotante sean exactos. No son.
Nicholi
@Nicholi Tu punto falla cuando te das cuenta de que 0.01y 0.010son exactamente números iguales . Además, la idea de que un tipo de datos numérico tiene algún tipo de semántica de "número de dígitos utilizados" en la que se puede confiar es completamente errónea (no debe confundirse con "número de dígitos permitido". No confunda presentación (la visualización de el valor de un número en una base particular, por ejemplo, la expansión decimal del valor indicado por la expansión binaria 111) con el valor subyacente. Para reiterar, los números no son dígitos, ni están formados por dígitos .
ErikE
6
Son equivalentes en valor, pero no en dígitos significativos. Que es un gran caso de uso de la clase Decimal. Si le preguntara cuántos dígitos hay en el literal 0.010m, ¿diría que solo 2? ¿A pesar de que los puntajes de profesores de matemáticas / ciencias de todo el mundo le dirían que el 0 final es significativo? El problema al que nos referimos se manifiesta al pasar de puntos flotantes a Decimal. No es el uso de GetBits en sí, que funciona exactamente como está documentado. Si no le importan los dígitos significativos, entonces sí, tiene un problema y probablemente no debería usar la clase Decimal en primer lugar.
Nicholi
1
@theberserker Por lo que recuerdo, no hubo trampa: debería funcionar en ambos sentidos.
Kristiyan Dimitrov
13

Confiar en la representación interna de decimales no está bien.

Qué tal esto:

    int CountDecimalDigits(decimal n)
    {
        return n.ToString(System.Globalization.CultureInfo.InvariantCulture)
                //.TrimEnd('0') uncomment if you don't want to count trailing zeroes
                .SkipWhile(c => c != '.')
                .Skip(1)
                .Count();
    }
Clemente
fuente
11

puedes usar InvariantCulture

string priceSameInAllCultures = price.ToString(System.Globalization.CultureInfo.InvariantCulture);

otra posibilidad sería hacer algo así:

private int GetDecimals(decimal d, int i = 0)
{
    decimal multiplied = (decimal)((double)d * Math.Pow(10, i));
    if (Math.Round(multiplied) == multiplied)
        return i;
    return GetDecimals(d, i+1);
}
arreglar
fuente
¿Cómo me ayuda esto a encontrar el número de lugares decimales en el decimal? No tengo ningún problema en convertir el decimal en una cadena que sea buena en cualquier cultura. Según la pregunta, estoy tratando de encontrar la cantidad de lugares decimales que estaban en el decimal
Jesse Carter
@JesseCarter: Significa que siempre puedes dividir ..
Austin Salonen
@AustinSalonen ¿De verdad? No sabía que el uso de InvariantCulture impondría el uso de un punto como separador decimal
Jesse Carter
como hiciste antes, siempre se emitirá el precio para encadenar con un. como separador decimal. pero no es la forma más elegante en mi opinión ...
fixagon
@JesseCarter: NumberFormatInfo.NumberDecimalSeparator
Austin Salonen
8

La mayoría de la gente aquí parece no saber que decimal considera que los ceros finales son importantes para el almacenamiento y la impresión.

Por lo tanto, 0,1 m, 0,10 my 0,100 m pueden compararse como iguales, se almacenan de manera diferente (como valor / escala 1/1, 10/2 y 100/3, respectivamente) y se imprimirán como 0,1, 0,10 y 0,100, respectivamente. , por ToString().

Como tal, las soluciones que informan "una precisión demasiado alta" en realidad informan la precisión correcta , en decimallos términos.

Además, las soluciones basadas en matemáticas (como multiplicar por potencias de 10) probablemente serán muy lentas (el decimal es ~ 40 veces más lento que el doble para la aritmética, y tampoco desea mezclar el punto flotante porque es probable que introduzca imprecisión ). Del mismo modo, la fundición a into longcomo un medio de truncar es propenso a errores ( decimaltiene una gama mucho mayor que cualquiera de los que - que está basado en torno a un número entero de 96 bits).

Si bien no es elegante como tal, lo siguiente probablemente será una de las formas más rápidas de obtener la precisión (cuando se define como "lugares decimales excluyendo ceros finales"):

public static int PrecisionOf(decimal d) {
  var text = d.ToString(System.Globalization.CultureInfo.InvariantCulture).TrimEnd('0');
  var decpoint = text.IndexOf('.');
  if (decpoint < 0)
    return 0;
  return text.Length - decpoint - 1;
}

La cultura invariante garantiza un '.' como punto decimal, los ceros finales se recortan, y luego es solo una cuestión de ver cuántas posiciones quedan después del punto decimal (si es que hay uno).

Editar: cambió el tipo de retorno a int

Zastai
fuente
1
@mvmorten No estoy seguro de por qué sintió que era necesario cambiar el tipo de retorno a int; byte representa con mayor precisión el valor devuelto: sin signo y rango pequeño (0-29, en la práctica).
Zastai
1
Estoy de acuerdo en que las soluciones iterativas y basadas en cálculos son lentas (además de no tener en cuenta los ceros finales). Sin embargo, asignar una cadena para esto y operar en eso tampoco es lo más eficaz, especialmente en contextos de rendimiento crítico y con un GC lento. Acceder a la báscula a través de la lógica del puntero es mucho más rápido y sin asignación.
Martin Tilo Schmitz
Sí, obtener la escala se puede hacer de manera mucho más eficiente, pero eso incluiría los ceros finales. Y eliminarlos requiere hacer aritmética en la parte entera.
Zastai
6

Y aquí hay otra forma, use el tipo SqlDecimal que tiene una propiedad de escala con el recuento de los dígitos a la derecha del decimal. Transmita su valor decimal a SqlDecimal y luego acceda a Scale.

((SqlDecimal)(decimal)yourValue).Scale
RitchieD
fuente
1
Al observar el código de referencia de Microsoft , la conversión a SqlDecimal utiliza internamente el, GetBytespor lo que asigna la matriz de bytes en lugar de acceder a los bytes en un contexto inseguro. Incluso hay una nota y un código comentado en el código de referencia, indicando eso y cómo podrían hacerlo en su lugar. Por qué no lo hicieron es un misterio para mí. Me mantendría alejado de esto y accedería a los bits de escala directamente en lugar de esconder el GC Alloc en este elenco, ya que no es muy obvio lo que hace debajo del capó.
Martin Tilo Schmitz
4

Hasta ahora, casi todas las soluciones enumeradas están asignando memoria GC, que es en gran medida la forma de C # para hacer las cosas, pero está lejos de ser ideal en entornos de rendimiento crítico. (Los que no asignan utilizan bucles y tampoco tienen en cuenta los ceros finales).

Entonces, para evitar GC Allocs, puede acceder a los bits de escala en un contexto inseguro. Eso puede sonar frágil, pero según la fuente de referencia de Microsoft , el diseño de estructura de decimal es secuencial e incluso tiene un comentario allí, no para cambiar el orden de los campos:

    // NOTE: Do not change the order in which these fields are declared. The
    // native methods in this class rely on this particular order.
    private int flags;
    private int hi;
    private int lo;
    private int mid;

Como puede ver, el primer int aquí es el campo flags. Por la documentación y como se menciona en otros comentarios aquí, sabemos que solo los bits de 16-24 codifican la escala y que debemos evitar el bit 31 que codifica el signo. Dado que int es del tamaño de 4 bytes, podemos hacer esto con seguridad:

internal static class DecimalExtensions
{
  public static byte GetScale(this decimal value)
  {
    unsafe
    {
      byte* v = (byte*)&value;
      return v[2];
    }
  }
}

Esta debería ser la solución de mayor rendimiento, ya que no hay ninguna asignación GC de la matriz de bytes o conversiones ToString. Lo probé contra .Net 4.xy .Net 3.5 en Unity 2019.1. Si hay alguna versión en la que esto falle, hágamelo saber.

Editar:

Gracias a @Zastai por recordarme la posibilidad de usar un diseño de estructura explícito para lograr prácticamente la misma lógica de puntero fuera del código inseguro:

[StructLayout(LayoutKind.Explicit)]
public struct DecimalHelper
{
    const byte k_SignBit = 1 << 7;

    [FieldOffset(0)]
    public decimal Value;

    [FieldOffset(0)]
    public readonly uint Flags;
    [FieldOffset(0)]
    public readonly ushort Reserved;
    [FieldOffset(2)]
    byte m_Scale;
    public byte Scale
    {
        get
        {
            return m_Scale;
        }
        set
        {
            if(value > 28)
                throw new System.ArgumentOutOfRangeException("value", "Scale can't be bigger than 28!")
            m_Scale = value;
        }
    }
    [FieldOffset(3)]
    byte m_SignByte;
    public int Sign
    {
        get
        {
            return m_SignByte > 0 ? -1 : 1;
        }
    }
    public bool Positive
    {
        get
        {
            return (m_SignByte & k_SignBit) > 0 ;
        }
        set
        {
            m_SignByte = value ? (byte)0 : k_SignBit;
        }
    }
    [FieldOffset(4)]
    public uint Hi;
    [FieldOffset(8)]
    public uint Lo;
    [FieldOffset(12)]
    public uint Mid;

    public DecimalHelper(decimal value) : this()
    {
        Value = value;
    }

    public static implicit operator DecimalHelper(decimal value)
    {
        return new DecimalHelper(value);
    }

    public static implicit operator decimal(DecimalHelper value)
    {
        return value.Value;
    }
}

Para resolver el problema original, se podía tira de lejos todos los campos aparte Valuey Scaleaunque tal vez podría ser útil para alguien que tiene a todos.

Martin Tilo Schmitz
fuente
1
También puede evitar el código inseguro codificando su propia estructura con un diseño explícito: coloque un decimal en la posición 0, luego bytes / ints en las ubicaciones apropiadas. Algo como:[StructLayout(LayoutKind.Explicit)] public struct DecimalHelper { [FieldOffset(0)] public decimal Value; [FieldOffset(0)] public uint Flags; [FieldOffset(0)] public ushort Reserved; [FieldOffset(2)] public byte Scale; [FieldOffset(3)] public DecimalSign Sign; [FieldOffset(4)] public uint ValuePart1; [FieldOffset(8)] public ulong ValuePart2; }
Zastai
Gracias @Zastai, buen punto. También he incorporado ese enfoque. :)
Martin Tilo Schmitz
1
Una cosa a tener en cuenta: establecer la escala fuera del rango 0-28 causa roturas. ToString () tiende a funcionar, pero la aritmética falla.
Zastai
Gracias de nuevo @Zastai, agregué un cheque para eso :)
Martin Tilo Schmitz
Otra cosa: varias personas aquí no querían tener en cuenta los ceros decimales finales. Si define a, const decimal Foo = 1.0000000000000000000000000000m;entonces dividir un decimal por eso lo reescalará a la escala más baja posible (es decir, ya no se incluirán los ceros decimales finales). Sin embargo, no he comparado esto para ver si es o no más rápido que el enfoque basado en cadenas que sugerí en otro lugar.
Zastai
2

Ayer escribí un pequeño método conciso que también devuelve el número de lugares decimales sin tener que depender de divisiones o culturas de cadenas, lo cual es ideal:

public int GetDecimalPlaces(decimal decimalNumber) { // 
try {
    // PRESERVE:BEGIN
        int decimalPlaces = 1;
        decimal powers = 10.0m;
        if (decimalNumber > 0.0m) {
            while ((decimalNumber * powers) % 1 != 0.0m) {
                powers *= 10.0m;
                ++decimalPlaces;
            }
        }
return decimalPlaces;
Jesse Carter
fuente
@ fix-like-codings similar a su segunda respuesta, aunque para algo como esto prefiero el enfoque iterativo en lugar de usar la recursividad
Jesse Carter
El post original establece que: 19.0 should return 1. Esta solución siempre asumirá una cantidad mínima de 1 lugar decimal e ignorará los ceros finales. decimal puede tenerlos ya que usa un factor de escala. Se puede acceder al factor de escala como en los bytes 16-24 del elemento con índice 3 en la matriz obtenida Decimal.GetBytes()o mediante la lógica de puntero.
Martin Tilo Schmitz
2

Estoy usando algo muy similar a la respuesta de Clement:

private int GetSignificantDecimalPlaces(decimal number, bool trimTrailingZeros = true)
{
  string stemp = Convert.ToString(number);

  if (trimTrailingZeros)
    stemp = stemp.TrimEnd('0');

  return stemp.Length - 1 - stemp.IndexOf(
         Application.CurrentCulture.NumberFormat.NumberDecimalSeparator);
}

Recuerde usar System.Windows.Forms para obtener acceso a Application.CurrentCulture

RooiWillie
fuente
1

Puedes probar:

int priceDecimalPlaces =
        price.ToString(System.Globalization.CultureInfo.InvariantCulture)
              .Split('.')[1].Length;
NicoRiff
fuente
7
¿No fallaría esto cuando el decimal es un número entero? [1]
Silvermind
1

Utilizo el siguiente mecanismo en mi código

  public static int GetDecimalLength(string tempValue)
    {
        int decimalLength = 0;
        if (tempValue.Contains('.') || tempValue.Contains(','))
        {
            char[] separator = new char[] { '.', ',' };
            string[] tempstring = tempValue.Split(separator);

            decimalLength = tempstring[1].Length;
        }
        return decimalLength;
    }

entrada decimal = 3.376; var instring = input.ToString ();

llamar a GetDecimalLength (instring)

Srikanth
fuente
1
Esto no funciona para mí, ya que la representación ToString () del valor decmial agrega "00" al final de mis datos. Estoy usando un tipo de datos Decimal (12,4) de SQL Server.
PeterX
¿Puede convertir sus datos en c # tipo decimal y probar la solución? Para mí, cuando uso Tostring () en el valor decimal c #, nunca veo un "00".
Srikanth
1

Usando la recursividad puedes hacer:

private int GetDecimals(decimal n, int decimals = 0)  
{  
    return n % 1 != 0 ? GetDecimals(n * 10, decimals + 1) : decimals;  
}
Marte
fuente
El post original establece que: 19.0 should return 1. Esta solución ignorará los ceros finales. decimal puede tenerlos ya que usa un factor de escala. Se puede acceder al factor de escala como en los bytes 16-24 del elemento con índice 3 en la Decimal.GetBytes()matriz o utilizando la lógica de puntero.
Martin Tilo Schmitz
1
string number = "123.456789"; // Convert to string
int length = number.Substring(number.IndexOf(".") + 1).Length;  // 6
Eva Chang
fuente
0

Sugiero usar este método:

    public static int GetNumberOfDecimalPlaces(decimal value, int maxNumber)
    {
        if (maxNumber == 0)
            return 0;

        if (maxNumber > 28)
            maxNumber = 28;

        bool isEqual = false;
        int placeCount = maxNumber;
        while (placeCount > 0)
        {
            decimal vl = Math.Round(value, placeCount - 1);
            decimal vh = Math.Round(value, placeCount);
            isEqual = (vl == vh);

            if (isEqual == false)
                break;

            placeCount--;
        }
        return Math.Min(placeCount, maxNumber); 
    }
Veysel Ozdemir
fuente
0

Como método de extensión decimal que tiene en cuenta:

  • Culturas diferentes
  • Números enteros
  • Números negativos
  • Fijar ceros al final del lugar decimal (por ejemplo, 1.2300M devolverá 2, no 4)
public static class DecimalExtensions
{
    public static int GetNumberDecimalPlaces(this decimal source)
    {
        var parts = source.ToString(CultureInfo.InvariantCulture).Split('.');

        if (parts.Length < 2)
            return 0;

        return parts[1].TrimEnd('0').Length;
    }
}
bytedev
fuente