반올림하지 않고 소수점 두 자리 자르기


107

3.4679의 값을 가지고 있고 3.46을 원한다고 가정 해 보겠습니다. 반올림하지 않고 소수점 이하 두 자리로자를 수있는 방법은 무엇입니까?

나는 다음을 시도했지만 세 가지 모두 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));
}

이것은 3.46을 반환하지만 약간 더러워 보입니다.

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

답변:


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

이와 같은 분수는 부동 소수점으로 정확하게 표현할 수 없습니다.


12
값에 십진수를 사용하면이 답변이 작동합니다. 부동 소수점 표현에서 항상 작동하지는 않습니다.
driis 2010-06-29

1
따라서 부동 소수점 리터럴에서 반올림 방향을 지정할 수 있는지 궁금합니다. 흠.
Steve314

이 있어야만 어떤 숫자 이상의 308 개 숫자를 저장할 수 있다는 가정하에 계산하는 것을 프로그래머에게하는 방법 심하게 부적절. Double은 15 개만 저장할 수 있습니다. Overflow는 여기에서 매우 중요한 기능입니다.
Hans Passant

죄송합니다. "값"이 십진수라고 생각했습니다.
nightcoder

54

C #에서 소수점을 자르는 실제 사용을위한 완전한 기능을 갖는 것이 더 유용 할 것입니다. 원하는 경우 Decimal 확장 메서드로 매우 쉽게 변환 할 수 있습니다.

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

VB.NET이 필요한 경우 다음을 시도하십시오.

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

그런 다음 다음과 같이 사용하십시오.

decimal result = TruncateDecimal(0.275, 2);

또는

Dim result As Decimal = TruncateDecimal(0.275, 2)

1
이것은 많은 수에서 오버플로됩니다.
nightcoder

1
야간 코더에 추가하려면 Int32를 함수의 매개체로 사용하고 있다는 사실로 인해 오버플로가 발생합니다. Integer로 캐스팅해야한다면 Int64를 사용해야합니다. 문제는 Truncate가 어쨌든 Decimal 정수를 반환하기 때문에 어쨌든 추가 오버 헤드를 발생시키고 싶은 이유입니다. 다음과 같이하십시오. decimal step = (decimal) Math.Pow (10, precision); return Math.Truncate (단계 * 값) / 단계;
Sarel Esterhuizen

나는 캐스트를 Integer에 떨어 뜨 렸습니다. 더 나은 가독성과 기능 작동 방식을 이해하기 위해 별도의 줄을 남겼습니다.
Corgalore

27

모듈러스 연산자를 사용합니다.

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

결과 : 0.54


1
나는 다른 모든 멋진 솔루션을 이해하지 못합니다 (읽기 : 확인하는 데 시간을 소비하지 않았습니다). 이것은 내가 찾던 것을 정확히 수행 합니다. 감사합니다!
Isaac Baker

이것을 .Net Fiddle clicky 에서 실행 하면 0.5400... D. Nesterov대답 은 예상되는 0.54.
ttugates

@ttugates, 0.54와 0.5400이 정확히 같은 값이라는 것을 알고 있습니까? : 올바른 형식의 경우는가 형식으로 시간을 때까지 /하지 않는 한 따라 얼마나 많은 제로 중요하지 않습니다 디스플레이의 경우, 결과는 동일합니다 $"{0.54m:C}"생산 "$0.54"및 예, $"{0.5400m:C}"생산 "$0.54".
Leonard Lewis

25

다음에 대한 보편적이고 빠른 방법 ( Math.Pow()/ 곱하기 없음) 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;
}

4
나는 다른 답변에서 언급 한 모든 테스트를 통해 이것을 실행했으며 완벽하게 작동합니다. 놀랍게도 더 많은 찬성표가 없습니다. 소수는 0에서 28 사이 여야합니다 (대부분의 사람들에게 가능할 것입니다).
RichardOD

1
두 번째입니다. 이것이 최고의 답변입니다. +1
Branko Dimitrijevic

1
내가 "상자 밖에서 생각"부르는 멋진 대답,
bruno.almeida

23

다른 예제의 한 가지 문제는 입력 값 나누기 전에 곱한다는 것입니다. 여기에 엣지 케이스를 먼저 곱하여 십진수를 오버플로 할 수있는 엣지 케이스가 있습니다. 다음과 같이 분수 부분을 개별적으로 처리하는 것이 더 안전합니다.

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

