Friday, August 19, 2011

Sipping and the benefits of Decoupling

Sipping: Systemically Incremental Programming


What do I mean by "Decoupling"
From Wikipedia:

In software development, the term “decoupling” is used to identify the separation of software blocks that shouldn't depend on each other... Special design techniques allow software designers to have as few dependencies as possible. This typically reduces the risk of malfunction in one part of a system when the other part changed. It also forces the developer to focus on one thing at a time.
Decoupling lowers or minimizes Coupling.
Coupling is characterized as follows:
Coupling is usually contrasted with cohesion. Low coupling often correlates with high cohesion, and vice versa....[where] cohesion is a measure of how strongly-related the functionality expressed by the source code of a software module is.
In my humble opinion, the single most destructive force in software design is tightly coupled functionality.
In the wikipedia article on decoupling, it states that "special design techniques allow software designers to have as few dependancies as possible".  Sipping is just such a technique.  Specifically, I claimed that Sipping will "encourage" your code to be:

  • Simpler and more elegant, start to finish
  • More stable and bug free, out of the gates
  • Easier to read
  • Easier to change as conditions require
  • Easier to maintain by you (or others)
  • Better software at the end of the day for the end user

    and last, but not least
  • Much, much faster to produce in the first place


Code Efficiency
Before going any further, I want to point out that Sipping specifically (and consciously) does not always produce the most efficient code.  In other words, performance is not the primary objective of Sipping.  If performance is needed, it is quite possible that a more direct and optimized approach may be necessary.  I have found though, that in most cases, performance is not nearly as critical as readability, stability, extensibility, etc.
For further information on this, please see Sipping, Code Efficiency and Preemptive Optimization.

Unnecessary (or Inadvertent) Coupling
Let's take a simple example where, if developed as a unit, inadvertent coupling might occur, even if written by a "strong" developer.
I found this article on CSharp corner about Converting Numbers into Words:
http://www.c-sharpcorner.com/UploadFile/b942f9/6362/
The class that implements this is a console application, included here:

using System;
class Program
{
    static void Main()
    {
        string input;
        int number;
        bool isValid;
        bool isUK = false;
        Console.WriteLine("\nEnter '0' to quit the program at any time\n");
        while (true)
        {
            Console.Write("\nUse UK numbering y/n : ");
            input = Console.ReadLine();
            if (!(input.ToLower() == "y" || input.ToLower() == "n"))
                Console.WriteLine("\n  Must be 'y' or 'n', please try again\n");
            else
            {
                if (input.ToLower() == "y") isUK = true;
                Console.WriteLine("\n");
                break;
            }
        }
        do
        {
            Console.Write("Enter integer : ");
            input = Console.ReadLine();
            isValid = int.TryParse(input, out number);
            if (!isValid)
                Console.WriteLine("\n  Not an integer, please try again\n");
            else
                Console.WriteLine("\n  {0}\n", NumberToText(number, isUK));
        }
        while (!(isValid && number == 0));
        Console.WriteLine("\nProgram ended");
    }

