Truncar dos lugares decimales sin redondear

107

Digamos que tengo un valor de 3.4679 y quiero 3.46, ¿cómo puedo truncarlo a dos decimales sin redondear hacia arriba?

He intentado lo siguiente, pero los tres me dan 3,47:

void Main()
{
    Console.Write(Math.Round(3.4679, 2,MidpointRounding.ToEven));
    Console.Write(Math.Round(3.4679, 2,MidpointRounding.AwayFromZero));
    Console.Write(Math.Round(3.4679, 2));
}

Esto devuelve 3.46, pero parece sucio de alguna manera:

void Main()
{
    Console.Write(Math.Round(3.46799999999 -.005 , 2));
}

fuente

Respuestas:

151
value = Math.Truncate(100 * value) / 100;

Tenga en cuenta que fracciones como estas no se pueden representar con precisión en punto flotante.

Hans Passant
fuente
12
Use decimal para sus valores y esta respuesta funcionará. Es poco probable que funcione siempre en una representación de punto flotante.
driis
1
Eso me hace preguntarme si debería ser posible especificar la dirección de redondeo en literales de punto flotante. Hmmmm.
Steve314
Tiene que haber alguna forma de decirle al programador que calcular con el supuesto de que un número puede almacenar más de 308 dígitos es extremadamente inapropiado. El doble sólo puede almacenar 15. El desbordamiento es una característica importante aquí, se desbordó bastante mal.
Hans Passant
Lo siento, pensé que "valor" es decimal.
codificador nocturno
54

Sería más útil tener una función completa para el uso en el mundo real de truncar un decimal en C #. Esto podría convertirse en un método de extensión decimal bastante fácil si quisiera:

public decimal TruncateDecimal(decimal value, int precision)
{
    decimal step = (decimal)Math.Pow(10, precision);
    decimal tmp = Math.Truncate(step * value);
    return tmp / step;
}

Si necesita VB.NET, intente esto:

Function TruncateDecimal(value As Decimal, precision As Integer) As Decimal
    Dim stepper As Decimal = Math.Pow(10, precision)
    Dim tmp As Decimal = Math.Truncate(stepper * value)
    Return tmp / stepper
End Function

Entonces úsalo así:

decimal result = TruncateDecimal(0.275, 2);

o

Dim result As Decimal = TruncateDecimal(0.275, 2)
Corgalore
fuente
1
Esto se desbordará en grandes cantidades.
codificador nocturno
1
Para agregar al codificador nocturno, el hecho de que esté utilizando Int32 como intermediario en su función causará desbordamientos. Debe usar Int64 si realmente debe convertirlo en un Integer. La pregunta sería por qué querría incurrir en esa sobrecarga adicional de todos modos, ya que Truncate devuelve integrales decimales de todos modos. Simplemente haga algo como: paso decimal = (decimal) Math.Pow (10, precisión); return Math.Truncate (paso * valor) / paso;
Sarel Esterhuizen
Dejé caer el elenco a Integer. Los dejé líneas separadas para una mejor legibilidad y comprensión de cómo funciona la función.
Corgalore
27

Utilice el operador de módulo:

var fourPlaces = 0.5485M;
var twoPlaces = fourPlaces - (fourPlaces % 0.01M);

resultado: 0.54

Leonard Lewis
fuente
1
No entiendo (léase: no pasé el tiempo para verificar) todas estas otras soluciones sofisticadas, esto hace exactamente lo que estaba buscando. ¡Gracias!
Isaac Baker
Ejecutar esto en .Net Fiddle clicky produce 0.5400... La respuesta de D. Nesterov a continuación produjo lo esperado 0.54.
ttugates
Te das cuenta, @ttugates, de que 0,54 y 0,5400 son exactamente el mismo valor, ¿verdad? No importa cuántos ceros sigan a menos que / hasta que llegue el momento de formatear para mostrar, en cuyo caso, el resultado será el mismo si se formatea correctamente: $"{0.54m:C}"produce "$0.54"y sí, $"{0.5400m:C}"produce "$0.54".
Leonard Lewis
25

Método universal y rápido (sin Math.Pow()/ multiplicación) para System.Decimal:

decimal Truncate(decimal d, byte decimals)
{
    decimal r = Math.Round(d, decimals);

    if (d > 0 && r > d)
    {
        return r - new decimal(1, 0, 0, false, decimals);
    }
    else if (d < 0 && r < d)
    {
        return r + new decimal(1, 0, 0, false, decimals);
    }

    return r;
}
D. Nesterov
fuente
4
Ejecuté esto a través de todas las pruebas mencionadas en las otras respuestas y funciona perfectamente. Sorprendido que no tenga más votos a favor. Vale la pena señalar que los decimales solo pueden estar entre 0 y 28 (probablemente está bien para la mayoría de las personas).
RichardOD
1
Secundo que. Esta es la mejor respuesta. +1
Branko Dimitrijevic
1
Gran respuesta, eso es lo que yo llamo "pensar fuera de la caja"
bruno.almeida
23

