이진 부동 소수점 형식 간 Decimal 변환과 이진 부동 소수점 형식으로 BigInteger 의 변환은 이제 올바르게 반올림된 결과를 생성합니다. 이전에는 이러한 변환 과정에서 값이 잘려 나가거나, 중간 값을 거치며 반올림되거나, 유효 자릿수가 버려질 수 있었습니다.
도입된 버전
.NET 11 미리 보기 7
이전 동작
float 및 double에서 decimal로의 변환에서는 각각 7자리와 15자리의 유효한 10진수 자릿수만 유지되었습니다. 이진 부동 소수점 리터럴과 10진 리터럴 사이의 차이를 숨길 수 있습니다.
using System.Globalization;
double value = 1.23;
decimal converted = (decimal)value;
Console.WriteLine(converted.ToString("G29", CultureInfo.InvariantCulture));
출력은 다음과 같습니다.
1.23
decimal에서 float 또는 double로의 변환은 두 번 이상 반올림될 수 있습니다. 다음은 그 예입니다.
using System.Globalization;
decimal value = 10000000000000.099609375m;
double converted = (double)value;
Console.WriteLine(converted.ToString("G99", CultureInfo.InvariantCulture));
출력은 다음과 같습니다.
10000000000000.09765625
decimal에서 Half 또는 BFloat16로의 변환은 먼저 값을 float로 변환했으며, 이로 인해 잘못 반올림된 결과가 생성될 수도 있었습니다.
BigInteger에서 double로 변환할 때 가장 가까운 표현 가능한 값으로 반올림하는 대신 잘려 나간 비트를 버렸습니다. 다음은 그 예입니다.
using System.Globalization;
using System.Numerics;
BigInteger value = long.MaxValue / 2;
double converted = (double)value;
Console.WriteLine(converted.ToString("G17", CultureInfo.InvariantCulture));
출력은 다음과 같습니다.
4.6116860184273874E+18
BigInteger에서 Half, float 또는 BFloat16로의 변환은 먼저 값을 double로 변환했으며, 이로 인해 두 번 반올림되어 가장 가까이 표현 가능한 값에서 마지막 자리 단위 하나만큼 떨어진 결과가 생성될 수 있었습니다.
새 동작
.NET 11부터 변환은 정확한 원본 값을 가장 가까운 표현 가능한 대상 값으로 한 번 반올림합니다.
첫 번째 예제에서는 double 값이 정확히 1.23와 일치하지 않습니다. 정확한 값은 대략적 1.229999999999999982236431605997...이므로 변환된 decimal 값은 다음과 같습니다.
1.229999999999999982236431606
두 번째 예제의 경우 변환된 double 값은 이제 다음과 같습니다.
10000000000000.099609375
decimal에서 BFloat16 또는 Half로의 변환도 또한 올바르게 반올림되며, 이전 버전과는 다른 대상 비트 패턴을 생성할 수 있습니다.
예제의 BigInteger 경우 변환된 double 값은 이제 다음과 같습니다.
4.6116860184273879E+18
BigInteger에서 float, Half 또는 BFloat16로의 변환도 또한 올바르게 반올림됩니다.
변환이 컴파일 시간 상수로 평가되는 경우 프로젝트의 대상 프레임워크에 관계없이 프로젝트가 다시 작성될 때 .NET 11 Preview 7 SDK 이상 SDK에서 호스트하는 컴파일러가 출력 어셈블리에 새 결과를 포함할 수 있습니다.
파괴적 변경 유형
이 변경 사항은 동작 변경입니다.
변경 이유
이전 변환 알고리즘은 대상 형식이 나타낼 수 있는 정보를 손실했으며 경우에 따라 가장 가까운 표현 가능한 결과 이외의 값을 선택했습니다. 이로 인해 decimal 변환의 양방향 모두와 BigInteger에서의 변환에서 정밀도 오류가 발생했습니다. 새 알고리즘은 변환을 계산할 때 중간 계산이 정확한 정밀도로 수행되는 것처럼 처리한 다음 대상 형식으로 한 번만 반올림하는, 기대되는 부동소수점 연산 규칙을 따릅니다.
자세한 내용은 dotnet/runtime#130565 및 dotnet/runtime#130566 참조하세요.
권장 작업
이진 부동 소수점 리터럴과 원본 텍스트가 같은 10진수 리터럴이 동일한 값을 나타낸다고 가정하지 마세요. 값이 10진수인 경우 10진수 리터럴을 사용합니다.
decimal value = 123.4567m;
double 리터럴로부터의 변환 대신:
decimal value = (decimal)123.4567;
일반적으로 decimal로 변환할 때 이전 결과를 복원하려면, 변환된 값을 float 원본의 경우 유효 숫자 7자리로, double 원본의 경우 유효 숫자 15자리로 반올림해야 합니다. 이전 변환은 짝수와의 관계가 있는 가장 가까운 값으로 반올림되었습니다. 잘리지 않았습니다. 양수 및 음수 값은 대칭적으로 처리되었습니다.
예를 들어, G7 또는 G15 표준 숫자 형식 문자열로 원본 값을 서식 지정하고 그 결과를 파싱하면 임의의 유한 값에 대해 해당하는 유효 자릿수 반올림이 수행됩니다.
using System.Globalization;
static decimal ConvertToDecimalLikePrevious(float value)
{
Span<char> text = stackalloc char[32];
value.TryFormat(text, out int length, "G7", CultureInfo.InvariantCulture);
return decimal.Parse(text[..length], NumberStyles.Float, CultureInfo.InvariantCulture);
}
static decimal ConvertToDecimalLikePrevious(double value)
{
Span<char> text = stackalloc char[32];
value.TryFormat(text, out int length, "G15", CultureInfo.InvariantCulture);
return decimal.Parse(text[..length], NumberStyles.Float, CultureInfo.InvariantCulture);
}
decimal fromFloat = ConvertToDecimalLikePrevious(14.1f); // 14.1
decimal fromDouble = ConvertToDecimalLikePrevious(-123.4567); // -123.4567
범위 기반 구현은 할당되지 않습니다.
decimal의 범위를 벗어나는 값은 변환 중과 마찬가지로 파싱 중에도 계속 예외가 발생합니다.
이전의 잘못 반올림된 결과를 인코딩한 테스트 및 직렬화된 예상 값을 업데이트합니다. 여기에는 BigInteger 변환 시 0 방향으로 절단되는 동작에 의존하던 코드가 포함됩니다. 애플리케이션에 프로토콜 또는 파일 형식에 대한 특정 레거시float, Halfdouble비트 패턴 또는 BFloat16 비트 패턴이 필요한 경우 숫자 변환을 통해 재현하지 않고 해당 비트 패턴을 명시적으로 인코딩합니다.
이전 변환 알고리즘을 복원하는 호환성 스위치는 없습니다.
영향을 받는 API
- Decimal.Decimal(Single)
- Decimal.Decimal(Double)
- Single 및 Double에서 Decimal로의 명시적 변환
- Decimal에서 Single 및 Double로의 명시적 변환
- Decimal.ToSingle(Decimal)
- Decimal.ToDouble(Decimal)
- Convert.ToDecimal(Single)
- Convert.ToDecimal(Double)
- Convert.ToSingle(Decimal)
- Convert.ToDouble(Decimal)
- Decimal.CreateChecked<TOther>(TOther), Decimal.CreateSaturating<TOther>(TOther) 및 Decimal.CreateTruncating<TOther>(TOther)가 Single 또는 Double(으)로 변환되거나 그로부터 변환될 때
- Decimal에서 Half로의 명시적 변환
- Decimal에서 BFloat16(으)로의 명시적 변환
- BigInteger에서 Double, Single, Half 및 BFloat16로의 명시적 변환
- IConvertible 또는 제네릭 수학 인터페이스를 통해 수행되는 동등한 변환
.NET