Have you ever wanted to spell out a number as words , perhaps for reporting / automated form filling ?
This script / UDF takes some cues from the various scripts out there, however they didn't cover numbers beyond a billion, decimal places beyond two, and often appended currency names to them.
If you did want to use currency names or smaller numbers, here's some links to other, similar scripts
Online Training Hub
AbleBits.com
Support.Office.Com
The above is taking it to extremes, but you get the idea.
So how do we turn 123456789 into :
'ONE HUNDRED TWENTY THREE MILLION FOUR HUNDRED FIFTY SIX THOUSAND SEVEN HUNDRED EIGHTY NINE'
Excel itself has some limitations when it comes to BIG numbers.
Beyond a certain size, numbers will start being truncated / rounded.
Case in point - if you tried typing a random number of digits up to a Quadrillion (Short-Scale) or more into a cell, it'd default to scientific notation, and promptly round up to the nearest thousand .
To get around this, for big numbers, you can prefix with an apostrophe ( ' ).
This is the shortcut for telling excel to interpret the cell value as text - will allow you to enter some really big numbers, without fear of Excel rounding them off when you get beyond a trillion ( Short-Scale ) .
Garbage in / Garbage out
Before beginning on any transformation - we need to remove any formatting or additional data.
To cover all bases, we can process the string looking for only numbers, dashes and the first occurance of a decimal place. Beyond that we can remove the unnecessary characters.
We want to keep ASCII 48 > 57, on occurrence of 45, and one occurrence of 46.
The rest we can discard.
The code :
This will cleanse the initial string of any extraneous characters, retaining only numbers [1-9] , a single dash on the leftmost position, and a single decimal place.
So the later functions can work off a clean number, you can see how it deals with various formats :
Hopefully this'll cover all bases.
What's the NumericDict function ?
This is just a dictionary of 'permitted' ASCII codes , that returns a 'Y' if valid.
We'll be using this functionality later, and the VBA dictionary object is a quick and easy way of performing lookups within code.
N.B If you're not already using it, you'll need a reference to the Microsoft Scripting Runtime, this will allow you to utilise data types like dictionaries that aren't in the standard Excel.
You can enable it via Tools > References > MicroSoft Scripting Runtime ( when in the VBA window ALT+F11 )
Zero Padding
Now that we've cleaned the extraneous data from the string, the next step is to normalise the length of it into chunks of three digits.
This will be used in the next step and simplifies the process of iterating through the numeric string and turning each 'chunk' into its written counterpart.
If we have the number 123456789 , we want to pad the string to the nearest multiple of 3.
We now have "0123456789" as the nearest multiple of 3 greater than 8 is 9.
We can then iterate through each character in the string, in reverse order, to derive the value of that portion, starting with units and working upwards :
Place | Reverse | Description | |
---|
1 | 9 | Units | |
2 | 8 | Tens | |
3 | 7 | Hundreds | |
4 | 6 | Thousands | |
5 | 5 | Ten-Thousands | |
6 | 4 | Hundred-Thousands | |
7 | 3 | Millions | |
8 | 2 | Ten-Millions | |
9 | 1 | Hundred-Millions | |
10 | - | Billions | |
11 | - | Ten-Billions | |
| | | |
The script will work through in chunks of three, so it'd begin with "Seven Hundred Eighty Nine"
The next cluster of three, it'd return "Four Hundred Fifty Six" and append that bracket's name, in this case 'Thousand".
For the final cluster, it'd return "One Hundred Twenty Three" + "million" .
Assembled into one string, you'd get:
"One Hundred Twenty Three Million, Four Hundred Fifty Six Thousand, Seven Hundred Eighty Nine."
The first part of the code - this takes a 3 digit input, and returns the Hundreds, Tens and Units description.
The next function assembles this together with its placement within the number ( Thousands, Millions, Billions etc. )
This should be enough to handle all integers , but what about non-integer (floating point) ?
Here's the next part - this will work forwards through the decimal portion of a string and describe that number's significance :
These two functions use the following dictionary :
This should cover most scenarios.
Putting it together
Now to combine the two functions, into a single UDF to be used within a worksheet.
This function works in three stages
1 : Splits the string where a pipe exists into either one or two parts.
2: Checks if the optional 'IsInteger' boolean is set, if set to False move onto
3 : Process integer and decimal portions into word strings and concatenate conditionally.
Usage in Workbook
Here's how it'd work within a sheet :
Number |
NumberToWords() Output |
Settings |
|
123.456 |
ONE HUNDRED TWENTY THREE |
Default - only uses integer portion |
|
123.456 |
ONE HUNDRED TWENTY THREE POINT FOUR TENTHS FIVE HUNDREDTHS SIX THOUSANDTHS |
With IsInteger=False - output now displays the decimal portion |
|
|
|
|
|
By using the IsInteger flag, the script will add on the '.456' decimal portion.
All scripts
Copy and paste the whole block below into a new module to use this in your own projects.
Option Explicit
Function NumberToWords(ByVal MyString As String, Optional IsInteger As Boolean = True) As String
Dim MyArray() As String
MyString = NumChunkCleanString(MyString)
MyArray = Split(MyString, "|")
Select Case UBound(MyArray, 1)
Case 0:
NumberToWords = NumChunkWords(MyArray(0))
Case 1:
NumberToWords = NumChunkWords(MyArray(0))
If Not IsInteger Then NumberToWords = NumberToWords & " POINT " & NumChunkDecimal(MyArray(1))
Case Else:
NumberToWords = "Cannot Calculate"
End Select
End Function
Private Function NumChunkDecimal(MyString As String) As String
Dim LP As Integer
Dim LUVdouble As Double
For LP = 1 To Len(MyString)
LUVdouble = 1 / (10 ^ LP)
If Mid(MyString, LP, 1) <> "0" Then
NumChunkDecimal = NumChunkDecimal & NumChunkName(Mid(MyString, LP, 1))
NumChunkDecimal = NumChunkDecimal & " " & NumChunkName(LUVdouble)
If Mid(MyString, LP, 1) <> "1" Then
NumChunkDecimal = NumChunkDecimal & "S "
Else
NumChunkDecimal = NumChunkDecimal & " "
End If
End If
Next LP
NumChunkDecimal = UCase(Trim(NumChunkDecimal))
End Function
Private Function NumChunkCleanString(MyString As String) As String
Dim LP As Integer
MyString = Replace(MyString, " ", "")
MyString = Replace(MyString, ",", "")
MyString = Replace(MyString, "'", "")
MyString = StrReverse(Replace(StrReverse(MyString), ".", "|", , 1))
MyString = Replace(MyString, Chr(46), "")
MyString = Replace(MyString, Chr(133), "")
If Left(MyString, 1) = "-" And Len(MyString) > 1 Then MyString = "-" & Replace(Right(MyString, Len(MyString) - 1), "-", "")
For LP = 1 To Len(MyString)
If NumericDict(Asc(Mid(MyString, LP, 1))) = "Y" Then NumChunkCleanString = NumChunkCleanString & Mid(MyString, LP, 1)
Next LP
End Function
Private Function NumChunkWords(MyString As String) As String
Dim LP As Integer
Dim MyChunks As Integer
Dim Outstring As String
Dim PadString As String
Dim MyPowerLUV As Double
Dim MyPos As Integer
PadString = NumChunkPadMe(MyString)
MyChunks = NumChunkCount(MyString)
MyPos = 1
If MyChunks = 1 And PadString = "000" Then
NumChunkWords = "ZERO"
Exit Function
End If
For LP = 1 To MyChunks
MyPowerLUV = 10 ^ ((MyChunks - LP) * 3)
If MyPos = MyChunks Then
Outstring = Outstring & NumChunkWordBlock(Mid(PadString, (3 * LP) - 2, 3)) & " "
End If
If MyPos <> MyChunks Then
If Val(Mid(PadString, (3 * LP) - 2, 3)) > 0 Then
Outstring = Outstring & NumChunkWordBlock(Mid(PadString, (3 * LP) - 2, 3)) & " "
Outstring = Outstring & NumChunkName(MyPowerLUV) & " "
End If
End If
MyPos = MyPos + 1
Next LP
NumChunkWords = UCase(Trim(Outstring))
End Function
Private Function NumChunkWordBlock(MyString As String) As String
Dim LP As Index
Dim NumHund, NumTen, NumUnit As Double
NumChunkWordBlock = ""
NumHund = Val(Left(MyString, 1))
NumTen = Val(Mid(MyString, 2, 1))
NumUnit = Val(Right(MyString, 1))
If NumHund > 0 Then
NumChunkWordBlock = NumChunkName(NumHund) & " " & NumChunkName(100)
End If
If NumTen + NumUnit > 0 And NumHund > 0 = True Then
NumChunkWordBlock = NumChunkWordBlock & " AND"
End If
If (NumTen * 10) + NumUnit > 19 Then
NumChunkWordBlock = NumChunkWordBlock & " " & NumChunkName(NumTen * 10)
If NumUnit > 0 Then
NumChunkWordBlock = NumChunkWordBlock & " " & NumChunkName(NumUnit)
End If
End If
If (NumTen * 10) + NumUnit <= 19 And (NumTen * 10) + NumUnit > 0 Then
NumChunkWordBlock = NumChunkWordBlock & " " & NumChunkName((NumTen * 10) + NumUnit)
End If
End Function
Private Function NumChunkCount(MyString As String) As Integer
NumChunkCount = Len(Format(Val(MyString), "#,###")) - Len(Format(Val(MyString), "#")) + 1
End Function
Private Function NumChunkPadMe(ByVal MyString As String) As String
Dim MyPad As String
NumChunkPadMe = "" & ZeroPad(MyString, NumChunkCount(MyString) * 3)
End Function
Private Function NumericDict(ByVal MyInteger As Integer) As String
Dim MyDict As Dictionary
Set MyDict = New Dictionary
MyDict(48) = "Y"
MyDict(49) = "Y"
MyDict(50) = "Y"
MyDict(51) = "Y"
MyDict(52) = "Y"
MyDict(53) = "Y"
MyDict(54) = "Y"
MyDict(55) = "Y"
MyDict(56) = "Y"
MyDict(57) = "Y"
MyDict(45) = "Y"
MyDict(46) = "Y"
MyDict(124) = "Y"
If MyDict.EXISTS(MyInteger) Then
NumericDict = MyDict(MyInteger)
Else
NumericDict = "N"
End If
End Function
Private Function NumChunkName(ByVal MyInteger As Double) As String
Dim MyDict As Dictionary
Set MyDict = New Dictionary
MyDict(0) = "zero"
MyDict(1) = "one"
MyDict(2) = "two"
MyDict(3) = "three"
MyDict(4) = "four"
MyDict(5) = "five"
MyDict(6) = "six"
MyDict(7) = "seven"
MyDict(8) = "eight"
MyDict(9) = "nine"
MyDict(10) = "ten"
MyDict(11) = "eleven"
MyDict(12) = "twelve"
MyDict(13) = "thirteen"
MyDict(14) = "fourteen"
MyDict(15) = "fifteen"
MyDict(16) = "sixteen"
MyDict(17) = "seventeen"
MyDict(18) = "eighteen"
MyDict(19) = "nineteen"
MyDict(20) = "twenty"
MyDict(30) = "thirty"
MyDict(40) = "forty"
MyDict(50) = "fifty"
MyDict(60) = "sixty"
MyDict(70) = "seventy"
MyDict(80) = "eighty"
MyDict(90) = "ninety"
MyDict(100) = "hundred"
MyDict(1000) = "thousand"
MyDict(1000000) = "million"
MyDict(1000000000) = "billion"
MyDict(1000000000000#) = "trillion"
MyDict(1E+15) = "quadrillion"
MyDict(1E+18) = "quintillion"
MyDict(1E+21) = "Sextillion"
MyDict(1E+24) = "Septillion"
MyDict(1E+27) = "Octillion"
MyDict(1E+30) = "Nonillion"
MyDict(1E+33) = "Decillion"
MyDict(0.1) = "Tenth"
MyDict(0.01) = "Hundredth"
MyDict(0.001) = "Thousandth"
MyDict(0.0001) = "Ten-Thousandth"
MyDict(0.00001) = "Hundred-Thousandth"
MyDict(0.000001) = "Millionth"
MyDict(0.0000001) = "Ten-Millionth"
MyDict(0.00000001) = "Hundred-Millionth"
MyDict(0.000000001) = "Billionth"
MyDict(0.0000000001) = "Ten-Billionth"
MyDict(0.00000000001) = "Hundred-Billionth"
MyDict(0.000000000001) = "Trillionth"
MyDict(0.0000000000001) = "Ten-Trillionth"
MyDict(0.00000000000001) = "Hundreed-Trillionth"
MyDict(0.000000000000001) = "Quadrillionth"
MyDict(1E-16) = "Ten-Quadrillionth"
MyDict(1E-17) = "Hundred-Quadrillionth"
MyDict(1E-18) = "Quitillionth"
MyDict(1E-19) = "Ten-Quitillionth"
MyDict(1E-20) = "Hundred-Quitillionth"
MyDict(1E-21) = "Sextillionth"
MyDict(1E-22) = "Ten-Sextillionth"
MyDict(1E-23) = "Hundred-Sextillionth"
MyDict(1E-24) = "Septillionth"
If MyDict.EXISTS(MyInteger) Then
NumChunkName = MyDict(MyInteger)
Else
NumChunkName = ""
End If
End Function
Function ZeroPad(MyInput As Variant, ZeroCount As Integer) As String
ZeroPad = Right(WorksheetFunction.Rept("0", ZeroCount) & CStr(MyInput), ZeroCount)
End Function