Public
NotInheritable
Class
NumericStrings
Const
MAX_DECIMAL_VALUE
As
Decimal
= 2147483647.2147483647D
Shared
DecimalSeparator
String
= System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator
GroupSeparator
= System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator
SpaceString
=
" "
AndString
"and"
DashString
"-"
DecimalString
"point"
NegativeString
"negative"
Protected
Sub
New
()
End
Instead of using an array or dictionary to store the root numbers and their names, we can create an Enum which is already designed to neatly provide a series of string identifiers each with an associated numeric value:
Enum
RootNumbers
zero
one
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve
thirteen
fourteen
fifteen
sixteen
seventeen
eighteen
nineteen
twenty
thirty = 30
forty = 40
fifty = 50
sixty = 60
seventy = 70
eighty = 80
ninety = 90
hundred = 100
thousand = 1000
million = 1000000
billion = 1000000000
This provides us with all of the root number words required to build a string for any number, up to the maximum supported decimal value. If we wanted to declare this Enum as type Long, we could support larger numbers in the algorithm that builds the word string. Having stored all of the number words in an Enum, we'll now want to create a small helper method which allows us to retrieve a number word based on an integer value:
Function
GetRootNumberWord(
ByVal
number
Integer
)
Return
[
].GetName(
GetType
(RootNumbers), number)
With the Enum and helper method in place, we can write the basic algorithm to build a word string for a given integer value. First we'll look at the algorithm in whole, then break it down and explain its parts:
GetNumberWords(
If
number = 0
Then
GetRootNumberWord(0)
number < 0
NegativeString & SpaceString & GetNumberWords(System.Math.Abs(number))
Dim
result
System.Text.StringBuilder
digitIndex
= 9
While
digitIndex > 1
digitValue
CInt
(10 ^ digitIndex)
number \ digitValue > 0
result.Append(GetNumberWords(number \ digitValue))
result.Append(SpaceString)
result.Append(GetRootNumberWord(digitValue))
number = number
Mod
digitIndex = 9
digitIndex = 6
ElseIf
digitIndex = 3
digitIndex = 2
Else
digitIndex = 0
number > 0
result.Length > 0
result.Append(AndString)
number < 20
result.Append(GetRootNumberWord(number))
result.Append(GetRootNumberWord((number \ 10) * 10))
modTen
= number
10
modTen > 0
result.Append(DashString)
result.Append(GetRootNumberWord(modTen))
result.ToString
The method begins by checking the trivial case that the number provided is zero and simply returns the root number word for zero if it is. The method then checks to see if the number is negative, and if so, it creates a string containing the text used for negative numbers and then recalls itself passing the absolute value of the number. Once executing with a positive value for number, the method creates a new StringBuilder to hold the resulting string and a temporary "digitIndex" variable to track the digit of the supplied number currently being analyzed.
To handle decimal values we'll want to split the number at the decimal point and use the functionality described to generate the text of the significant portion of the number, but we'll need to create a different routine for generating the fractional portion of the decimal number. For this we'll want a method to simply return the root word for each digit of the number:
GetDigitWords(
digits()
Char
= number.ToString.ToCharArray
For
Each
digit
In
digits
result.Append(GetRootNumberWord(Val(digit)))
Next
This method is straight-forward and simply loops each character of the string representation of the number, getting the root number word for each character converted back into an integer. This process is fast enough for a series of up to ten digits that the conversion overhead is minimal and so the problem is not worth addressing mathematically. With that method in place, we can now create the GetNumberWords implementation for a decimal value which combines the results of the two previous methods into a single output string:
number > MAX_DECIMAL_VALUE
"Overflow"
parts()
= number.ToString.Split({DecimalSeparator}, StringSplitOptions.None)
result.Append(GetNumberWords(
(parts(0))))
parts.Length > 1
result.Append(DecimalString)
mantissaString
= parts(1).TrimEnd(
"0"
c)
mantissaString.Length > 9
mantissaString = mantissaString.Substring(0, 9)
result.Append(GetDigitWords(
(mantissaString)))
The method first checks the trivial case of a decimal value which is too large to parse. It then creates a StringBuilder to hold the result and splits the number into two strings at the decimal point. Finally, it proceeds to get the number words for the significant portion of the string and then, if there is a fractional portion, it adds the appropriate separators and gets the digit words for the value. Again the code takes advantage of a little string manipulation because it is fast enough when used sparingly.
The following example code builds a simple GUI for converting a decimal number into words using the NumericStrings class shown above:
Form1
Friend
LayoutPanel
FlowLayoutPanel
With
{.Dock = DockStyle.Fill}
WithEvents
Button1
Button
{.Text =
"Convert"
}
TextBox1
TextBox
Label1
Label
{.AutoSize =
True
Private
Form1_Load(sender
System.
Object
, e
System.EventArgs)
Handles
MyBase
.Load
Controls.Add(LayoutPanel)
LayoutPanel.Controls.Add(Button1)
LayoutPanel.Controls.Add(TextBox1)
LayoutPanel.SetFlowBreak(TextBox1,
LayoutPanel.Controls.Add(Label1)
Button1_Click(sender
Button1.Click
.TryParse(TextBox1.Text, number)
Label1.Text = NumericStrings.GetNumberWords(number)
Label1.Text =
"Invalid Number"
Summary
By creating an Enum to hold the root number words and using simple math to process the digits of a number we can create an efficient class for converting numbers to their textual string representations. Further improvements could be made by expanding the class to support Long Integer values and further reducing string manipulation, however, the average decimal value should take less than 2 ms to convert with the existing design. The design might also be altered to support instance members, allowing more than one style of formatting at the same time.
Option
Strict
On
Imports
System.ComponentModel
System.Runtime.CompilerServices
''' <summary>
''' Converts Integer or Decimal numbers into their textual String representations (converts numbers to words).
''' </summary>
''' <remarks></remarks>
<EditorBrowsable(EditorBrowsableState.Advanced)>
RootNumbers)