3

I need to format a double value so that it fits within a field of 13 characters. Is there a way to do this with String.Format or am I stuck with character-by-character work?

Edits: (hopefully they will stay this time)

With cases greater than a trillion I am to report an error. It's basically a calculator interface.

My own answer:

private void DisplayValue(double a_value)
{
    String displayText = String.Format("{0:0." + "".PadRight(_maxLength, '#') + "}", a_value);

    if (displayText.Length > _maxLength)
    {
        var decimalIndex = displayText.IndexOf('.');
        if (decimalIndex >= _maxLength || decimalIndex < 0)
        {
            Error();
            return;
        }

        var match = Regex.Match(displayText, @"^-?(?<digits>\d*)\.\d*$");
        if (!match.Success)
        {
            Error();
            return;
        }

        var extra = 1;
        if (a_value < 0)
            extra = 2;

        var digitsLength = match.Groups["digits"].Value.Length;
        var places = (_maxLength - extra) - digitsLength;

        a_value = Math.Round(a_value, places);

        displayText = String.Format("{0:0." + "".PadRight(_maxLength, '#') + "}", a_value);

        if (displayText.Length > _maxLength)
        {
            Error();
            return;
        }
    }

    DisplayText = displayText;
}
Jordan
  • 9,642
  • 10
  • 71
  • 141
  • It's easy to limit N characters before decimal and/or M characters after decimal. However ensuring N + M = 12 (or 13 if one is 0) will be difficult without doing it yourself. –  Dec 05 '13 at 15:12
  • That's what I was worried about. Thanks. – Jordan Dec 05 '13 at 15:15
  • Note: I wouldn't call `if(len)...substring(0,len)...if(EndsWith('.'))Replace(".","")` character by character exactly, but it's certainly more work than a simple format string. Not sure what you do for >= 1 trillion though. (exponent format?) `"{0:E6}"` might work (ugly though) –  Dec 05 '13 at 15:20
  • I edited this post with that case and it didn't take. With this requirement I would report an error. It's basically a calculator interface. – Jordan Dec 05 '13 at 15:21
  • In your maximum string size, do you include decimal and thousands separators or only the numbers? (Is "1,234.567" 7 or 9 characters?) – Danny T. Dec 05 '13 at 15:56
  • Just decimal point and minus sign. I'm having trouble preventing `String.Format` from using scientific notation. – Jordan Dec 05 '13 at 16:02

3 Answers3

2

If this is calculator, then you can not use character-by-character method you mention in your question. You must round number to needed decimal places first and only then display it otherwise you could get wrong result. For example, number 1.99999 trimmed to length of 4 would be 1.99, but result 2 would be more correct.

Following code will do what you need:

int maxLength = 3;
double number = 1.96;
string output = null;
int decimalPlaces = maxLength - 2; //because every decimal contains at least "0."
bool isError = true;

while (isError && decimalPlaces >= 0)
{
    output = Math.Round(number, decimalPlaces).ToString();
    isError = output.Length > maxLength;
    decimalPlaces--;
}

if (isError)
{
    //handle error
}
else
{
    //we got result
    Debug.Write(output);
}
Kaspars Ozols
  • 6,967
  • 1
  • 20
  • 33
  • One could argue a calculator should normally only truncate numbers (to avoid cumulative rounding errors) and have round as a function when necessary... (Most programming languages truncate by default unless you choose to round for example) But ya, a mixture of formatted rounding with truncation will be a nightmare to deal with. –  Dec 05 '13 at 16:19
  • I'm only rounding in the display. I'm keeping the actual number underneath. – Jordan Dec 05 '13 at 16:25
  • 1
    Note: `5000` for `number` will give an infinite loop here. –  Dec 05 '13 at 16:29
  • ebyrob, you are partialy right. Instead of infinite loop there would be an exception. I edited my answer to fix this. – Kaspars Ozols Dec 05 '13 at 16:42
  • @KasparsOzols sorry I was thinking of the round that treats negative digits as places to left of decimal... Now I can't seem to find it, maybe it was in SQL. –  Dec 05 '13 at 16:53
0

You have a lot formatting options using String.Format, just specify format after placeholder like this {0:format}.

Complete example looks like this:

Console.WriteLine("Your account balance is {0:N2}.", value);

Output would be:

Your account balance is 15.34.

All of the options for numeric types are listed here:

http://msdn.microsoft.com/en-us/library/dwhawy9k(v=vs.110).aspx

Kaspars Ozols
  • 6,967
  • 1
  • 20
  • 33
  • I have Google searched myself before posting. I've already been through that page ad nauseum. "N" doesn't do what I want. I want to enforce a maximum string length which includes the numbers before and after the decimal. – Jordan Dec 05 '13 at 15:17
  • Then I have extra question for you. What should happen if the number itself is more than 13 digits? – Kaspars Ozols Dec 05 '13 at 15:18
  • Dang it! I edited this post minutes ago and it didn't take. With this requirement I would report an error. It's basically a calculator interface. – Jordan Dec 05 '13 at 15:20
0

This seems to work for me (but is hand-rolled):

    static string FormatDouble(double d)
    {
        int maxLen = 13;
        double threshold = Math.Pow(10, maxLen);

        if (d >= threshold || d <= 0 - (threshold/10))
            return "OVERFLOW";
        string strDisplay = "" + d;
        if (strDisplay.Length > maxLen )
            strDisplay = strDisplay.Substring(0, maxLen);
        if (strDisplay.EndsWith("."))
            strDisplay = strDisplay.Replace(".", "");
        return strDisplay;
    }

Let me know if it gives you trouble with scientific notation creeping in. I believe the format "{0:R}" should help you avoid that explicitly.

Also, I wasn't sure if you were including +/- sign in digit count or if that was in a separate UI slot.

The theory on rounding here is that, yes, "" + d might round some things, but in general it's going to be many more digits out than are ever displayed so it shouldn't matter. So this method should always truncate.

Here's a solution that does rounding. (I couldn't think of a non-mathematical way to do it):

    static string FormatDouble(double d)
    {
        int maxLen = 13;
        int places = (int)Math.Max(Math.Log10(Math.Abs(d)), 0);
        places += (d == Math.Abs(d) ? 1 : 2);
        if (places > maxLen || places < 1 - maxLen)
            return "OVERFLOW";
        if (Math.Floor(d) == d) ++places;  // no decimal means one extra spot 
        d = Math.Round(d, Math.Max(maxLen - places - 1, 0));
        return string.Format("{0:R}", d);
    }

Note: I still think your users might appreciate seeing something closer to what is being stored in the underlying memory than what is often typical of calculators. (I especially hate the ones that can turn 0.99 into 1.01) Either way, you've got at least 3 solutions now so it's up to you.