Un problema con los otros ejemplos es que multiplican el valor de entrada antes de dividirlo. Hay un caso límite aquí en el que puede desbordar decimal multiplicando primero, un caso límite, pero algo con lo que me he encontrado. Es más seguro tratar la parte fraccionaria por separado de la siguiente manera:

    public static decimal TruncateDecimal(this decimal value, int decimalPlaces)
    {
        decimal integralValue = Math.Truncate(value);

        decimal fraction = value - integralValue;

        decimal factor = (decimal)Math.Pow(10, decimalPlaces);

        decimal truncatedFraction = Math.Truncate(fraction * factor) / factor;

        decimal result = integralValue + truncatedFraction;

        return result;
    }
Tim Lloyd
fuente
Sé que esto es antiguo, pero me di cuenta y problema con esto. El factor que tiene aquí es un int y, por lo tanto, si está truncando a una gran cantidad de decimales (por ejemplo, 25), el resultado final tendrá un error de precisión. Lo arreglé cambiando el tipo de factor a decimal.
TheKingDave
@TheKingDave: probablemente sea irrelevante, pero como el factor no puede tener decimales, debería ser mejor modelarlo tan largo, ¿verdad?
Ignacio Soler García
@SoMoS Para mí, Decimal funcionó mejor porque me dio los valores de almacenamiento más altos para el factor. Todavía tiene una limitación, pero es lo suficientemente grande para mi aplicación. Long, por otro lado, no pudo almacenar números lo suficientemente grandes para mi aplicación. Por ejemplo, si hace un Truncar (25) con largo, habrá alguna inexactitud.
TheKingDave
Actualizado para permitir el truncamiento a una mayor cantidad de lugares según la sugerencia de @TheKingDave, gracias.
Tim Lloyd
6

Dejaré la solución para números decimales.

Algunas de las soluciones para decimales aquí son propensas a desbordarse (si pasamos un número decimal muy grande y el método intentará multiplicarlo).

La solución de Tim Lloyd está protegida contra desbordamientos, pero no es demasiado rápida.

La siguiente solución es aproximadamente 2 veces más rápida y no tiene un problema de desbordamiento:

public static class DecimalExtensions
{
    public static decimal TruncateEx(this decimal value, int decimalPlaces)
    {
        if (decimalPlaces < 0)
            throw new ArgumentException("decimalPlaces must be greater than or equal to 0.");

        var modifier = Convert.ToDecimal(0.5 / Math.Pow(10, decimalPlaces));
        return Math.Round(value >= 0 ? value - modifier : value + modifier, decimalPlaces);
    }
}

