Truncar duas casas decimais sem arredondamento

107

Vamos dizer que tenho um valor de 3,4679 e quero 3,46, como posso truncar para duas casas decimais sem arredondar?

Eu tentei o seguinte, mas todos os três me deram 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));
}

Isso retorna 3,46, mas parece sujo de alguma forma:

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

fonte

Respostas:

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

Esteja ciente de que frações como essas não podem ser representadas com precisão em ponto flutuante.

Hans Passant
fonte
12
Use decimal para seus valores e esta resposta funcionará. É improvável que sempre funcione em qualquer representação de ponto flutuante.
driis
1
Isso me faz pensar se deveria ser possível especificar a direção do arredondamento em literais de ponto flutuante. Hmmmm.
Steve314
Deve haver alguma maneira de dizer ao programador que calcular com a suposição de que um número pode armazenar mais de 308 dígitos é grosseiramente inapropriado. Double pode armazenar apenas 15. Overflow é uma característica muito importante aqui, overflow bastante.
Hans Passant
Desculpe, pensei que "valor" é decimal.
nightcoder
54

Seria mais útil ter uma função completa para uso no mundo real de truncar um decimal em C #. Isso poderia ser convertido em um método de extensão decimal muito fácil se você quisesse:

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

Se você precisa de VB.NET, tente isto:

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

Em seguida, use-o assim:

decimal result = TruncateDecimal(0.275, 2);

ou

Dim result As Decimal = TruncateDecimal(0.275, 2)
Corgalore
fonte
1
Isso irá transbordar em grandes números.
nightcoder de
1
Para adicionar ao codificador noturno, o fato de você estar usando Int32 como intermediário em sua função irá causar overflows. Você deve usar Int64 se realmente precisar convertê-lo em um Integer. A questão seria por que você iria querer incorrer nessa sobrecarga extra de qualquer maneira, já que Truncate retorna integrais decimais de qualquer maneira. Basta fazer algo como: etapa decimal = (decimal) Math.Pow (10, precisão); retornar Math.Truncate (step * value) / step;
Sarel Esterhuizen de
Eu mudei o elenco para Integer. Deixei linhas separadas para melhor legibilidade e compreensão de como a função funciona.
Corgalore
27

Use o operador de módulo:

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

resultado: 0,54

Leonard Lewis
fonte
1
Não entendo (leia-se: não perdi tempo para verificar) todas essas outras soluções extravagantes, isso faz exatamente o que eu estava procurando. Obrigado!
Isaac Baker
Executando isso no .Net Fiddle clicky produz 0.5400... A resposta de D. Nesterov abaixo produziu o esperado 0.54.
ttugates
Você percebe, @ttugates, que 0,54 e 0,5400 são exatamente o mesmo valor, certo? Não importa quantos zeros se seguem, a menos / até que chegue a hora de formatar para exibição - nesse caso, o resultado será o mesmo se formatado corretamente: $"{0.54m:C}"produz "$0.54"e, sim, $"{0.5400m:C}"produz "$0.54".
Leonard Lewis
25

Método universal e rápido (sem Math.Pow()/ multiplicação) 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
fonte
4
Executei todos os testes mencionados nas outras respostas e funcionou perfeitamente. Surpreso por não ter mais votos positivos. É importante notar que os decimais só podem estar entre 0 e 28 (provavelmente OK para a maioria das pessoas).
RichardOD
1
Eu concordo. Esta é a melhor resposta. +1
Branko Dimitrijevic
1
Ótima resposta, isso é o que eu chamo de "pensar fora da caixa"
bruno.almeida
23

Um problema com os outros exemplos é que eles multiplicam o valor de entrada antes de dividi-lo. Há um caso extremo aqui em que você pode estourar o decimal multiplicando primeiro, um caso extremo, mas algo que encontrei. É mais seguro lidar com a parte fracionária separadamente da seguinte maneira:

    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
fonte
Eu sei que isso é antigo, mas percebi e problema com isso. O fator que você tem aqui é um int e, portanto, se você estiver truncando para um grande número de casas decimais (digamos 25), o resultado final terá um erro de precisão. Eu corrigi alterando o tipo de fator para decimal.
TheKingDave
@TheKingDave: provavelmente é irrelevante, mas como o fator não pode ter decimais, seria melhor modelá-lo por mais tempo certo?
Ignacio Soler Garcia
@SoMoS Para mim, Decimal funcionou melhor porque me deu os maiores valores de armazenamento para fator. Ele ainda tem uma limitação, mas é grande o suficiente para meu aplicativo. Long, por outro lado, não foi capaz de armazenar números grandes o suficiente para meu aplicativo. Por exemplo, se você fizer um Truncate (25) com long, haverá alguma imprecisão.
TheKingDave
Atualizado para permitir o truncamento para um número maior de lugares de acordo com a sugestão de @TheKingDave, obrigado.
Tim Lloyd
6

Vou deixar a solução para números decimais.

Algumas das soluções para decimais aqui estão sujeitas a estourar (se passarmos um número decimal muito grande e o método tentará multiplicá-lo).

A solução de Tim Lloyd é protegida contra estouro, mas não é muito rápida.

