views:

2066

answers:

9

How to convert a double into a floating-point string representation without scientific notation in the .NET Framework?

"Small" samples (effective numbers may be of any size, such as 1.5E200 or 1e-200) :

3248971234698200000000000000000000000000000000
0.00000000000000000000000000000000000023897356978234562

None of the standard number formats are like this, and a custom format also doesn't seem to allow having an open number of digits after the decimal separator.

This is not a duplicate of How to convert double to string without the power to 10 representation (E-05) because the answers given there do not solve the issue at hand. The accepted solution in this question was to use a fixed point (such as 20 digits), which is not what I want. A fixed point formatting and trimming the redundant 0 doesn't solve the issue either because the max width for fixed width is 99 characters.

Note: the solution has to deal correctly with custom number formats (e.g. other decimal separator, depending on culture information).

Edit: The question is really only about displaing aforementioned numbers. I'm aware of how floating point numbers work and what numbers can be used and computed with them.

+3  A: 

This is what I've got so far, seems to work, but maybe someone has a better solution:

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));
Lucero
-1 since does not provide solution for the following stuation (and it cannot): double d1 = 1e-200; d = d + 1; ToFloatingPointString(d) just returns 1 here. Not 1,000...........000001.
JCasso
Adding one to a very small double is just your idea, and has nothing to do with the question at hand. If you just run it without the d=d+1, you'll see that it does in fact display 0.000.....0001.
Lucero
Find a way to calculate 1e-200 on runtime instead of setting a "constant" value, i will vote it up.
JCasso
No problem. `double x = 1.0; for (int i = 0; i < 200; i++) x /= 10; Console.WriteLine(x);`
Lucero
@Lucero: That's it. Works perfect. This post is also the answer of your question. I am sorry for the misinformation i provided. However I really don't understand why division works but putting 1 does not work here.
JCasso
By the way can you add double x stuff with editing your answer? I cannot vote up (I want to).
JCasso
That's because only 15 digits are in fact meaningful, but you can "shift" them with the exponent to be very large or very small. But you cannot add a very small number with a number which is more than about 15 digits larger, because doing so exceeds the number of significant digits and since the larger number is more significant, the small portion will be lost. Therefore, computing with numbers in a similar range (like adding 1e-200 and 1e-200, or 1+1, or 1e200+1e200) does work, but mixing such values will result in rounding the smaller value away.
Lucero
So 1e-200 + 1e-199 can be converted to string by the method you provide. I got it. Thank you very very much.
JCasso
You're welcome, and thank you for being open to the discussion. :)
Lucero
I always bow down to the knowledge :)
JCasso
A: 

I could be wrong, but isn't it like this?

data.ToString("n");

http://msdn.microsoft.com/en-us/library/dwhawy9k.aspx

csharptest.net
Seeing your answer I must have misunderstood your question, sorry.
csharptest.net
No, first I don't want the thousand separator and second there seems to be always a fixed number of digits after the comma. See also MSDN help for N format: http://msdn.microsoft.com/en-us/library/dwhawy9k.aspx#NFormatString
Lucero
A: 

Just to build on what jcasso said what you can do is to adjust your double value by changing the exponent so that your favorite format would do it for you, apply the format, and than pad the result with zeros to compensate for the adjustment.

mfeingold
The exponent in the IEEE floating point numbers is 2-base, but the decimal numbers are 10-base. Therefore, this just doesn't work. This is also the reason why you cannot store 0.1 as exact value in a double. Or please just provide some sample (code) if you think that I misunderstood your answer.
Lucero
+2  A: 

In the old days when we had to write our own formatters, we'd isolate the mantissa and exponent and format them separately.

