[c#] 반올림하지 않고 소수점 두 자리 자르기

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



답변

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

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


답변

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)


답변

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

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

결과 : 0.54


답변

다음에 대한 보편적이고 빠른 방법 ( 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;
}


답변

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

    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의 솔루션은 오버플로로부터 보호되지만 너무 빠르지는 않습니다.

다음 솔루션은 약 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));
}


답변

이것은 오래된 질문이지만 많은 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보다 훨씬 빠른 것 같습니다. , 내가 언급했듯이 내 테스트는 엄격하지 않습니다). 추가 된 코드 복잡성에 대해 성능 향상이 보상을 받는지 여부를 평가하는 것은이를 구현하는 사람에게 달려 있습니다.