3
\$\begingroup\$

I wrote a C# program that converts decimal to string. It is not the best but it works. I'm looking for inputs from others on how can it be improved? One aspect that baffled me was how to find out total number of digits after decimal point? Right now I've to pass it as a separate parameter. It is in C#, but I'm open to any programming language. credit goes to all authors that I found online and little bit to me

    static void Main(string[] args)
    {           
        string s = decToStr(123456.789M,3);
        Console.WriteLine(s);
        Console.ReadLine();            
    }

    static string decToStr(decimal d,double totaldec)
    {            
        if (d == 0)
            return "0";
        int i = (int)d;
        decimal fracPart = d - i;
        int fracPartInt = 0;

        StringBuilder strResult = new StringBuilder();
        StringBuilder fracStrResult = new StringBuilder();         

        strResult = ProcessInt(strResult,i);
        //not sure how to find total number of digts after decimal point 
        fracPartInt = (int)(fracPart * (decimal)Math.Pow(10, totaldec));

        fracStrResult = ProcessInt(fracStrResult, fracPartInt);

        strResult.Append(".").Append(fracStrResult);         

        return strResult.ToString();
    }

    static StringBuilder ProcessInt(StringBuilder sb, int i)
    {
        string digits = "0123456789";
        while (i > 0)
        {                
            sb.Insert(0, digits[i % 10]);
            i = i / 10;
        }

        return sb;

    }
\$\endgroup\$
2
\$\begingroup\$

You don't really need to know the length of the fractional part. Simply separate the fractional from the integral and handle each one separately.

static string DecToStr(decimal value)
{
    int integral = 0;
    decimal fractional = 0;
    SplitDecimal(value, out integral, out fractional);
    StringBuilder sb = new StringBuilder();
    while(integral > 0)
    {
        sb.Insert(0,integral % 10);
        integral /= 10;
    }
    sb.Append('.');
    while(fractional != 0)
    {
        int temp = 0;
        SplitDecimal(fractional * 10, out temp, out fractional);
        sb.Append(temp);
    }
    return sb.ToString();
}
static void SplitDecimal(decimal value,out int integral,out decimal fractional)
{
    integral = (int)value;
    fractional = value - integral;
}

EDIT added check for negative

static string DecToStr(decimal value)
{
    int integral = 0;
    decimal fractional = 0;
    StringBuilder sb = new StringBuilder();
    if(value < 0)
    {
        value *= -1;
        sb.Append('-');
    }
    SplitDecimal(value, out integral, out fractional);
    while(integral > 0)
    {
        sb.Insert(0,integral % 10);
        integral /= 10;
    }
    sb.Append('.');
    while(fractional != 0)
    {
        int temp = 0;
        SplitDecimal(fractional * 10, out temp, out fractional);
        sb.Append(temp);
    }
    return sb.ToString();
}
static void SplitDecimal(decimal value,out int integral,out decimal fractional)
{
    integral = (int)value;
    fractional = value - integral;
}
\$\endgroup\$
  • \$\begingroup\$ @gmailuser - For completeness I added a check for negative numbers. \$\endgroup\$ – tinstaafl Jun 13 '18 at 16:45
2
\$\begingroup\$

If you want to take the integral and fractional parts of a decimal, use Decimal.Truncate(Decimal) instead of a cast to int: Decimal can represent much much larger numbers than int, and you code will presently fail for such large numbers.

decimal intergralPart = Decimal.Truncate(d);
decimal fractionalPart = d - integralPart;

Simply dividing by 10 won't work anymore, because of course decimal will keep the fraction part; rather, you can subtract the last 'digit' before dividing (see code below).


Note that everyone has changed the name of i from your code: i screams 'counter'; integralPart and such are much better names. d having a sensible name is even more important, because it is part of the API exposed by the method. Good naming is really important.