    public static string NumberToText(int number, bool isUK)
    {
        if (number == 0) return "Zero";
        string and = isUK ? "and " : ""// deals with UK or US numbering
        if (number == -2147483648) return "Minus Two Billion One Hundred " + and +
        "Forty Seven Million Four Hundred " + and + "Eighty Three Thousand " +
        "Six Hundred " + and + "Forty Eight";
        int[] num = new int[4];
        int first = 0;
        int u, h, t;
        System.Text.StringBuilder sb = new System.Text.StringBuilder(); 
        if (number < 0)
        {
            sb.Append("Minus ");
            number = -number;
        }
        string[] words0 = {"""One ""Two ""Three ""Four ""Five ""Six ",
            "Seven ""Eight ""Nine "};
        string[] words1 = {"Ten ""Eleven ""Twelve ""Thirteen ""Fourteen ",
             "Fifteen ""Sixteen ""Seventeen ""Eighteen ""Nineteen "};
        string[] words2 = {"Twenty ""Thirty ""Forty ""Fifty ""Sixty ",
             "Seventy ""Eighty ""Ninety "};
        string[] words3 = { "Thousand ""Million ""Billion " };
        num[0] = number % 1000;           // units
        num[1] = number / 1000;
        num[2] = number / 1000000;
        num[1] = num[1] - 1000 * num[2];  // thousands
        num[3] = number / 1000000000;     // billions
        num[2] = num[2] - 1000 * num[3];  // millions
        for (int i = 3; i > 0; i--)
        {
            if (num[i] != 0)
            {
                first = i;
                break;
            }
        }
        for (int i = first; i >= 0; i--)
        {
            if (num[i] == 0) continue;
            u = num[i] % 10;              // ones
            t = num[i] / 10;
            h = num[i] / 100;             // hundreds
            t = t - 10 * h;               // tens
            if (h > 0) sb.Append(words0[h] + "Hundred ");
            if (u > 0 || t > 0)
            {
                if (h > 0 || i < first) sb.Append(and);
                if (t == 0)
                    sb.Append(words0[u]);
                else if (t == 1)
                    sb.Append(words1[u]);
                else
                    sb.Append(words2[t - 2] + words0[u]);
            }
            if (i != 0) sb.Append(words3[i - 1]);
        }
        return sb.ToString().TrimEnd();
    }
}

This class is about 100 lines of code.  It supports the following features:
  • Handles any Int32 number
  • Handles both positive and negative numbers
  • Both British and English Phrasing are supported
Some items that could be criticized about it are:
  • While this method totally works, and is efficient in it's design, I'm not exactly sure how it works.
  • The actual algorithm that constructs the words is somewhat cryptic, and not easily understood.
  • I think that most developers would struggle to write a method of this complexity.
  • In fact, many developers (myself included) would struggle to even understand how it works, let along come up with it ourselves
  • It would be harder still to write such a method without any bugs, and to be confident that the final design really does work as expected in all situations.
  • The entire process is "encapsulated" into one method.  This tightly couples all of the features supported into one process that must be (to some extent at least) developed, and understood as a unit.
  • I can't see an easy way to "evolve" this method.  In other words, again, because of it's underlying design, it appears that you'd have to understand the entire problem, and then write the entire method all at once.
  • If any bugs were found, most developers would have a hard time fixing them
  • The article states that the method could easily be extended to support bigger numbers and decimals, but exactly how this would be done is not immediately apparent to me.
Sipping and a Decoupled alternative
This problem would be challenging for most developers to write from scratch.  Using the process of sipping however, the problem can be broken down into a series of smaller steps, each of which is simple and easily understood.  In this way, we will take the relatively complex problem solved above, and simplify it.
In thinking about the underlying problem, I made the following observations:
  • The problem should be relatively easily "parsed" into SIPs
  • Each 3 digit sequence is identical.  eg. (365 is "Three Hundred Sixty Five", just as 365,000 is "Three Hundred Sixty Five [Thousand]", or Million, Billion, Etc)
  • This 3 digit sequence is the only thing that is "Modified" by British Phrasing.
  • Only the 1st digit needs to be looked at to determine if the number is negative.
In further thinking about the problem, I think that it can be divided into the following SIPs.
  1. Develop an abstract base class that can be used to parse a number into english words.
  2. Implement a "TensTranslator" that can handle the numbers between 0 and 99.
  3. Implement a "HundredsTranslator" that can handle any numbers from 0 to 999.
  4. Implement a "BigNumberTranslator" that can string together multiple HundredsTranslators to handle Thousands, Millions, Billions, Etc.
  5. Implement a NegativeNumberTranslator that can handle negative numbers.
  6. Modify the HundredsTranslator to handle US or British Phrasing.
By "evolving" the problem SIP by SIP as described above, we will get the same functionality, but hopefully with a more stable, easily understood and extensible algorithm.


Source Files


SIP 1: Abstract Base Class
Develop an abstract base class that can be used to parse a number into english words.
    1/// <summary>
    2/// This is the base class that all Translators will be based on
    3/// </summary>
    4public abstract class TranslatorBase
    5{
    6    public TranslatorBase(String input)
    7    {
    8        this.Input = input;
    9    }
   10
   11    public String Input { get; set; }
   12
   13    public abstract String Parse();
   14
   15    public override String ToString()
   16    {
   17        return this.Parse();
   18    }
   19}
All translating of numbers to words will be based on this simple abstract class.  In the 2nd SIP we will develop our first translator that will handle all numbers from 0 to 99.


SIP 2: Translating 0 to 99
This class will be used again and again and again by the rest of the system.

    1/// <summary>
    2/// This class will translate all numbers between 0 and 99 into words
    3/// </summary>
    4public class TensTranslator : TranslatorBase
    5{
    6    // Pass the result to the base class
    7    public TensTranslator(String input) : base(input) { }
    8
    9    // Ones (really 1-19) & Tens Arrays
   10    private String[] Ones = new String[] { "", "One", "Two", "Three", "Four", 
                   "Five", "Six", "Seven", "Eight", "Nine", "Ten", 
   11            "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", 
                   "Sixteen", "Seventeen", "Eighteen", "Nineteen" };
   12
   13    private String[] Tens = new String[] { "", "", "Twenty", "Thirty", "Fourty",
                  "Fifty", "Sixty", "Seventy", "Eighty", "Ninety" };
   14
   15    public override String Parse()
   16    {
   17        // Store the input as an int
   18        int inputAsInt = Int32.Parse(this.Input);
   19
   20        // If the input is not between 0 and 99, throw an exception
   21        if ((inputAsInt < 0) || (inputAsInt > 99)) 
                    throw new Exception("... can only handle numbers between 0 and 99");
   22
   23        // Translate the number into words.  
               // If the number is less than 20, we can use the ones array
   24        if (inputAsInt < 20) return this.Ones[inputAsInt];
   25
   26        // Otherwise, construct the number using the Tens and Ones array
   27        else
   28        {
   29          int ten = inputAsInt / 10;
   30          int one = inputAsInt % 10;
   31          return String.Format("{0} {1}", this.Tens[ten], this.Ones[one]).Trim();
   32        }
   33    }
   34}
The TensTranslator will handle any number between 0 and 99.  Notice how simple the class is.  This class will not handle Hundreds, Thousands, Millions, etc.  It will not even handle negative numbers.  But for all the numbers between 0 and 99, this class will handle it.
Writing this was easy.  It doesn't solve the entire problem, but it solves the first part of the problem.  We now have a translator that can translate any number under 100.  So far it's not nearly as functional as the original class, but was easy to develop, is easy to read, is totally stable, and will be easy to incorporate into additional SIPs to get the rest of the functionality that we want.
Using (and more importantly, testing) our new TensTranslator class is as simple as plugging it into the console app, similarly to how it was done in the original article:
    1using System;
    2using System.Text;
    3class Program
    4{
    5    static void Main()
    6    {
    7        Console.WriteLine("\nEnter '0' to quit the program at any time\n");
    8        do
    9        {
   10            Console.Write("Enter integer (0 to terminate): ");
   11            try
   12            {
   13                TranslatorBase translator = new TensTranslator(Console.ReadLine());
   14                Console.WriteLine("\n {0} is {1}\n", translator.Input, translator);
   15                if (translator.Input == "0") break;
   16            }
   17            catch (Exception ex)
   18            {
   19                Console.WriteLine("\n  ERROR: {0}\n", ex.Message);
   20            }
   21        }
   22        while (true);
   23        Console.WriteLine("\nProgram ended");
   24    }
   25}


SIP 3: HundredsTranslator - 0 to 999
Now that we've written and tested a 10's translator, we're ready to tackle bigger numbers.  The hundreds translator will use two 10's translators to handle any number up to 1000.

    1/// <summary>
    2/// Translate any 3 digit number between 0 and 999
    3/// </summary>
    4public class HundredsTranslator : TranslatorBase
    5{
    6    // Pad the input to ensure that this is always at least a 3 digit input string
    7    public HundredsTranslator(String input)
    8        : base(input.PadLeft(3, '0'))
    9    { }
   10
   11    public override String Parse()
   12    {
   13        // If the number is over 3 digits in length, we've got a problem
   14        if (this.Input.Length > 3) {
   15           throw new Exception("... can only handle numbers between 0 and 999");
   16        }
   17
   18        // Check if there's a hundreds column
   19        String hundredResult = String.Empty;
   20        if (this.Input.Substring(0, 1) != "0")
   21        {
   22            TensTranslator hundreds = new TensTranslator(this.Input.Substring(0, 1));
   23            hundredResult = String.Format("{0} Hundred", hundreds);
   24        }
   25
   26        // Parse the tens part of the input string
   27        TensTranslator tens = new TensTranslator(this.Input.Substring(1, 2));
   28        String tensResult = String.Format("{0}", tens);
   29
   30        // Return the english phrase
   31        return String.Format("{0} {1}", hundredResult, tensResult).Trim();
   32    }
   33}
The hundreds translator simply pads it's input to at least 3 digits.  In this way, 5 becomes 005, while 345 is left as 345.  
The algorithm then just checks if the first digit is anything other than 0.  If so, it creates a tens translator to parse it into One, Two, Three, etc and adds the word "Hundred" to it.  It then takes the remaining two digits and uses a TensTranslator to parse those into english as well.  It then returns the two strings (hundredResult and tensResult) as one, trimmed string.
We now have a Translator that can handle any number between 0 and 999.  We still can't handle Thousands, Millions, Billions, etc, negative numbers or British phrasing, but here again, it's simple, easily tested, easy to read and understand, and moves the problem further down the road.
In addition, because it uses the Tens translator, as we test this HundredsTranslator we are also further testing the TensTranslator.  If there are any issues in that first class, they will often be exposed during subsequent SIPs.  Now we just re-run the application, and should be able to enter any number up to one thousand.
Ready for SIP 4?


SIP 4: BigNumberTranslator - 0 through Quintillion.
Stringing together multiple HundredsTranslators, we can handle any number (of virtually any size)

    1/// <summary>
    2/// Handles numbers up to Quintillion
    3/// </summary>
    4public class BigNumberTranslator : TranslatorBase
    5{
    6    public BigNumberTranslator(String input)
    7        : base(input)
    8    { }
    9
   10    // Number group types
   11    private String[] NumberGroups = new String[] { "", "Thousand","Million","Billion", 
   12           "Trillion", "Quadrillion", "Quintillion" };
   13
   14    /// <summary>
   15    /// Parse the input string
   16    /// </summary>
   18    public override string Parse()
   19    {
   21        // Pad the left side of the string with 0's
   22        String input = this.Input.PadLeft(this.NumberGroups.Length * 3, '0');
   23
   24        // Iterate over and add each group of words to result, 3 digits at a time
   25        StringBuilder result = new StringBuilder();
   26        foreach (String groupName in this.NumberGroups)
   27        {
   28            // Remove the last 3 digits from the input string
   29            String group = input.Substring(input.Length - 3, 3);
   30            input = input.Substring(0, input.Length - 3);
   31
   32            // Add this group to the result
   33            HundredsTranslator hundredGroup = new HundredsTranslator(group);
   34
   35            // If this number has a name, insert it into the beginning of the result
   36            if (!String.IsNullOrEmpty(hundredGroup.ToString()))
   37            {
   38                // Insert a comma if there are already words
   39                if (result.Length > 0) result.Insert(0, ", ");
   40                result.Insert(0, String.Format("{0} {1}", group, groupName));
   41            }
   42        }
   43
   44        // Return the result
   45        return result.ToString();
   46    }
   47}
The BigNumberTranslator simply strings together HundredsTranslators, parsing each group of numbers, 3 at a time from the end of the input string passed to it.  Once again, we've made an incremental improvement to the overall system, building on our previous work.  This new class allows us to handle any number (up to 999 Quintillion) - and we could add support for larger numbers by simply adding additional group names to the end of the NumberGroups array.  How much easier could it be?
At this point, we still can't handle negative numbers, or British Phrasing.  Both of these are easily solved, each in their own SIPs of course!


SIP 5: Negative Numbers

    1/// <summary>
    2/// Adds the ability to handle negative numbers
    3/// </summary>
    4public class NegativeCapableTranslator : TranslatorBase
    5{
    6    public NegativeCapableTranslator(String input) : base(input) { }
    7
    8    public override string Parse()
    9    {
   10        // Check if the input starts with negative
   11        String input = this.Input;
   12        bool negative = input.StartsWith("-");
   13
   14        // Trim the negative
   15        if (negative) input = input.Substring(1);
   16
   17        // return the big number
   18        return String.Format("{0}{1}", (negative ? "Minus " : ""), 
                       new BigNumberTranslator(input));
   19    }
   20}
This NegativeNumberTranslator builds on the BigNumberTranslator developed in SIP 4 and simply adds the process of checking if the first character is - symbol, in which case it inserts the word "Minus" before the output of the BigNumberTranslator, which gets passed the rest of the input string (as a positive number).


SIP 6: British Phrasing
We can add british phrasing by simply modifying the HundredsTranslator.  If this translator had a static bool property for BritishPhrasing, we could simply insert the word "and" between the Hundred and Tens part - as follows.  Here again, you can see that this is simply a small, systemic change to the existing code that adds support for British Phrasing, but doesn't substantially change the rest of the system we've put in place.

    1/// <summary>
    2/// Translate any 3 digit number between 0 and 999
    3/// </summary>
    4public class HundredsTranslator : TranslatorBase
    5{
    6    // Pad the input to ensure that this is always at least a 3 digit input string
    7    public HundredsTranslator(String input)
    8        : base(input.PadLeft(3, '0'))
    9    { }
   10
   11    public static bool BritshPhrasing { get; set; }
   12
   13    public override String Parse()
   14    {
   15        // If the number is over 3 digits in length, we've got a problem
   16        if (this.Input.Length > 3) 
                 throw new Exception("... can only handle numbers between 0 and 999");
   17
   18        // Check if there's a hundreds column
   19        String hundredResult = String.Empty;
   20        if (this.Input.Substring(0, 1) != "0")
   21        {
   22            TensTranslator hundreds = new TensTranslator(this.Input.Substring(0, 1));
   23            hundredResult = String.Format("{0} Hundred", hundreds);
   24        }
   25
   26        // parse the tens part of the input string
   27        TensTranslator tens = new TensTranslator(this.Input.Substring(1, 2));
   28        String tensResult = String.Format("{0}", tens);
   29
   30        // Add british phrasing (if needed)
   31        if (!String.IsNullOrEmpty(hundredResult) && 
                     !String.IsNullOrEmpty(tensResult) && BritshPhrasing)
   32        {
   33            hundredResult += " and";
   34        }
   35
   36        // Return the english phrase
   37        return String.Format("{0} {1}", hundredResult, tensResult).Trim();
   38    }
   39}


Conclusion
Each time we added functionality, all we had to do to our console app was change the "new TensTranslator(...);" to "new HundredsTranslator(...);", and then to "new BigNumberTranslator(...);", etc.  After each SIP, we can fully test the new functionality, and check in our "improved" system.  And by the end of the 6th SIP, we've reproduced the original functionality in, as I see it, a simpler, more easily understood way.  In writing these classes, it took about 1 hour to write and test them - and once completed, I have a very high degree of confidence that they work as designed - and will continue to do so.  Further, if I want to add additional functionality, the framework designed should be easily extended and/or improved.
While in this case I ended up with more code than the original application (~200 lines, vs ~100 lines), this is actually the opposite of my common experience.  I find that usually I end up with less code (often substantially less code) when using the Sipping method.


The final code:

    1using System;
    2using System.Text;
    3class Program
    4{
    5    static void Main()
    6    {
    7        Console.WriteLine("\nEnter '0' to quit the program at any time\n");
    8        while (true)
    9        {
   10            Console.Write("\nUse UK numbering y/n : ");
   11            String input = String.Format("{0}", Console.ReadKey().KeyChar);
   12            if (!(input.ToLower() == "y" || input.ToLower() == "n"))
   13                Console.WriteLine("\n  Must be 'y' or 'n', please try again\n");
   14            else
   15            {
   16                if (input.ToLower() == "y") HundredsTranslator.BritshPhrasing = true;
   17                Console.WriteLine("\n");
   18                break;
   19            }
   20        }
   21        do
   22        {
   23            Console.Write("Enter integer (0 to terminate): ");
   24            try
   25            {
   26                TranslatorBase translator = 
                             new NegativeCapableTranslator(Console.ReadLine());
   27                Console.WriteLine("\n {0} is {1}\n", translator.Input, translator);
   28                if (translator.Input == "0") break;
   29            }
   30            catch (Exception ex)
   31            {
   32                Console.WriteLine("\n  ERROR: {0}\n", ex.Message);
   33            }
   34        }
   35        while (true);
   36        Console.WriteLine("\nProgram ended");
   37    }
   38
   39    /// <summary>
   40    /// This is the base class that all Translators will be based on
   41    /// </summary>
   42    public abstract class TranslatorBase
   43    {
   44        public TranslatorBase(String input)
   45        {
   46            this.Input = input;
   47        }
   48
   49        public String Input { get; set; }
   50        public abstract String Parse();
   51
   52        public override string ToString()
   53        {
   54            return String.Format("{0}", this.Parse());
   55        }
   56    }
   57
   58    /// <summary>
   59    /// This class will translate all numbers between 0 and 99 into words
   60    /// </summary>
   61    public class TensTranslator : TranslatorBase
   62    {
   63        // Pass the result to the base class
   64        public TensTranslator(String input) : base(input) { }
   65
   66        // Arrays
   67        private String[] Ones = new String[] { "", "One", "Two", "Three", "Four", 
                       "Five", "Six", "Seven", "Eight", "Nine", "Ten", 
   68                "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", 
                       "Sixteen", "Seventeen", "Eighteen", "Nineteen" };
   69
   70        private String[] Tens = new String[] { "", "", "Twenty", "Thirty", "Fourty", 
                       "Fifty", "Sixty", "Seventy", "Eighty", "Ninety" };
   71
   72        private int InputAsInt { get; set; }
   73
   74        public override String Parse()
   75        {
   76            // Store the input as an int
   77            this.InputAsInt = Int16.Parse(this.Input);
   78
   79            // If the input is not between 0 and 99, throw an exception
   80            if ((this.InputAsInt < 0) || (this.InputAsInt > 99)) 
                       throw new Exception("... can only handle numbers between 0 and 99");
   81
   82            // Translate the number into words.  
                   // If the number is less than 20, we can use the ones array
   83            if (this.InputAsInt < 20) return this.Ones[this.InputAsInt];
   84
   85            // Otherwise, construct the number using the Tens and Ones array
   86            else
   87            {
   88                int ten = this.InputAsInt / 10;
   89                int one = this.InputAsInt % 10;
   90                return String.Format("{0} {1}", this.Tens[ten], this.Ones[one]).Trim();
   91            }
   92        }
   93    }
   94
   95    /// <summary>
   96    /// Translate any 3 digit number between 0 and 999
   97    /// </summary>
   98    public class HundredsTranslator : TranslatorBase
   99    {
  100        // Pad the input to ensure that this is always at least a 3 digit input string
  101        public HundredsTranslator(String input)
  102            : base(input.PadLeft(3, '0'))
  103        { }
  104
  105        public static bool BritshPhrasing { get; set; }
  106
  107        public override String Parse()
  108        {
  109            // If the number is over 3 digits in length, we've got a problem
  110            if (this.Input.Length > 3) 
                       throw new Exception("... can only handle numbers between 0 and 999");
  111
  112            // Check if there's a hundreds column
  113            String hundredResult = String.Empty;
  114            if (this.Input.Substring(0, 1) != "0")
  115            {
  116               TensTranslator hundreds = new TensTranslator(this.Input.Substring(0,1));
  117                hundredResult = String.Format("{0} Hundred", hundreds);
  118            }
  119
  120            // parse the tens part of the input string
  121            TensTranslator tens = new TensTranslator(this.Input.Substring(1, 2));
  122            String tensResult = String.Format("{0}", tens);
  123
  124            // Add british phrasing
  125            if (!String.IsNullOrEmpty(hundredResult) && 
                        !String.IsNullOrEmpty(tensResult) && BritshPhrasing)
  126            {
  127                hundredResult += " and";
  128            }
  129
  130            // Return the english phrase
  131            return String.Format("{0} {1}", hundredResult, tensResult).Trim();
  132        }
  133    }
  134
  135    /// <summary>
  136    /// Handles numbers up to Quintillion
  137    /// </summary>
  138    public class BigNumberTranslator : TranslatorBase
  139    {
  140        public BigNumberTranslator(String input)
  141            : base(input)
  142        { }
  143
  144        // Number group types
  145        private String[] NumberGroups = new string[] { "", "Thousand", "Million", 
                  "Billion", "Trillion", "Quadrillion", "Quintillion" };
  146
  147        /// <summary>
  148        /// Parse the input string
  149        /// </summary>
  150        /// <returns></returns>
  151        public override string Parse()
  152        {
  153            // Parse the input until there is no input left
  154            StringBuilder result = new StringBuilder();
  155
  156            // Padd the left side of the string with 0's
  157            String input = this.Input.PadLeft(this.NumberGroups.Length * 3, '0');
  158
  159            // Iterate over each group and add the words to the list
  160            foreach (String groupName in this.NumberGroups)
  161            {
  162                // Remove the last 3 digits from the input string
  163                String group = input.Substring(input.Length - 3, 3);
  164                input = input.Substring(0, input.Length - 3);
  165
  166                // Add this group to the result
  167                HundredsTranslator hundred = new HundredsTranslator(group);
  168                group = String.Format("{0}", hundred);
  169
  170                // If this number has a name, insert it into beginning of the result
  171                if (!String.IsNullOrEmpty(group))
  172                {
  173                    if (result.Length > 0) result.Insert(0, ", ");
  174                    result.Insert(0, String.Format("{0} {1}", group, groupName));
  175                }
  176            }
  180
  181            // Return the result
  182            return result.ToString();
  183        }
  184    }
  185
  186    /// <summary>
  187    /// Adds the ability to handle negative numbres
  188    /// </summary>
  189    public class NegativeCapableTranslator : TranslatorBase
  190    {
  191        public NegativeCapableTranslator(String input) : base(input) { }
  192
  193        public override string Parse()
  194        {
  195            // Check if the input starts with negative
  196            String input = this.Input;
  197            bool negative = input.StartsWith("-");
  198
  199            // Trim the negative
  200            if (negative) input = input.Substring(1);
  201
  202            // return the big number
  203            return String.Format("{0}{1}", (negative ? "Minus " : ""), 
                        new BigNumberTranslator(input));
  204        }
  205    }
  206}



No comments:

Post a Comment