Convert Currency Amounts to Words in Excel: CNY, USD, GBP, HKD, JPY and EUR

By huanggs

For a single-cell input such as USD1500.00, read the first three characters as the currency code and the remaining characters as the amount. The task is to convert one combined Excel text value into amounts in words for CNY, USD, GBP, HKD, JPY, or EUR without splitting the input into two cells.

Excel’s built-in currency formatting is only display formatting. Microsoft documents currency display separately from text conversion: format numbers as currency in Excel. The MID and TEXT functions help parse and format text, but spelling a currency amount needs a custom rule.

Parse the combined input first

NeedFormulaResult for USD1500.00
Currency code=UPPER(LEFT(TRIM(A1),3))USD
Amount text=MID(TRIM(A1),4,LEN(TRIM(A1))-3)1500.00
Formatted display only=UPPER(LEFT(TRIM(A1),3))&" "&TEXT(--MID(TRIM(A1),4,LEN(TRIM(A1))-3),"#,##0.00")USD 1,500.00

Those formulas do not spell the amount. Use the user-defined function below when you need words. The English currency output is Title Case, such as One Thousand Five Hundred US Dollars. If you need all capital letters, wrap the function with UPPER, for example =UPPER(CurrencyAmountWords(A1)).

VBA user-defined function for six currency codes

The function accepts text such as CNY315.05, USD1500.00, GBP1.01, HKD12.30, JPY1500.50, or EUR0. CNY returns a custom RMB financial-style numeral wording; the other supported codes return English amount words. It does not convert exchange rates.

Installation in desktop Excel: press Alt+F11, choose Insert > Module, paste the code into a standard module, save as .xlsm, and use =CurrencyAmountWords(A1).

Option Explicit

Public Function CurrencyAmountWords(ByVal CodeAndAmount As Variant) As Variant
    On Error GoTo Fail
    Dim s As String, code As String, amountText As String
    Dim decimals As Long, neg As Boolean, roundedMinor As String
    Dim majorText As String, minorText As String
    Dim errMsg As String

    If IsError(CodeAndAmount) Then
        CurrencyAmountWords = CVErr(xlErrValue)
        Exit Function
    End If

    s = Trim(CStr(CodeAndAmount))
    If Len(s) < 4 Then
        CurrencyAmountWords = CVErr(xlErrValue)
        Exit Function
    End If

    code = UCase(Left(s, 3))
    amountText = Mid(s, 4)

    Select Case code
        Case "CNY", "USD", "GBP", "HKD", "EUR"
            decimals = 2
        Case "JPY"
            decimals = 0
        Case Else
            CurrencyAmountWords = CVErr(xlErrValue)
            Exit Function
    End Select

    roundedMinor = RoundAmountTextToMinor(amountText, decimals, neg, errMsg)
    If errMsg <> "" Then
        If errMsg = "NUM" Then
            CurrencyAmountWords = CVErr(xlErrNum)
        Else
            CurrencyAmountWords = CVErr(xlErrValue)
        End If
        Exit Function
    End If

    SplitMinor roundedMinor, decimals, majorText, minorText
    If Len(StripLeadingZeroes(majorText)) > 15 Then
        CurrencyAmountWords = CVErr(xlErrNum)
        Exit Function
    End If
    If StripLeadingZeroes(roundedMinor) = "0" Then neg = False

    If code = "CNY" Then
        CurrencyAmountWords = RMBWords(majorText, minorText, neg)
    Else
        CurrencyAmountWords = EnglishCurrencyWords(code, majorText, minorText, neg)
    End If
    Exit Function

Fail:
    CurrencyAmountWords = CVErr(xlErrValue)
End Function