A solução a seguir é cerca de 2 vezes mais rápida e não tem um problema de estouro:

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));
}
nightcoder
fonte
2
Eu não gosto de sufixar "Ex" para isso. C # suporta sobrecarga, seu Truncatemétodo será agrupado com os nativos .net, proporcionando ao usuário uma experiência perfeita.
Gqqnbig
1
Seu algoritmo resulta em alguns resultados incorretos. O modo padrão MidpointRounding é Banker's Rounding, que arredonda 0,5 para o valor par mais próximo. Assert.AreEqual(1.1m, 1.12m.TruncateEx(1));falha por causa disso. Se você especificar o arredondamento "normal" (AwayFromZero) na chamada Math.Round, então Assert.AreEqual(0m, 0m.TruncateEx(1));falhará
Jon Senchyna
1
A única maneira dessa solução funcionar é se você usar MidpointRounding.AwayFromZeroum código específico para lidar com o valor 0.
Jon Senchyna
1
Jon está correto: 0m.TruncateEx (0) resulta em -1, a menos que 0 seja explicitamente tratado. Da mesma forma, -11m.TruncateEx (0) resulta em -10, a menos que MidpointRounding.AwayFromZero seja usado em Math.Round. Parece funcionar bem com essas modificações.
Ho Ho Ho
1
Mesmo com alterações para AwayFromZero e manipulação explícita de 0, -9999999999999999999999999999m.TruncateEx (0) resulta em -999999999999999999999999999998, portanto, ainda é falível em alguns casos.
Ho Ho Ho
3

Esta é uma questão antiga, mas muitos anwsers não funcionam bem ou transbordam para números grandes. Acho que a resposta de D. Nesterov é a melhor: robusta, simples e rápida. Eu só quero adicionar meus dois centavos. Eu brinquei com decimais e também verifiquei o código-fonte . Da public Decimal (int lo, int mid, int hi, bool isNegative, byte scale) documentação do construtor .

A representação binária de um número decimal consiste em um sinal de 1 bit, um número inteiro de 96 bits e um fator de escala usado para dividir o número inteiro e especificar qual parte dele é uma fração decimal. O fator de escala é implicitamente o número 10 elevado a um expoente que varia de 0 a 28.

Sabendo disso, minha primeira abordagem foi criar outro decimalcuja escala corresponda aos decimais que eu queria descartar, truncar e finalmente criar um decimal com a escala desejada.

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 não é mais rápido que o de D. Nesterov e é mais complexo, então brinquei um pouco mais. Meu palpite é que ter que criar um auxiliar decimale recuperar os bits duas vezes o torna mais lento. Em minha segunda tentativa, eu mesmo manipulei os componentes retornados pelo método Decimal.GetBits (Decimal d) . A ideia é dividir os componentes em 10 vezes quantas forem necessárias e reduzir a escala. O código é baseado (fortemente) no 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;
    }

Não realizei testes de desempenho rigorosos, mas em um MacOS Sierra 10.12.6, processador Intel Core i3 de 3,06 GHz e direcionado para .NetCore 2.1 este método parece ser muito mais rápido do que o de D. Nesterov (não vou dar números desde , como já mencionei, meus testes não são rigorosos). Cabe a quem implementa isso avaliar se os ganhos de desempenho compensam ou não pela complexidade de código adicionada.

Muscicapa Striata
fonte
Eu tive que votar positivamente por causa de todo o pensamento e esforço. Você definiu o de Nesterov como um ponto de referência e continuou - tiro o chapéu.
AndrewBenjamin de
2

Isso funcionaria para você?

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

Daria o ((long)(3.4679 * 100)) / 100.0que você quer?

Frank
fonte
1

Aqui está um método de extensão:

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
fonte
0

Se você não se preocupa muito com o desempenho e seu resultado final pode ser uma string, a seguinte abordagem será resiliente a problemas de precisão flutuante:

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
fonte
6
Na verdade, codificar '.' não é uma boa ideia, é melhor usar System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator [0]
David Airapetyan
0

Aqui está minha implementação da função 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
fonte
0

que tal isso?

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
fonte
0

Além das soluções acima, há outra maneira de conseguir.

    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
fonte
0

Em algumas condições, isso pode ser suficiente.

Eu tinha um valor decimal de SubCent = 0,009999999999999999999999999999M que tende a formatar para | SubCent: 0,010000 | via string.Format("{0:N6}", SubCent );e muitas outras opções de formatação.

Minha exigência não era arredondar o valor do SubCent, mas também não registrar todos os dígitos.

O seguinte atendeu ao meu requisito:

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

Que retorna a string: | SubCent: 0,0099999 |

Para acomodar o valor que tem uma parte inteira, o seguinte é um começo.

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

Que retorna a string:

valFmt4 = "567890.00999999"
kevinwaite
fonte
0

estou usando esta função para truncar valor após decimal em uma variável de string

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
fonte
1
Embora este código possa responder à pergunta, fornecer contexto adicional sobre como e / ou por que ele resolve o problema melhoraria o valor da resposta a longo prazo.
Nic3500
0

Isso é o que eu fiz:

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

subtrair dois números inseridos sem arredondar para cima ou para baixo os decimais. porque as outras soluções não funcionam para mim. não sei se vai funcionar para os outros, só quero compartilhar isso :) Espero que funcione tho para aqueles que estão encontrando uma solução para um problema semelhante ao meu. obrigado

PS: eu sou um iniciante, fique à vontade para apontar algo sobre isso. : D isso é bom se você está realmente lidando com dinheiro, causa dos centavos certo? tem apenas 2 casas decimais e o arredondamento é um não não.

Nooj
fonte
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
fonte
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);
        }
    }
Mr.Wang do Next Door
fonte
-2

Na verdade, você quer 3,46 de 3,4679. Esta é apenas a representação de caracteres. Portanto, não há nada a ver com a função matemática. A função matemática não se destina a fazer este trabalho. Basta usar o seguinte 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

O código acima pode ser modificado para qualquer número Coloque o seguinte código em um evento de clique de botão

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

A respeito

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