没有科学记数法的双到字符串转换

Luc*_*ero 71 .net c# floating-point number-formatting

如何在.NET Framework中没有科学记数法的情况下将double转换为浮点字符串表示形式?

"小"样本(有效数字可以是任何大小,例如1.5E2001e-200):

3248971234698200000000000000000000000000000000
0.00000000000000000000000000000000000023897356978234562
Run Code Online (Sandbox Code Playgroud)

的无标准的数字格式都是这样的,和一个自定义格式也似乎并没有让小数点后具有开放位数.

这不是如何将double转换为字符串而没有10代表(E-05)的重复,因为那里给出的答案并没有解决手头的问题.这个问题中接受的解决方案是使用固定点(例如20位数),这不是我想要的.固定点格式化和修剪冗余0不能解决问题,因为固定宽度的最大宽度为99个字符.

注意:解决方案必须正确处理自定义数字格式(例如,其他小数分隔符,具体取决于区域性信息).

编辑:问题实际上只是取代前面提到的数字.我知道浮点数如何工作以及可以使用和计算哪些数字.

jnm*_*nm2 31

对于通用¹解决方案,您需要保留339个位置:

doubleValue.ToString("0." + new string('#', 339))

最大非零小数位数为16. 15位于小数点的右侧.指数可以将这15个数字移动到右边最多324个位置.(参见范围和精度.)

它的工作原理为double.Epsilon,double.MinValue,double.MaxValue,和任何之间.

性能将比正则表达式/字符串操作解决方案大得多,因为所有格式化和字符串工作都是通过非托管CLR代码一次完成的.此外,代码更容易证明是正确的.

为了便于使用,甚至更好的性能,使它成为一个常数:

public static class FormatStrings
{
    public const string DoubleFixedPoint = "0.###################################################################################################################################################################################################################################################################################################################################################";
}
Run Code Online (Sandbox Code Playgroud)

¹ 更新:我错误地说这也是一个无损解决方案.事实上它不是,因为ToString它的正常显示舍入所有格式除外r.实例.谢谢,@ Loathing!如果您需要能够以固定点表示法进行往返(即,如果您今天使用的话),请参阅Lothing的答案.ToString("r").


小智 29

我有类似的问题,这对我有用:

doubleValue.ToString("F99").TrimEnd('0')
Run Code Online (Sandbox Code Playgroud)

F99可能有点矫枉过正,但你明白了.

  • `TrimEnd('0')`就足够了,因为`char`数组是`params`.也就是说,传递给`TrimEnd`的任何`char`将自动分组为一个数组. (2认同)

Pau*_*sik 20

这是一个字符串解析解决方案,其中源编号(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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这个更容易阅读,因为你不必理解正则表达式. (3认同)

ygo*_*goe 12

你可以施放doubledecimal,然后做ToString().

(0.000000005).ToString()   // 5E-09
((decimal)(0.000000005)).ToString()   // 0,000000005
Run Code Online (Sandbox Code Playgroud)

我没有做过更快的性能测试,从64位转换double为128位decimal或者超过300个字符的格式字符串.哦,转换过程中可能存在溢出错误,但如果你的值适合,那么decimal这应该可以正常工作.

更新:施法似乎要快得多.使用另一个答案中给出的准备好的格式字符串,格式化一百万次需要2.3秒并且仅投射0.19秒.重复.这快了10倍.现在它只是关于价值范围.

  • 鉴于您知道价值范围,这是一个非常好的解决方案 (2认同)

Luc*_*ero 8

这是我到目前为止,似乎工作,但也许有人有一个更好的解决方案:

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));
Run Code Online (Sandbox Code Playgroud)

  • 那是因为只有15位数字实际上是有意义的,但你可以用指数"移动"它们非常大或非常小.但是你不能添加一个数字超过大约15位数的非常小的数字,因为这样做会超过有效数字的数量,并且由于数字越大越有意义,小部分将丢失.因此,使用类似范围内的数字进行计算(例如添加1e-200和1e-200,或1 + 1或1e200 + 1e200)确实有效,但混合这些值将导致将较小的值四舍五入. (6认同)
  • 将一个加到一个非常小的双倍只是你的想法,与手头的问题无关.如果你只是在没有d = d + 1的情况下运行它,你会发现它确实显示0.000 ..... 0001. (5认同)
  • 没问题.`double x = 1.0; for(int i = 0; i <200; i ++)x/= 10; Console.WriteLine(X);` (2认同)

Loa*_*ing 6

使用#.###...###or的问题F99是它不会在小数点结束时保留精度,例如:

String t1 = (0.0001/7).ToString("0." + new string('#', 339)); // 0.0000142857142857143
String t2 = (0.0001/7).ToString("r");                         //      1.4285714285714287E-05
Run Code Online (Sandbox Code Playgroud)

问题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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)