Private Function RoundAmountTextToMinor(ByVal amountText As String, ByVal decimals As Long, ByRef neg As Boolean, ByRef errMsg As String) As String
    Dim s As String, i As Long, ch As String
    Dim p As Long, intPart As String, fracPart As String
    Dim keepFrac As String, nextDigit As String, combined As String

    s = Trim(amountText)
    If s = "" Then errMsg = "VALUE": Exit Function

    If Left(s, 1) = "-" Then
        neg = True
        s = Mid(s, 2)
    ElseIf Left(s, 1) = "+" Then
        s = Mid(s, 2)
    End If
    If s = "" Then errMsg = "VALUE": Exit Function

    p = InStr(1, s, ".", vbBinaryCompare)
    If p > 0 Then
        intPart = Left(s, p - 1)
        fracPart = Mid(s, p + 1)
        If InStr(p + 1, s, ".", vbBinaryCompare) > 0 Then errMsg = "VALUE": Exit Function
    Else
        intPart = s
        fracPart = ""
    End If

    If intPart = "" And fracPart = "" Then errMsg = "VALUE": Exit Function
    If intPart = "" Then intPart = "0"
    For i = 1 To Len(intPart)
        ch = Mid(intPart, i, 1)
        If ch < "0" Or ch > "9" Then errMsg = "VALUE": Exit Function
    Next i
    For i = 1 To Len(fracPart)
        ch = Mid(fracPart, i, 1)
        If ch < "0" Or ch > "9" Then errMsg = "VALUE": Exit Function
    Next i

    intPart = StripLeadingZeroes(intPart)
    If Len(intPart) > 15 Then errMsg = "NUM": Exit Function

    If decimals = 0 Then
        combined = intPart
        nextDigit = Left(fracPart & "0", 1)
    Else
        keepFrac = Left(fracPart & String(decimals, "0"), decimals)
        nextDigit = Mid(fracPart & String(decimals + 1, "0"), decimals + 1, 1)
        combined = intPart & keepFrac
    End If

    combined = StripLeadingZeroes(combined)
    If nextDigit >= "5" Then combined = AddOneToDigitString(combined)
    If Len(combined) > 15 + decimals Then errMsg = "NUM": Exit Function

    RoundAmountTextToMinor = combined
End Function

Private Sub SplitMinor(ByVal roundedMinor As String, ByVal decimals As Long, ByRef majorText As String, ByRef minorText As String)
    roundedMinor = StripLeadingZeroes(roundedMinor)
    If decimals = 0 Then
        majorText = roundedMinor
        minorText = ""
    Else
        If Len(roundedMinor) <= decimals Then
            majorText = "0"
            minorText = Right(String(decimals, "0") & roundedMinor, decimals)
        Else
            majorText = Left(roundedMinor, Len(roundedMinor) - decimals)
            minorText = Right(roundedMinor, decimals)
        End If
    End If
End Sub

Private Function StripLeadingZeroes(ByVal s As String) As String
    Do While Len(s) > 1 And Left(s, 1) = "0"
        s = Mid(s, 2)
    Loop
    If s = "" Then s = "0"
    StripLeadingZeroes = s
End Function

Private Function AddOneToDigitString(ByVal s As String) As String
    Dim i As Long, carry As Long, d As Long, out As String
    carry = 1
    For i = Len(s) To 1 Step -1
        d = CLng(Mid(s, i, 1)) + carry
        If d = 10 Then
            out = "0" & out
            carry = 1
        Else
            out = CStr(d) & out
            carry = 0
        End If
    Next i
    If carry = 1 Then out = "1" & out
    AddOneToDigitString = out
End Function

Private Function RMBWords(ByVal majorText As String, ByVal minorText As String, ByVal neg As Boolean) As String
    Dim digits As Variant, jiao As Long, fen As Long, prefix As String
    digits = Array("零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖")
    If neg Then prefix = "负"

    If Len(minorText) < 2 Then minorText = minorText & String(2 - Len(minorText), "0")
    jiao = CLng(Mid(minorText, 1, 1))
    fen = CLng(Mid(minorText, 2, 1))

    RMBWords = "人民币" & prefix & ChineseIntegerWords(majorText) & "元"
    If jiao = 0 And fen = 0 Then
        RMBWords = RMBWords & "整"
    Else
        If jiao > 0 Then RMBWords = RMBWords & digits(jiao) & "角"
        If jiao = 0 And fen > 0 Then RMBWords = RMBWords & "零"
        If fen > 0 Then RMBWords = RMBWords & digits(fen) & "分"
    End If