[Test]
public void FastDecimalTruncateTest()
{
    Assert.AreEqual(-1.12m, -1.129m. TruncateEx(2));
    Assert.AreEqual(-1.12m, -1.120m. TruncateEx(2));
    Assert.AreEqual(-1.12m, -1.125m. TruncateEx(2));
    Assert.AreEqual(-1.12m, -1.1255m.TruncateEx(2));
    Assert.AreEqual(-1.12m, -1.1254m.TruncateEx(2));
    Assert.AreEqual(0m,      0.0001m.TruncateEx(3));
    Assert.AreEqual(0m,     -0.0001m.TruncateEx(3));
    Assert.AreEqual(0m,     -0.0000m.TruncateEx(3));
    Assert.AreEqual(0m,      0.0000m.TruncateEx(3));
    Assert.AreEqual(1.1m,    1.12m.  TruncateEx(1));
    Assert.AreEqual(1.1m,    1.15m.  TruncateEx(1));
    Assert.AreEqual(1.1m,    1.19m.  TruncateEx(1));
    Assert.AreEqual(1.1m,    1.111m. TruncateEx(1));
    Assert.AreEqual(1.1m,    1.199m. TruncateEx(1));
    Assert.AreEqual(1.2m,    1.2m.   TruncateEx(1));
    Assert.AreEqual(0.1m,    0.14m.  TruncateEx(1));
    Assert.AreEqual(0,      -0.05m.  TruncateEx(1));
    Assert.AreEqual(0,      -0.049m. TruncateEx(1));
    Assert.AreEqual(0,      -0.051m. TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.14m.  TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.15m.  TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.16m.  TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.19m.  TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.199m. TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.101m. TruncateEx(1));
    Assert.AreEqual(0m,     -0.099m. TruncateEx(1));
    Assert.AreEqual(0m,     -0.001m. TruncateEx(1));
    Assert.AreEqual(1m,      1.99m.  TruncateEx(0));
    Assert.AreEqual(1m,      1.01m.  TruncateEx(0));
    Assert.AreEqual(-1m,    -1.99m.  TruncateEx(0));
    Assert.AreEqual(-1m,    -1.01m.  TruncateEx(0));
}
codificador nocturno
fuente
2
No me gusta ponerle el sufijo "Ex". C # admite la sobrecarga, su Truncatemétodo se agrupará junto con los nativos .net, lo que brinda al usuario una experiencia perfecta.
Gqqnbig
1
Su algoritmo da como resultado algunos resultados incorrectos. El modo MidpointRounding predeterminado es el redondeo bancario, que redondea 0,5 al valor par más cercano. Assert.AreEqual(1.1m, 1.12m.TruncateEx(1));falla debido a esto. Si especifica el redondeo "normal" (AwayFromZero) en la llamada Math.Round, Assert.AreEqual(0m, 0m.TruncateEx(1));falla
Jon Senchyna
1
La única forma en que funcionará esta solución es si usa MidpointRounding.AwayFromZeroun código específico para manejar el valor 0.
Jon Senchyna
1
Jon tiene razón: 0m.TruncateEx (0) da como resultado -1 a menos que 0 se maneje explícitamente. Del mismo modo, -11m.TruncateEx (0) da como resultado -10 a menos que se utilice MidpointRounding.AwayFromZero dentro de Math.Round. Sin embargo, parece funcionar bien con esas modificaciones.
Ho Ho Ho
1
Incluso con los cambios para AwayFromZero y el manejo explícito de 0, -9999999999999999999999999999m.TruncateEx (0) da como resultado -9999999999999999999999999998, por lo que sigue siendo falible en algunos casos.
Ho Ho Ho
3

Esta es una pregunta antigua, pero muchas respuestas no funcionan bien o se desbordan para grandes números. Creo que la respuesta de D. Nesterov es la mejor: robusta, simple y rápida. Solo quiero agregar mis dos centavos. Jugué con los decimales y también revisé el código fuente . De la public Decimal (int lo, int mid, int hi, bool isNegative, byte scale) documentación del constructor .

La representación binaria de un número 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 número entero y especificar qué parte del mismo 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.

Sabiendo esto, mi primer enfoque fue crear otro decimalcuya escala corresponda a los decimales que quería descartar, luego truncarlo y finalmente crear un decimal con la escala deseada.

private const int ScaleMask = 0x00FF0000;
    public static Decimal Truncate(decimal target, byte decimalPlaces)
    {
        var bits = Decimal.GetBits(target);
        var scale = (byte)((bits[3] & (ScaleMask)) >> 16);

        if (scale <= decimalPlaces)
            return target;

        var temporalDecimal = new Decimal(bits[0], bits[1], bits[2], target < 0, (byte)(scale - decimalPlaces));
        temporalDecimal = Math.Truncate(temporalDecimal);

        bits = Decimal.GetBits(temporalDecimal);
        return new Decimal(bits[0], bits[1], bits[2], target < 0, decimalPlaces);
    }

Este método no es más rápido que el de D. Nesterov y es más complejo, así que jugué un poco más. Supongo que tener que crear un auxiliar decimaly recuperar los bits dos veces lo hace más lento. En mi segundo intento, manipulé los componentes devueltos por el método Decimal.GetBits (Decimal d) yo mismo. La idea es dividir los componentes por 10 tantas veces como sea necesario y reducir la escala. El código se basa (en gran medida) en el método Decimal.InternalRoundFromZero (ref Decimal d, int decimalCount) .

