.NET Framework에서 과학적 표기법없이 double을 부동 소수점 문자열 표현으로 변환하는 방법은 무엇입니까?
“작은”샘플 (유효 숫자는 1.5E200
또는 등의 크기가 될 수 있음 1e-200
) :
3248971234698200000000000000000000000000000000
0.00000000000000000000000000000000000023897356978234562
의 없음 표준 숫자 형식은 다음과 같이 없습니다 및 사용자 지정 형식은 또한 소수 구분 자리수의 오픈 수를 가지고 허용하지 않는 것 같습니다.
주어진 대답이 당면한 문제를 해결 하지 못 하기 때문에 이것은 10의 거듭 제곱 표현 (E-05)없이 double을 문자열로 변환하는 방법 의 중복이 아닙니다 . 이 질문에서 허용되는 해결책은 내가 원하는 것이 아닌 고정 소수점 (예 : 20 자리)을 사용하는 것입니다. 고정 소수점 서식을 지정하고 중복 0을 트리밍해도 고정 너비의 최대 너비가 99 자이기 때문에 문제가 해결되지 않습니다.
참고 : 솔루션은 사용자 지정 숫자 형식 (예 : 문화 정보에 따라 다른 소수 구분 기호)을 올바르게 처리해야합니다.
편집 : 질문은 실제로 위에서 언급 한 숫자를 대체하는 것에 관한 것입니다. 나는 부동 소수점 숫자가 어떻게 작동하는지, 그리고 그것들로 어떤 숫자를 사용하고 계산할 수 있는지 알고 있습니다.
답변
범용 ¹ 솔루션의 경우 339 개의 장소를 보존해야합니다.
doubleValue.ToString("0." + new string('#', 339))
0이 아닌 십진수의 최대 수는 16입니다. 15는 소수점 오른쪽에 있습니다. 지수는이 15 자리 숫자를 오른쪽으로 최대 324 자리까지 이동할 수 있습니다. ( 범위와 정밀도를 참조하십시오. )
그것은 작동 double.Epsilon
, double.MinValue
, double.MaxValue
그 사이에, 아무것도.
모든 서식 및 문자열 작업이 관리되지 않는 CLR 코드에 의해 한 번에 수행되므로 성능은 정규식 / 문자열 조작 솔루션보다 훨씬 큽니다. 또한 코드가 정확하다는 것을 증명하는 것이 훨씬 간단합니다.
사용 편의성과 더 나은 성능을 위해 일정하게 만드십시오.
public static class FormatStrings
{
public const string DoubleFixedPoint = "0.###################################################################################################################################################################################################################################################################################################################################################";
}
¹ 업데이트 : 이 역시 무손실 솔루션이라고 잘못 말했습니다. 때문에 사실이 아니다 ToString
제외한 모든 포맷의 일반 표시 라운딩을한다 r
. 라이브 예. 감사합니다, @Loathing! 고정 소수점 표기법으로 왕복하는 기능이 필요하면 Lothing의 답변을 참조하십시오 (즉, .ToString("r")
오늘 사용하는 경우 ).
답변
비슷한 문제가 있었고 이것은 나를 위해 일했습니다.
doubleValue.ToString("F99").TrimEnd('0')
F99는 과잉 일 수 있지만 아이디어를 얻습니다.
답변
이것은 소스 번호 (double)가 문자열로 변환되고 구성 요소로 구문 분석되는 문자열 구문 분석 솔루션입니다. 그런 다음 규칙에 의해 전체 길이 숫자 표현으로 재 조립됩니다. 또한 요청 된 로케일을 고려합니다.
업데이트 : 변환 테스트에는 일반적인 한 자리 정수만 포함되지만 알고리즘은 다음과 같은 경우에도 작동합니다. 239483.340901e-20
using System;
using System.Text;
using System.Globalization;
using System.Threading;
public class MyClass
{
public static void Main()
{
Console.WriteLine(ToLongString(1.23e-2));
Console.WriteLine(ToLongString(1.234e-5)); // 0.00010234
Console.WriteLine(ToLongString(1.2345E-10)); // 0.00000001002345
Console.WriteLine(ToLongString(1.23456E-20)); // 0.00000000000000000100023456
Console.WriteLine(ToLongString(5E-20));
Console.WriteLine("");
Console.WriteLine(ToLongString(1.23E+2)); // 123
Console.WriteLine(ToLongString(1.234e5)); // 1023400
Console.WriteLine(ToLongString(1.2345E10)); // 1002345000000
Console.WriteLine(ToLongString(-7.576E-05)); // -0.00007576
Console.WriteLine(ToLongString(1.23456e20));
Console.WriteLine(ToLongString(5e+20));
Console.WriteLine("");
Console.WriteLine(ToLongString(9.1093822E-31)); // mass of an electron
Console.WriteLine(ToLongString(5.9736e24)); // mass of the earth
Console.ReadLine();
}
private static string ToLongString(double input)
{
string strOrig = input.ToString();
string str = strOrig.ToUpper();
// if string representation was collapsed from scientific notation, just return it:
if (!str.Contains("E")) return strOrig;
bool negativeNumber = false;
if (str[0] == '-')
{
str = str.Remove(0, 1);
negativeNumber = true;
}
string sep = Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator;
char decSeparator = sep.ToCharArray()[0];
string[] exponentParts = str.Split('E');
string[] decimalParts = exponentParts[0].Split(decSeparator);
// fix missing decimal point:
if (decimalParts.Length==1) decimalParts = new string[]{exponentParts[0],"0"};
int exponentValue = int.Parse(exponentParts[1]);
string newNumber = decimalParts[0] + decimalParts[1];
string result;
if (exponentValue > 0)
{
result =
newNumber +
GetZeros(exponentValue - decimalParts[1].Length);
}
else // negative exponent
{
result =
"0" +
decSeparator +
GetZeros(exponentValue + decimalParts[0].Length) +
newNumber;
result = result.TrimEnd('0');
}
if (negativeNumber)
result = "-" + result;
return result;
}
private static string GetZeros(int zeroCount)
{
if (zeroCount < 0)
zeroCount = Math.Abs(zeroCount);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < zeroCount; i++) sb.Append("0");
return sb.ToString();
}
}
답변
당신은 캐스팅 수 double
에 decimal
다음을 수행 ToString()
.
(0.000000005).ToString() // 5E-09
((decimal)(0.000000005)).ToString() // 0,000000005
64 비트 double
에서 128 비트 decimal
또는 300 자 이상의 형식 문자열로 캐스팅하는 더 빠른 성능 테스트를 수행하지 않았습니다 . 아, 그리고 변환 중에 오버플로 오류가 발생할 수 있지만 값이 적합하면 decimal
제대로 작동합니다.
업데이트 : 캐스팅이 훨씬 빨라진 것 같습니다. 다른 답변과 같이 준비된 형식 문자열을 사용하면 백만 번 형식을 지정하는 데 2.3 초가 걸리고 0.19 초만 전송됩니다. 반복 가능. 그건 10 배 빨라진 . 이제는 값 범위에 관한 것입니다.
답변
이것은 내가 지금까지 얻은 것이며 작동하는 것처럼 보이지만 누군가가 더 나은 해결책을 가지고있을 수 있습니다.
private static readonly Regex rxScientific = new Regex(@"^(?<sign>-?)(?<head>\d+)(\.(?<tail>\d*?)0*)?E(?<exponent>[+\-]\d+)$", RegexOptions.IgnoreCase|RegexOptions.ExplicitCapture|RegexOptions.CultureInvariant);
public static string ToFloatingPointString(double value) {
return ToFloatingPointString(value, NumberFormatInfo.CurrentInfo);
}
public static string ToFloatingPointString(double value, NumberFormatInfo formatInfo) {
string result = value.ToString("r", NumberFormatInfo.InvariantInfo);
Match match = rxScientific.Match(result);
if (match.Success) {
Debug.WriteLine("Found scientific format: {0} => [{1}] [{2}] [{3}] [{4}]", result, match.Groups["sign"], match.Groups["head"], match.Groups["tail"], match.Groups["exponent"]);
int exponent = int.Parse(match.Groups["exponent"].Value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
StringBuilder builder = new StringBuilder(result.Length+Math.Abs(exponent));
builder.Append(match.Groups["sign"].Value);
if (exponent >= 0) {
builder.Append(match.Groups["head"].Value);
string tail = match.Groups["tail"].Value;
if (exponent < tail.Length) {
builder.Append(tail, 0, exponent);
builder.Append(formatInfo.NumberDecimalSeparator);
builder.Append(tail, exponent, tail.Length-exponent);
} else {
builder.Append(tail);
builder.Append('0', exponent-tail.Length);
}
} else {
builder.Append('0');
builder.Append(formatInfo.NumberDecimalSeparator);
builder.Append('0', (-exponent)-1);
builder.Append(match.Groups["head"].Value);
builder.Append(match.Groups["tail"].Value);
}
result = builder.ToString();
}
return result;
}
// test code
double x = 1.0;
for (int i = 0; i < 200; i++) {
x /= 10;
}
Console.WriteLine(x);
Console.WriteLine(ToFloatingPointString(x));
답변
#.###...###
or를 사용하는 문제 F99
는 끝 소수점 자리에서 정밀도를 유지하지 않는다는 것입니다. 예 :
String t1 = (0.0001/7).ToString("0." + new string('#', 339)); // 0.0000142857142857143
String t2 = (0.0001/7).ToString("r"); // 1.4285714285714287E-05
문제 DecimalConverter.cs
는 느리다는 것입니다. 이 코드는 Sasik의 답변과 동일한 아이디어이지만 두 배 빠릅니다. 하단의 단위 테스트 방법.
public static class RoundTrip {
private static String[] zeros = new String[1000];
static RoundTrip() {
for (int i = 0; i < zeros.Length; i++) {
zeros[i] = new String('0', i);
}
}
private static String ToRoundTrip(double value) {
String str = value.ToString("r");
int x = str.IndexOf('E');
if (x < 0) return str;
int x1 = x + 1;
String exp = str.Substring(x1, str.Length - x1);
int e = int.Parse(exp);
String s = null;
int numDecimals = 0;
if (value < 0) {
int len = x - 3;
if (e >= 0) {
if (len > 0) {
s = str.Substring(0, 2) + str.Substring(3, len);
numDecimals = len;
}
else
s = str.Substring(0, 2);
}
else {
// remove the leading minus sign
if (len > 0) {
s = str.Substring(1, 1) + str.Substring(3, len);
numDecimals = len;
}
else
s = str.Substring(1, 1);
}
}
else {
int len = x - 2;
if (len > 0) {
s = str[0] + str.Substring(2, len);
numDecimals = len;
}
else
s = str[0].ToString();
}
if (e >= 0) {
e = e - numDecimals;
String z = (e < zeros.Length ? zeros[e] : new String('0', e));
s = s + z;
}
else {
e = (-e - 1);
String z = (e < zeros.Length ? zeros[e] : new String('0', e));
if (value < 0)
s = "-0." + z + s;
else
s = "0." + z + s;
}
return s;
}
private static void RoundTripUnitTest() {
StringBuilder sb33 = new StringBuilder();
double[] values = new [] { 123450000000000000.0, 1.0 / 7, 10000000000.0/7, 100000000000000000.0/7, 0.001/7, 0.0001/7, 100000000000000000.0, 0.00000000001,
1.23e-2, 1.234e-5, 1.2345E-10, 1.23456E-20, 5E-20, 1.23E+2, 1.234e5, 1.2345E10, -7.576E-05, 1.23456e20, 5e+20, 9.1093822E-31, 5.9736e24, double.Epsilon };
foreach (int sign in new [] { 1, -1 }) {
foreach (double val in values) {
double val2 = sign * val;
String s1 = val2.ToString("r");
String s2 = ToRoundTrip(val2);
double val2_ = double.Parse(s2);
double diff = Math.Abs(val2 - val2_);
if (diff != 0) {
throw new Exception("Value {0} did not pass ToRoundTrip.".Format2(val.ToString("r")));
}
sb33.AppendLine(s1);
sb33.AppendLine(s2);
sb33.AppendLine();
}
}
}
}
답변
필수 로그 기반 솔루션입니다. 이 솔루션은 수학을 포함하므로 숫자의 정확도를 약간 떨어 뜨릴 수 있습니다. 심하게 테스트되지 않았습니다.
private static string DoubleToLongString(double x)
{
int shift = (int)Math.Log10(x);
if (Math.Abs(shift) <= 2)
{
return x.ToString();
}
if (shift < 0)
{
double y = x * Math.Pow(10, -shift);
return "0.".PadRight(-shift + 2, '0') + y.ToString().Substring(2);
}
else
{
double y = x * Math.Pow(10, 2 - shift);
return y + "".PadRight(shift - 2, '0');
}
}
편집 : 소수점이 숫자의 0이 아닌 부분을 교차하면이 알고리즘은 비참하게 실패합니다. 나는 단순한 것을 시도했고 너무 멀리 갔다.