End Function

Private Function ChineseIntegerWords(ByVal nText As String) As String
    Dim groupUnits As Variant, groups() As String, gCount As Long
    Dim s As String, part As String, i As Long, needZero As Boolean

    groupUnits = Array("", "万", "亿", "兆")
    nText = StripLeadingZeroes(nText)
    If nText = "0" Then ChineseIntegerWords = "零": Exit Function

    Do While Len(nText) > 0
        ReDim Preserve groups(gCount)
        If Len(nText) > 4 Then
            groups(gCount) = Right(nText, 4)
            nText = Left(nText, Len(nText) - 4)
        Else
            groups(gCount) = nText
            nText = ""
        End If
        gCount = gCount + 1
    Loop

    For i = gCount - 1 To 0 Step -1
        part = ChineseFourDigits(groups(i))
        If part = "" Then
            If s <> "" Then needZero = True
        Else
            If s <> "" And (needZero Or CLng(groups(i)) < 1000) Then s = s & "零"
            s = s & part & groupUnits(i)
            needZero = False
        End If
    Next i
    ChineseIntegerWords = s
End Function

Private Function ChineseFourDigits(ByVal groupText As String) As String
    Dim digits As Variant, units As Variant, s As String
    Dim padded As String, i As Long, d As Long, pos As Long, zeroPending As Boolean
    digits = Array("零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖")
    units = Array("仟", "佰", "拾", "")
    padded = Right("0000" & groupText, 4)
    For i = 1 To 4
        d = CLng(Mid(padded, i, 1))
        pos = i - 1
        If d = 0 Then
            If s <> "" Then zeroPending = True
        Else
            If zeroPending Then s = s & "零": zeroPending = False
            s = s & digits(d) & units(pos)
        End If
    Next i
    ChineseFourDigits = s
End Function

Private Function EnglishCurrencyWords(ByVal code As String, ByVal majorText As String, ByVal minorText As String, ByVal neg As Boolean) As String
    Dim majorSingular As String, majorPlural As String, minorSingular As String, minorPlural As String
    Dim majorWords As String, minorVal As Long, prefix As String

    Select Case code
        Case "USD": majorSingular = "US Dollar": majorPlural = "US Dollars": minorSingular = "Cent": minorPlural = "Cents"
        Case "GBP": majorSingular = "British Pound": majorPlural = "British Pounds": minorSingular = "Penny": minorPlural = "Pence"
        Case "HKD": majorSingular = "Hong Kong Dollar": majorPlural = "Hong Kong Dollars": minorSingular = "Cent": minorPlural = "Cents"
        Case "EUR": majorSingular = "Euro": majorPlural = "Euros": minorSingular = "Cent": minorPlural = "Cents"
        Case "JPY": majorSingular = "Japanese Yen": majorPlural = "Japanese Yen": minorSingular = "": minorPlural = ""
    End Select

    If neg Then prefix = "Negative "
    majorWords = EnglishIntegerWords(majorText)

    If code = "JPY" Then
        EnglishCurrencyWords = prefix & majorWords & " " & majorPlural
        Exit Function
    End If

    If Len(minorText) < 2 Then minorText = minorText & String(2 - Len(minorText), "0")
    minorVal = CLng(minorText)

    EnglishCurrencyWords = prefix & majorWords & " " & IIf(majorText = "1", majorSingular, majorPlural)
    If minorVal = 0 Then
        EnglishCurrencyWords = EnglishCurrencyWords & " and No " & minorPlural
    Else
        EnglishCurrencyWords = EnglishCurrencyWords & " and " & EnglishIntegerWords(CStr(minorVal)) & " " & IIf(minorVal = 1, minorSingular, minorPlural)
    End If
End Function