private const Int32 MaxInt32Scale = 9;
private const int ScaleMask = 0x00FF0000;
    private const int SignMask = unchecked((int)0x80000000);
    // Fast access for 10^n where n is 0-9        
    private static UInt32[] Powers10 = new UInt32[] {
        1,
        10,
        100,
        1000,
        10000,
        100000,
        1000000,
        10000000,
        100000000,
        1000000000
    };

    public static Decimal Truncate(decimal target, byte decimalPlaces)
    {
        var bits = Decimal.GetBits(target);
        int lo = bits[0];
        int mid = bits[1];
        int hi = bits[2];
        int flags = bits[3];

        var scale = (byte)((flags & (ScaleMask)) >> 16);
        int scaleDifference = scale - decimalPlaces;
        if (scaleDifference <= 0)
            return target;

        // Divide the value by 10^scaleDifference
        UInt32 lastDivisor;
        do
        {
            Int32 diffChunk = (scaleDifference > MaxInt32Scale) ? MaxInt32Scale : scaleDifference;
            lastDivisor = Powers10[diffChunk];
            InternalDivRemUInt32(ref lo, ref mid, ref hi, lastDivisor);
            scaleDifference -= diffChunk;
        } while (scaleDifference > 0);


        return new Decimal(lo, mid, hi, (flags & SignMask)!=0, decimalPlaces);
    }
    private static UInt32 InternalDivRemUInt32(ref int lo, ref int mid, ref int hi, UInt32 divisor)
    {
        UInt32 remainder = 0;
        UInt64 n;
        if (hi != 0)
        {
            n = ((UInt32)hi);
            hi = (Int32)((UInt32)(n / divisor));
            remainder = (UInt32)(n % divisor);
        }
        if (mid != 0 || remainder != 0)
        {
            n = ((UInt64)remainder << 32) | (UInt32)mid;
            mid = (Int32)((UInt32)(n / divisor));
            remainder = (UInt32)(n % divisor);
        }
        if (lo != 0 || remainder != 0)
        {
            n = ((UInt64)remainder << 32) | (UInt32)lo;
            lo = (Int32)((UInt32)(n / divisor));
            remainder = (UInt32)(n % divisor);
        }
        return remainder;
    }

No he realizado pruebas de rendimiento rigurosas, pero en un procesador MacOS Sierra 10.12.6, Intel Core i3 de 3,06 GHz y apuntando a .NetCore 2.1, este método parece ser mucho más rápido que el de D. Nesterov (no daré números desde , como he mencionado, mis pruebas no son rigurosas). Depende de quien implemente esto evaluar si las ganancias de rendimiento se compensan o no con la complejidad adicional del código.

Muscicapa Striata
fuente
Tuve que votar a favor por todo el pensamiento y el esfuerzo. Estableciste el de Nesterov como punto de referencia y seguiste adelante, me quito el sombrero.
AndrewBenjamin
2

¿Que este trabajo para usted?

Console.Write(((int)(3.4679999999*100))/100.0);
John Boker
fuente
2

¿ ((long)(3.4679 * 100)) / 100.0Darías lo que quieres?

Franco
fuente
1

Aquí hay un método de extensión:

public static decimal? TruncateDecimalPlaces(this decimal? value, int places)
    {
        if (value == null)
        {
            return null;
        }

        return Math.Floor((decimal)value * (decimal)Math.Pow(10, places)) / (decimal)Math.Pow(10, places);

    } // end
John Meyer
fuente
0

Si no se preocupa demasiado por el rendimiento y el resultado final puede ser una cadena, el siguiente enfoque será resistente a los problemas de precisión flotante:

string Truncate(double value, int precision)
{
    if (precision < 0)
    {
        throw new ArgumentOutOfRangeException("Precision cannot be less than zero");
    }

    string result = value.ToString();

    int dot = result.IndexOf('.');
    if (dot < 0)
    {
        return result;
    }

    int newLength = dot + precision + 1;

    if (newLength == dot + 1)
    {
        newLength--;
    }

    if (newLength > result.Length)
    {
        newLength = result.Length;
    }

    return result.Substring(0, newLength);
}
David Airapetyan
fuente
6
En realidad, codificando '.' no es una buena idea, mejor use System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator [0]
David Airapetyan
0

Aquí está mi implementación de la función TRUNC

private static object Tranc(List<Expression.Expression> p)
{
    var target = (decimal)p[0].Evaluate();

    // check if formula contains only one argument
    var digits = p.Count > 1
        ? (decimal) p[1].Evaluate()
        : 0;

    return Math.Truncate((double)target * Math.Pow(10, (int)digits)) / Math.Pow(10, (int)digits);
}
ladeangel
fuente
0

que hay de esto

Function TruncateDecimal2(MyValue As Decimal) As Decimal
        Try
            Return Math.Truncate(100 * MyValue) / 100
        Catch ex As Exception
            Return Math.Round(MyValue, 2)
        End Try
End Function
user2241289
fuente
0

Aparte de las soluciones anteriores, hay otra forma de lograrlo.

    decimal val=23.5678m,finalValue;

    //take the decimal part    
     int decimalPos = val.ToString().IndexOf('.');
     string decimalPart = val.ToString().Substring(decimalPosition+1,val.ToString().Length);
    //will result.56
   string wholePart=val.ToString().Substring(0,decimalPos-1);
   //concantinate and parse for decimal.
  string truncatedValue=wholePart+decimalPart;//"23.56"
  bool isDecimal=Decimal.tryParse(truncatedValue,out finalValue);//finalValue=23.56