나는 이것이 오래되었다는 것을 알고 있지만 이것을 발견하고 문제를 일으켰습니다. 여기에있는 요소는 int이므로 많은 소수 자릿수 (예 : 25)로 자르면 최종 결과에 정밀도 오류가 발생합니다. 요인 유형을 십진수로 변경하여 수정했습니다.
TheKingDave

@TheKingDave : 아마도 그것은 관련이 없지만 소수를 가질 수 없기 때문에 그것을 길게 모델링하는 것이 더 낫습니다.
이그나시오 솔러 가르시아

@SoMoS For me Decimal은 factor에 대해 가장 높은 저장 값을 제공했기 때문에 더 잘 작동했습니다. 여전히 제한이 있지만 내 응용 프로그램에는 충분히 큽니다. 반면에 오랫동안 내 응용 프로그램에 충분한 수를 저장할 수 없었습니다. 예를 들어 Truncate (25)를 길게하면 약간의 부정확성이 있습니다.
TheKingDave

@TheKingDave 제안에 따라 더 많은 장소로 잘릴 수 있도록 업데이트되었습니다.
Tim Lloyd

6

나는 십진수에 대한 해결책을 남길 것입니다.

여기서 소수에 대한 일부 솔루션은 오버플로가 발생하기 쉽습니다 (매우 큰 소수를 전달하고 메서드가이를 곱하려고 시도하는 경우).

Tim Lloyd의 솔루션은 오버플로로부터 보호되지만 너무 빠르지는 않습니다.

다음 솔루션은 약 2 배 더 빠르며 오버플로 문제가 없습니다.

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

2
나는 그것에 "Ex"접미사를 붙이는 것을 좋아하지 않는다. C #은 오버로딩을 지원하며 Truncate메서드는 .net 네이티브 메서드와 함께 그룹화되어 사용자에게 원활한 경험을 제공합니다.
Gqqnbig

1
알고리즘으로 인해 잘못된 결과가 발생합니다. 기본 MidpointRounding 모드는 Banker 's Rounding이며 0.5를 가장 가까운 짝수 값으로 반올림합니다. Assert.AreEqual(1.1m, 1.12m.TruncateEx(1));이 때문에 실패합니다. Math.Round 호출에서 "정상"반올림 (AwayFromZero)을 지정 Assert.AreEqual(0m, 0m.TruncateEx(1));하면 실패합니다
Jon Senchyna

1
이 솔루션이 작동하는 유일한 방법 MidpointRounding.AwayFromZero은 값 0을 처리하도록 특별히 코드를 사용하는 것입니다.
Jon Senchyna

1
Jon은 정확합니다 : 0m.TruncateEx (0)는 0이 명시 적으로 처리되지 않는 한 -1이됩니다. 마찬가지로 -11m.TruncateEx (0)는 MidpointRounding.AwayFromZero가 Math.Round 내에서 사용되지 않는 한 -10이됩니다. 그래도 그 수정으로 잘 작동하는 것 같습니다.
Ho Ho Ho

1
AwayFromZero를 변경하고 0, -9999999999999999999999999999m.TruncateEx (0)를 명시 적으로 처리하더라도 -9999999999999999999999999998이 발생하므로 일부 경우 여전히 오류가 발생합니다.
Ho Ho Ho

3

이것은 오래된 질문이지만 많은 anwsers가 잘 수행되지 않거나 큰 숫자에 대해 오버플로됩니다. 나는 D. Nesterov 대답이 가장 좋은 대답이라고 생각합니다. 강력하고 간단하며 빠릅니다. 2 센트 만 더하고 싶습니다. 나는 소수 를 가지고 놀았고 또한 소스 코드를 확인했다 . 로부터 public Decimal (int lo, int mid, int hi, bool isNegative, byte scale) 생성자 문서 .

Decimal 숫자의 이진 표현은 1 비트 부호, 96 비트 정수 및 정수를 나누고 소수 부분을 지정하는 데 사용되는 배율 인수로 구성됩니다. 배율 인수는 암시 적으로 0에서 28까지의 지수로 올린 숫자 10입니다.

이것을 알고, 나의 첫 번째 접근 방식은 decimal내가 버리고 싶은 소수에 해당하는 스케일을 가진 다른 하나 를 만든 다음 잘라 내고 마지막으로 원하는 스케일로 소수를 만드는 것입니다.

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