In this article by Jon Skeet (http://www.yoda.arachsys.com/csharp/floatingpoint.html) he provides a link to his DoubleConverter.cs routine that should do exactly what you want. Skeet also refers to this at http://stackoverflow.com/questions/389993/extracting-mantissa-and-exponent-from-double-in-c.

ebpower
Thanks for the link, I've tried the code from Jon already, however for my purpose it's kind of too exact; for instance, 0.1 does not show as 0.1 (which is technically correct, but not what I'd need)...
Lucero
Try rounding it?
Brian
Yeah, but you see, the whole point of Jon's code is to display the number EXACTLY and this is kind of too much for my case. Rounding as done by the runtime when doing ToString() is just fine for me, and that's probably also why most solutions proposed here use ToString() as base for further processing.
Lucero
So take Jon's code and tailor it.
ebpower
+1  A: 

The obligatory Logarithm-based solution. Note that this solution, because it involves doing math, may reduce the accuracy of your number a little bit. Not heavily tested.

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

Edit: If the decimal point crosses non-zero part of the number, this algorithm will fail miserably. I tried for simple and went too far.

Brian
Thanks for the input, I'll try to implement a fully working solution like this and compare it to mine.
Lucero
+1  A: 

try this one:

public static string DoubleToFullString(double value, 
                                        NumberFormatInfo formatInfo)
{
    string[] valueExpSplit;
    string result, decimalSeparator;
    int indexOfDecimalSeparator, exp;

    valueExpSplit = value.ToString("r", formatInfo)
                         .ToUpper()
                         .Split(new char[] { 'E' });

    if (valueExpSplit.Length > 1)
    {
        result = valueExpSplit[0];
        exp = int.Parse(valueExpSplit[1]);
        decimalSeparator = formatInfo.NumberDecimalSeparator;

        if ((indexOfDecimalSeparator 
             = valueExpSplit[0].IndexOf(decimalSeparator)) > -1)
        {
            exp -= (result.Length - indexOfDecimalSeparator - 1);
            result = result.Replace(decimalSeparator, "");
        }

        if (exp >= 0) result += new string('0', Math.Abs(exp));
        else
        {
            exp = Math.Abs(exp);
            if (exp >= result.Length)
            {
                result = "0." + new string('0', exp - result.Length) 
                             + result;
            }
            else
            {
                result = result.Insert(result.Length - exp, decimalSeparator);
            }
        }
    }
    else result = valueExpSplit[0];

    return result;
}
najmeddine
+2  A: 

This is a string parsing solution where the source number (double) is converted into a string and parsed into its constituent components. It is then reassembled by rules into the full-length numeric representation. It also accounts for locale as requested.

Update: The tests of the conversions only include single-digit whole numbers, which is the norm, but the algorithm also works for something like: 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(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 str = input.ToString().ToUpper();

        // if string representation was collapsed from scientific notation, just return it:
        if (!str.Contains("E")) return str;

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

        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();
    }
}
Paul Sasik
That seems pretty similar to the one I've posted on Oct 9, at least conceptually it seems to be the same.
Lucero
Huh. Honestly, i noticed that it got voted down so i didn't examine the code very closely. i did read it just now and you're right. They are close, i just chose to not use RegEx in my process and did my own string parsing. Have you tested this solution? It's a complete console app.
Paul Sasik
Not yet, will do it soon... ;)
Lucero
This one is more easily read, as you don't have to grok the regex.
Gregory
+1 LOL @ "grok the regex" i love it. i will make it part of my development vernacular! Thanks.
Paul Sasik
Well, the Regex one at least has nicely named groups instead of unspecific indexes in some arrays... ;)
Lucero
+1  A: 

Being millions of programmers world wide, it's always a good practice to try search if someone has bumped into your problem already. Sometimes there's solutions are garbage, which means it's time to write your own, and sometimes there are great, such as the following:

http://www.yoda.arachsys.com/csharp/DoubleConverter.cs

(details: http://www.yoda.arachsys.com/csharp/floatingpoint.html)

Itay
This is the same as already posted by ebpower, see the comments there... ;)
Lucero
A: 

i think you need only to use Iformat with ToString(doubleVar,System.Globalization.NumberStyles.Number)

ex: double x=double.MaxValue; x.ToString(x,System.Globalization.NumberStyles.Number);

mohamed abdo
That doesn't even compile, can you post something that compiles?
Lucero