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