이 방법은 D. Nesterov의 방법보다 빠르지 않고 더 복잡해서 조금 더 놀았습니다. 내 생각 엔 보조 장치를 만들고 decimal비트를 두 번 검색 해야하는 것이 속도가 느려진다는 것입니다. 두 번째 시도에서 Decimal.GetBits (Decimal d) 메서드에서 반환 한 구성 요소를 직접 조작했습니다 . 아이디어는 필요한만큼 구성 요소를 10 배로 나누고 규모를 줄이는 것입니다. 코드는 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;
    }

엄격한 성능 테스트를 수행하지는 않았지만 MacOS Sierra 10.12.6, 3,06GHz Intel Core i3 프로세서 및 .NetCore 2.1을 대상으로하는이 방법은 D. Nesterov보다 훨씬 빠른 것 같습니다. , 내가 언급했듯이 내 테스트는 엄격하지 않습니다). 추가 된 코드 복잡성에 대해 성능 향상이 보상을 받는지 여부를 평가하는 것은이를 구현하는 사람에게 달려 있습니다.


나는 모든 생각과 노력 때문에 찬성 투표를해야했다. 네 스테 로프를 벤치 마크로 설정하고 계속해서 모자를 썼습니다.
AndrewBenjamin 19 년

2

이것이 당신을 위해 일할까요?

Console.Write(((int)(3.4679999999*100))/100.0);

2

겠습니까 ((long)(3.4679 * 100)) / 100.0당신이 원하는 것을주는?


1

확장 방법은 다음과 같습니다.

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

0

성능에 대해 너무 걱정하지 않고 최종 결과가 문자열이 될 수있는 경우 다음 접근 방식은 부동 정밀도 문제에 탄력적입니다.

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

6
사실 '.' 좋은 생각, 더 나은 사용 System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator [0] 아닙니다
데이비드 Airapetyan

0

다음은 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);
}

0

이건 어때?

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

0

위의 솔루션 외에도 우리가 달성 할 수있는 또 다른 방법이 있습니다.

    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

0

일부 조건에서는 이것으로 충분할 수 있습니다.

나는 진수 값했다 SubCent = 0.0099999999999999999999999999M 에 포맷하는 경향이 SubCent | : 0.010000를 | string.Format("{0:N6}", SubCent );및 다른 많은 형식 선택을 통해 .

내 요구 사항은 SubCent 값을 반올림하는 것이 아니라 모든 숫자를 기록하는 것도 아닙니다.

다음은 내 요구 사항을 충족했습니다.

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

다음 문자열을 반환합니다. | SubCent : 0.0099999 |

정수 부분을 갖는 값을 수용하기 위해 다음이 시작됩니다.

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

문자열을 반환합니다.

valFmt4 = "567890.00999999"

0

이 함수를 사용하여 문자열 변수에서 소수점 이하 값을 자릅니다.

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

1
이 코드는 질문에 답할 수 있지만 문제를 해결하는 방법 및 / 또는 이유에 대한 추가 컨텍스트를 제공하면 답변의 장기적인 가치가 향상됩니다.
Nic3500

0

이것이 내가 한 일입니다.

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

소수를 반올림하거나 내림하지 않고 입력 된 두 숫자를 뺍니다. 다른 솔루션은 나를 위해 작동하지 않기 때문입니다. 그것이 다른 사람들에게 효과가 있을지 모르겠습니다. 저는 이것을 공유하고 싶습니다. 감사

추신 : 저는 초보자이므로 이것에 대해 자유롭게 지적하십시오. : D 당신이 실제로 돈을 다루고 있다면 이것이 좋습니다. 센트의 원인 이지요? 소수점 이하 두 자리 만 있고 반올림은 아니오입니다.


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

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

-2

실제로 3.4679에서 3.46을 원합니다. 이것은 문자의 표현 일 뿐이므로 수학 함수와는 아무 관련이 없으며 수학 함수는이 작업을 수행하기위한 것이 아닙니다. 다음 코드를 사용하십시오.

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

위의 코드는 모든 숫자에 대해 수정할 수 있습니다. 버튼 클릭 이벤트에 다음 코드를 입력하십시오.

Dim str As String 
str= result(3.4679)
 MsgBox("The number is " & str)

-2

이건 어떤가요

var i = Math.Truncate(number);var r = i + Math.Truncate((number - i) * 100) / 100;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.