Private Function EnglishIntegerWords(ByVal nText As String) As String
    Dim scales As Variant, parts() As String, pCount As Long
    Dim chunk As String, words As String, i As Long
    scales = Array("", "Thousand", "Million", "Billion", "Trillion")
    nText = StripLeadingZeroes(nText)
    If nText = "0" Then EnglishIntegerWords = "Zero": Exit Function

    Do While Len(nText) > 0
        ReDim Preserve parts(pCount)
        If Len(nText) > 3 Then
            parts(pCount) = Right(nText, 3)
            nText = Left(nText, Len(nText) - 3)
        Else
            parts(pCount) = nText
            nText = ""
        End If
        pCount = pCount + 1
    Loop

    For i = pCount - 1 To 0 Step -1
        If CLng(parts(i)) > 0 Then
            chunk = EnglishUnder1000(CLng(parts(i)))
            If scales(i) <> "" Then chunk = chunk & " " & scales(i)
            If words <> "" Then words = words & " "
            words = words & chunk
        End If
    Next i
    EnglishIntegerWords = words
End Function

Private Function EnglishUnder1000(ByVal n As Long) As String
    Dim ones As Variant, teens As Variant, tens As Variant
    Dim h As Long, r As Long, t As Long, o As Long, s As String
    ones = Array("", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine")
    teens = Array("Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen")
    tens = Array("", "", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety")
    h = n \ 100
    r = n Mod 100
    If h > 0 Then s = ones(h) & " Hundred"
    If r >= 10 And r <= 19 Then
        If s <> "" Then s = s & " "
        s = s & teens(r - 10)
    Else
        t = r \ 10
        o = r Mod 10
        If t > 0 Then
            If s <> "" Then s = s & " "
            s = s & tens(t)
        End If
        If o > 0 Then
            If s <> "" Then s = s & " "
            s = s & ones(o)
        End If
    End If
    EnglishUnder1000 = s
End Function

Code boundaries and edge-case checks

The rounding step treats the amount as text digits, not as CDbl(amount)*100. That is why CNY1.005 is rounded by the third decimal digit to 人民币壹元零壹分 instead of depending on binary floating-point representation. The integer part is limited to 15 digits after rounding; larger values return #NUM!. Unsupported codes, blank amounts, nonnumeric amounts, and Excel error inputs return #VALUE!. A negative amount that rounds to zero becomes zero, not negative zero.

For CNY, this implementation uses 兆 as 10^12. It is a custom financial-style number wording for worksheet use, not a guarantee that every bank, invoice platform, or contract reviewer will accept the exact same wording. If the output will be used for payment, reimbursement, or legal documents, confirm the required wording style with the receiving institution.

InputExpected output or errorBoundary covered
CNY0人民币零元整zero
CNY10001人民币壹万零壹元整zero inside 万 grouping
CNY100000001人民币壹亿零壹元整亿 group gap
CNY100010001人民币壹亿零壹万零壹元整multiple group gaps
CNY1.005人民币壹元零壹分decimal text rounding
CNY-0.004人民币零元整negative tiny value rounds to zero
USD1500.00One Thousand Five Hundred US Dollars and No CentsUSD plural and cents
GBP1.01One British Pound and One Pennysingular pound/penny
HKD12.30Twelve Hong Kong Dollars and Thirty CentsHKD cents
EUR0Zero Euros and No Centszero euro value
JPY1500.49One Thousand Five Hundred Japanese YenJPY whole-yen rounding down
JPY1500.50One Thousand Five Hundred One Japanese YenJPY whole-yen rounding up
CNY999999999999999.99人民币玖佰玖拾玖兆玖仟玖佰玖拾玖亿玖仟玖佰玖拾玖万玖仟玖佰玖拾玖元玖角玖分largest accepted 15-digit integer part
CNY1000000000000000#NUM!too large
ABC12#VALUE!unsupported code
CNY#VALUE!blank amount

These expected results were checked by reading the algorithm and by a separate reasoning mirror of the string rules. They are not presented as proof that the VBA ran in this environment. Before using the function for invoices or contracts, paste it into your own workbook and test the exact examples your organization needs.