Your method will always leave a "." after the integral part, even if there is no fractional part, except for the special case of 0. This seems inconsistent, and may or may not be a concern. (it's not what Decimal.ToString() does, for instance).


You've not asked about performance, but I'll just point out that inserting at the start of a string-builder is more expensive than adding to the end (linear vs. amortised constant). This means you code is actually quadratic in the string-length of the value. You could make it linear by writing the integral part backwards, and then reversing it, but who cares.


Code based on tinstaalf's code, but using decimal integral and fractional parts (should hopefully work for any decimal value):

static string DecToStr(decimal value)
{
    StringBuilder sb = new StringBuilder();

    bool negative = value < 0M;

    if (negative)
    {
        value = -value; // decimals are symetrical, so this can't go wrong
    }

    decimal integral;
    decimal fractional;
    SplitDecimal(value, out integral, out fractional);

    if (integral > 0M)
    {
        while (integral > 0)
        {
            decimal digit = Decimal.Truncate(integral % 10);
            sb.Insert(0, digit);
            integral = (integral - digit) / 10M;
        }
    }
    else
    {
        sb.Append('0'); // explicitly add a "0" if the integral part is zero
    }

    if (negative)
    {
        sb.Insert(0, '-'); // add a - if we were negative
    }

    if (fractional != 0) // only add a fraction part if we have one to add
    {
        sb.Append('.'); // only add a . if we have a fraction part

        while (fractional != 0)
        {
            decimal digit; // `digit` is perhaps more meaningful than `temp`
            SplitDecimal(fractional * 10, out digit, out fractional);
            sb.Append(digit);
        }
    }

    return sb.ToString();
}

static void SplitDecimal(decimal value, out decimal integral, out decimal fractional)
{
    integral = Decimal.Truncate(value);
    fractional = value - integral;
}
\$\endgroup\$
  • \$\begingroup\$ Wouldn't it be faster to sb.Append rather than sb.Insert, and then reverse the string once all the "inserting" is complete? \$\endgroup\$ – Rick Davin Jun 14 '18 at 13:37
  • \$\begingroup\$ @RickDavin I'd expect so, but I can't be bothered to benchmark it. With this code, you'd only be inverting the integral part (i.e. fractional part is already appended 'correctly'); the OP's code actually lends itself to this optimisation better. \$\endgroup\$ – VisualMelon Jun 14 '18 at 16:39
1
\$\begingroup\$

Minor comments

I think double totaldec should be int

I would not pass StringBuilder to ProcessInt. Just new it in ProcessInt.

\$\endgroup\$
1
\$\begingroup\$

A couple of things:

1) The algorithm doesn't handle negative values.

2) If I try decToStr(1.01m, 2) or decToStr(1.001m, 3), I get 1.1 for both. ProcessInt(...) can't handle preceding zeros.


This algorithm seems odd because of the StringBuilder argument and the string digits local variable:

static StringBuilder ProcessInt(StringBuilder sb, int i)
{
  string digits = "0123456789";
  while (i > 0)
  {
    sb.Insert(0, digits[i % 10]);
    i = i / 10;
  }

  return sb;

}

Why not just:

static StringBuilder ProcessInt(int i)
{
  StringBuilder sb = new StringBuilder();

  while (i > 0)
  {
    sb.Insert(0, i % 10);
    i = i / 10;
  }

  return sb;
}

You could then reduce the main algorithm to:

static string decToStr(decimal d, double totaldec)
{
  if (d == 0)
    return "0";
  int i = (int)d;
  decimal fracPart = d - i;
  //not sure how to find total number of digts after decimal point 
  int fracPartInt = (int)(fracPart * (decimal)Math.Pow(10, totaldec));
  return ProcessInt(i).Append('.').Append(ProcessInt(fracPartInt)).ToString();
}

An alternative approach could be:

  string DtoS(decimal input)
  {
    decimal value = Math.Abs(input);
    int separatorIndex = 0;
    int index = 0;

    while (value >= 1)
    {
      value /= 10;
      separatorIndex++;
    }

    StringBuilder builder = new StringBuilder();
    while (index <= separatorIndex || value % 1 > 0)
    {
      value *= 10;
      int vi = (int)value;
      builder.Append(vi);
      value -= vi;
      index++;
    }

    if (separatorIndex > 0)
    {
      builder.Insert(separatorIndex, '.');
    }
    else if (separatorIndex == 0)
    {
      builder.Insert(0, "0.");
    }

    if (input < 0)
    {
      builder.Insert(0, '-');
    }

    return builder.ToString();
  }
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.