Hameed Syed
fuente
0

En algunas condiciones, esto puede ser suficiente.

Tenía un valor decimal de SubCent = 0.0099999999999999999999999999M que tiende a formatear a | SubCent: 0.010000 | via string.Format("{0:N6}", SubCent );y muchas otras opciones de formato.

Mi requisito era no redondear el valor de SubCent, pero tampoco registrar todos los dígitos.

Lo siguiente cumplió con mi requisito:

string.Format("SubCent:{0}|", 
    SubCent.ToString("N10", CultureInfo.InvariantCulture).Substring(0, 9));

Que devuelve la cadena: | SubCent: 0.0099999 |

Para acomodar el valor que tiene una parte entera, lo siguiente es un comienzo.

tmpValFmt = 567890.0099999933999229999999M.ToString("0.0000000000000000000000000000");
decPt = tmpValFmt.LastIndexOf(".");
if (decPt < 0) decPt = 0;
valFmt4 = string.Format("{0}", tmpValFmt.Substring(0, decPt + 9));

Que devuelve la cadena:

valFmt4 = "567890.00999999"
kevinwaite
fuente
0

estoy usando esta función para truncar el valor después del decimal en una variable de cadena

public static string TruncateFunction(string value)
    {
        if (string.IsNullOrEmpty(value)) return "";
        else
        {
            string[] split = value.Split('.');
            if (split.Length > 0)
            {
                string predecimal = split[0];
                string postdecimal = split[1];
                postdecimal = postdecimal.Length > 6 ? postdecimal.Substring(0, 6) : postdecimal;
                return predecimal + "." + postdecimal;

            }
            else return value;
        }
    }
Arun Kumar
fuente
1
Si bien este código puede responder a la pregunta, proporcionar un contexto adicional sobre cómo y / o por qué resuelve el problema mejoraría el valor de la respuesta a largo plazo.
Nic3500
0

Esto es lo que hice:

        c1 = a1 - b1;
        d1 = Math.Ceiling(c1 * 100) / 100;

restar dos números ingresados ​​sin redondear los decimales hacia arriba o hacia abajo. porque las otras soluciones no me funcionan. No sé si funcionará para otros, solo quiero compartir esto :) Espero que funcione para aquellos que están encontrando una solución a un problema similar al mío. Gracias

PD: soy un principiante, así que siéntete libre de señalar algo sobre esto. : D esto es bueno si realmente estás tratando con dinero, por los centavos, ¿verdad? solo tiene 2 lugares decimales y redondearlo es un no no.

Nooj
fuente
0
        public static void ReminderDigints(decimal? number, out decimal? Value,  out decimal? Reminder)
        {
            Reminder = null;
            Value = null;
            if (number.HasValue)
            {
                Value = Math.Floor(number.Value);
                Reminder = (number - Math.Truncate(number.Value));
            }
        }



        decimal? number= 50.55m;             
        ReminderDigints(number, out decimal? Value, out decimal? Reminder);
Zoyeb Shaikh
fuente
0
public static decimal TruncateDecimalPlaces(this decimal value, int precision)
    {
        try
        {
            step = (decimal)Math.Pow(10, precision);
            decimal tmp = Math.Truncate(step * value);
            return tmp / step;
        }
        catch (OverflowException)
        {
            step = (decimal)Math.Pow(10, -1 * precision);
            return value - (value % step);
        }
    }
Sr. Wang de la puerta de al lado
fuente
-2

En realidad, quieres 3.46 de 3.4679. Esto es solo una representación de caracteres. Por lo tanto, no hay nada que ver con la función matemática. La función matemática no está destinada a hacer este trabajo. Simplemente use el siguiente código.

Dim str1 As String
str1=""
str1 ="3.4679" 
  Dim substring As String = str1.Substring(0, 3)

    ' Write the results to the screen.
    Console.WriteLine("Substring: {0}", substring)

Or 
    Please use the following code.
Public function result(ByVal x1 As Double) As String 
  Dim i as  Int32
  i=0
  Dim y as String
  y = ""
  For Each ch as Char In x1.ToString
    If i>3 then
     Exit For
    Else
    y + y +ch
    End if
    i=i+1
  Next
  return y
End Function

El código anterior se puede modificar para cualquier número Coloque el siguiente código en un evento de clic de botón

Dim str As String 
str= result(3.4679)
 MsgBox("The number is " & str)
antony thomas
fuente
-2

qué pasa

var i = Math.Truncate(number);var r = i + Math.Truncate((number - i) * 100) / 100;
Jacky
fuente