Преобразование чисел в слова с помощью VBA

У меня есть столбец чисел. В следующем столбце я хочу преобразовать числа в текст/слово.

Пример: 123.561 преобразуется в One hundred twenty three point five six one.

Я не хочу конвертировать в валюту, просто число в текст, с любым количеством знаков после запятой.

пример изображения

Как я могу это сделать?


person Aman Devrath    schedule 06.07.2018    source источник
comment
Насколько велико самое большое число? тысячи? миллионы? триллионы?   -  person ashleedawg    schedule 06.07.2018
comment
Если бы вы погуглили раньше, вы бы узнали, что Microsoft предлагает решение для этого: Как преобразовать числовое значение в английские слова в Excel   -  person Pᴇʜ    schedule 06.07.2018
comment
@Pᴇʜ Я искал этого помощника, но это работает только для 2 знаков после запятой. Я хочу больше, чем 2. Также как я могу заставить это работать как макрос при нажатии кнопки на ленте?   -  person Aman Devrath    schedule 06.07.2018
comment
@aman - ознакомьтесь с измененной процедурой. Он больше не содержит доллары или центы и включает неограниченное количество знаков после запятой.   -  person ashleedawg    schedule 06.07.2018


Ответы (1)


Редактировать. Я адаптировал приведенную ниже процедуру к неденежным, неограниченным десятичным разрядам.

Правка 2 учитывает интернационализацию посредством двух изменений в (1) Function SpellNumber и (2) Function fractionWords, чтобы код работал с ним. другие десятичные разделители (например, двоеточие в средней Европе) '- см. комментарий


Пример:

MsgBox SpellNumber(2123.4575)

... возвращает:

Two Thousand One Hundred Twenty Three point Four Five Seven Five


Вставьте следующий код в новый модуль:

Option Explicit

Function SpellNumber(ByVal numIn)
    Dim LSide, RSide, Temp, DecPlace, Count, oNum
    oNum = numIn
    ReDim Place(9) As String
    Place(2) = " Thousand "
    Place(3) = " Million "
    Place(4) = " Billion "
    Place(5) = " Trillion "
    numIn = Trim(Str(numIn)) 'String representation of amount
    ' Edit 2.(0)/Internationalisation
    ' Don't change point sign here as the above assignment preserves the point!
    DecPlace = InStr(numIn, ".") 'Pos of dec place 0 if none 
    If DecPlace > 0 Then 'Convert Right & set numIn
        RSide = GetTens(Left(Mid(numIn, DecPlace + 1) & "00", 2))
        numIn = Trim(Left(numIn, DecPlace - 1))
    End If
    RSide = numIn
    Count = 1
    Do While numIn <> ""
        Temp = GetHundreds(Right(numIn, 3))
        If Temp <> "" Then LSide = Temp & Place(Count) & LSide
        If Len(numIn) > 3 Then
            numIn = Left(numIn, Len(numIn) - 3)
        Else
            numIn = ""
        End If
        Count = Count + 1
    Loop

    SpellNumber = LSide
    If InStr(oNum, Application.DecimalSeparator) > 0 Then    ' << Edit 2.(1) 
        SpellNumber = SpellNumber & " point " & fractionWords(oNum)
    End If

End Function

Function GetHundreds(ByVal numIn) 'Converts a number from 100-999 into text
    Dim w As String
    If Val(numIn) = 0 Then Exit Function
    numIn = Right("000" & numIn, 3)
    If Mid(numIn, 1, 1) <> "0" Then 'Convert hundreds place
        w = GetDigit(Mid(numIn, 1, 1)) & " Hundred "
    End If
    If Mid(numIn, 2, 1) <> "0" Then 'Convert tens and ones place
        w = w & GetTens(Mid(numIn, 2))
    Else
        w = w & GetDigit(Mid(numIn, 3))
    End If
    GetHundreds = w
End Function

Function GetTens(TensText)  'Converts a number from 10 to 99 into text
    Dim w As String
    w = ""           'Null out the temporary function value
    If Val(Left(TensText, 1)) = 1 Then   'If value between 10-19
        Select Case Val(TensText)
            Case 10: w = "Ten"
            Case 11: w = "Eleven"
            Case 12: w = "Twelve"
            Case 13: w = "Thirteen"
            Case 14: w = "Fourteen"
            Case 15: w = "Fifteen"
            Case 16: w = "Sixteen"
            Case 17: w = "Seventeen"
            Case 18: w = "Eighteen"
            Case 19: w = "Nineteen"
            Case Else
        End Select
    Else      'If value between 20-99..
        Select Case Val(Left(TensText, 1))
            Case 2: w = "Twenty "
            Case 3: w = "Thirty "
            Case 4: w = "Forty "
            Case 5: w = "Fifty "
            Case 6: w = "Sixty "
            Case 7: w = "Seventy "
            Case 8: w = "Eighty "
            Case 9: w = "Ninety "
            Case Else
        End Select
        w = w & GetDigit _
            (Right(TensText, 1))  'Retrieve ones place
    End If
    GetTens = w
End Function

Function GetDigit(Digit) 'Converts a number from 1 to 9 into text
    Select Case Val(Digit)
        Case 1: GetDigit = "One"
        Case 2: GetDigit = "Two"
        Case 3: GetDigit = "Three"
        Case 4: GetDigit = "Four"
        Case 5: GetDigit = "Five"
        Case 6: GetDigit = "Six"
        Case 7: GetDigit = "Seven"
        Case 8: GetDigit = "Eight"
        Case 9: GetDigit = "Nine"
        Case Else: GetDigit = ""
    End Select
End Function

Function fractionWords(n) As String
    Dim fraction As String, x As Long
    fraction = Split(n, Application.DecimalSeparator)(1)   ' << Edit 2.(2)
    For x = 1 To Len(fraction)
        If fractionWords <> "" Then fractionWords = fractionWords & " "
        fractionWords = fractionWords & GetDigit(Mid(fraction, x, 1))
    Next x
End Function

(Адаптировано из Источник: Microsoft)


В сети есть еще несколько примеров. Возможно, вы уже нашли их, если искали "преобразование чисел в текст", поскольку это подразумевает изменение типа данных. Лучшим условием для поиска было бы "vba конвертировать числа в слова.".

person ashleedawg    schedule 06.07.2018
comment
Вы повысите производительность, используя String возвращающие функции Left$, Right$, Mid$ и Trim$. Следует избегать функции Val, так как она не зависит от локали и будет возвращать ошибочные значения в некоторых регионах — лучше использовать CDbl или CLng в зависимости от ситуации. Кроме того, сделайте возвращаемый тип SpellNumber явным String и строго введите все ваши переменные и вспомогательные функции. И, наконец, лучше использовать vbNullString, чем "". - person ThunderFrame; 06.07.2018
comment
Я сказал в своем вопросе, что может быть любое количество десятичных знаков. Будь то 0 знаков после запятой или 5 знаков после запятой. Я нашел этот ответ, но он фокусируется только на 2 десятичных знаках, а также я хочу, чтобы этот код работал как макрос с ленты. - person Aman Devrath; 06.07.2018
comment
@AmanDevrath Я изменил процедуру. Посмотрите пример и попробуйте. - person ashleedawg; 06.07.2018
comment
@ThunderFrame - интересный, дельный совет, хотя в данном случае ИМО - излишество. Во-первых, я не писал код, и если бы я его переделывал, у меня были бы другие проблемы, такие как объявление типа данных. Мне было бы интересно увидеть любую документацию, указывающую, что Left$ более эффективна, чем Left (особенно на сабвуфере, который вряд ли будет использоваться), и особенно любопытно увидеть любую официальную документацию, показывающую, что константа, которая в 7 раз больше символов, чем буквальное "" лучше. Несмотря на это, многие области кодирования являются вопросом личных предпочтений. Вы можете опубликовать альтернативный ответ. - person ashleedawg; 06.07.2018
comment
@ashleedawg единственная разница между "" и vbNullString заключается в 6 байтах памяти: к этому сравнению "" занимает 6 байтов памяти, где vbNullString занимает 0 байтов. Так что, если вас не волнуют эти 6 байтов, я думаю, это не повод для беспокойства. - person Pᴇʜ; 06.07.2018
comment
@AmanDevrathЧтобы заставить его работать с ленты, вы можете работать с этим: заголовок stackoverflow.com/questions/8850836/ - person ; 06.07.2018
comment
@ Pᴇʜ - Боюсь, в этой логике есть дыра: действительно сохраняется 6 байтов памяти для хранения переменных, но код, необходимый для ввода этого слова, требует дополнительных 10 байтов кода каждый раз, когда вы его используете. Аналогичная идея, почему Integer не следует использовать. Он занимает на 2 байта меньше памяти, которые тратятся впустую из-за 2 байтов, необходимых для 2 дополнительных нажатий клавиш. (Кроме того, нет, меня не волнуют 6 байтов.) - person ashleedawg; 06.07.2018
comment
@ashleedawg Ну, память дешевая, память дорогая. Кроме того, в большинстве случаев у вас гораздо больше памяти, чем доступной памяти. Поэтому я всегда предпочел бы оптимизацию памяти и скорости оптимизации памяти. Длина кода никогда не должна вызывать беспокойства (если ее длина оптимизирует память или скорость). Я никогда не слышал о ком-то, у кого заканчивается память из-за кода VBA, но я слышал много случаев нехватки памяти! (Но это уже не по теме) - person Pᴇʜ; 06.07.2018
comment
@Pᴇʜ - на самом деле, я еще раз посмотрел на эту ссылку, и она неверна, по крайней мере, сейчас. я использую 64-разрядную версию Office 365, и запуск теста на этой странице показывает, что vbNullString и "" занимают одно и то же место для хранения переменной, поэтому это означает, что vbNullString` тратит впустую дополнительные 10 байтов пространства для хранения кода. Даже если бы вы сохраняли эти 6 байтов, потребовалось бы очень много пустых строк, чтобы заполнить ГБ (или КБ, если уж на то пошло). Соглашусь с вами, что мы не по теме. В следующий раз мы найдем о чем поспорить. - person ashleedawg; 06.07.2018
comment
@AmanDevrath - Рад слышать, что это работает. Оглядываясь назад, я сделал ошибку.. Вместо dot технически слово должно быть point. Dot — это скорее справочник по программированию. (Я удивлен, что никто не указал мне на это!) - person ashleedawg; 06.07.2018
comment
Собственно, в моем случае точка работает, так как мой клиент ею доволен. :P Но да, я попытаюсь убедить его использовать point, так как нехорошо использовать dot. Узнал что-то новое. Спасибо еще раз. - person Aman Devrath; 06.07.2018
comment
@ashleedawg длина открытого текста не имеет значения. Текст кода преобразуется в p-код, затем интерпретируется и запускается как исполняемый код. Что имеет значение, так это байты, которые используются во время выполнения. vbNullString имеет размер 0 байт и не требует создания, заполнения и уничтожения адреса памяти каждый раз, когда встречается строковый литерал, такой как "", поэтому он использует меньше памяти во время работы и работает быстрее. Надстройка VBA Rubberduck (соавтором которой я являюсь) идентифицирует и исправит эти возможности повышения производительности. - person ThunderFrame; 06.07.2018
comment
@ashleedawg Функция Left на самом деле является псевдонимом для скрытой функции _B_var_Left и предназначена как для приема, так и для возврата Variant, тогда как функция Left$ на самом деле является псевдонимом для функции _B_str_Left и предназначена как для приема, так и для возврата String. Если ваш код передает и/или ожидает String в ответ, тогда ваш код будет работать более эффективно, так как вы не столкнетесь с неявными преобразованиями из String в Variant, а затем Variant обратно в String каждый вызов таких функций, как Left, Right, Mid, Trim и других. - person ThunderFrame; 06.07.2018
comment
@ThunderFrame - Интересно ... это надстройка VBA? Где его можно найти? Я не знал о различиях с Left/Right/Mid/Trim, однако что касается обработки литерала "", я не уверен, что информация актуальна. Например, демонстрация, опубликованная выше, не дала результата. для меня, как описал автор (и основан на VBA 2 десятилетия назад, т. Е. Без 64-битных установок). Изменения, очевидно, по-прежнему не имеют отношения к этому вопросу, но вы, очевидно, знаете свое дело, спасибо, что нашли время. :-) - person ashleedawg; 06.07.2018
comment
@ashleedawg дом Реббердака. Относительно дебатов "" против vbNullString: Еще один вопрос, который следует учитывать, — это намерение программиста (/ясность кода) — vbNullString намного лучше передает намерение: Да, я действительно хочу иметь строку нулевой длины. здесь - я не забыл случайно поставить между кавычками значащие символы. По сути, это устраняет один источник недопонимания/ошибки. - person Inarion; 06.07.2018
comment
@ashleedawg Если я сравниваю время повторных вызовов с If myString = vbNullString Then и If myString = "" Then, то сравнение vbNullString происходит примерно на 5% быстрее. чем сравнение "" Но более убедительно, если я заполняю массив vbNullString и снова "", подход vbNullString в 7 раз быстрее, чем подход "". - person ThunderFrame; 06.07.2018
comment
@Inarion - ИМХО, этот аргумент немного натянут, я не могу сказать, что когда-либо писал или видел код, в котором кто-то забыл поместить что-то между кавычками ... но все равно спасибо за вклад, я всегда ценю других точка зрения. ...и спасибо за ссылку. - person ashleedawg; 06.07.2018
comment
@ThunderFrame - я провел тест, разница была не столь значительной на моей машине (в 3 раза быстрее при заполнении и на самом деле на 13% медленнее с If/сравнение), но тем не менее большей разницы я и не ожидал. Интересно ради аргумента (к сожалению, практической разницы мало / нет, поскольку я не часто использую это.) - Тем не менее, я вижу, что текстовые функции имеют заметную разницу. Еще раз спасибо за то, что поделились своими знаниями. - person ashleedawg; 06.07.2018
comment
Два изменения, чтобы приведенный выше код работал с другими десятичными разделителями (например, двоеточие в средней Европе) [1] Последнее условие в Function SpellNumber: If InStr(oNum, Application.DecimalSeparator) > 0 Then ) см. примечание ниже ** [2] Первое присвоение в Function FractionWords: fraction = Split(n, Application.DecimalSeparator)(1) *)Примечание: присвоение DecPlace = InStr(numIn, ".") не должно изменяться, так как предыдущее присвоение numIn = Trim(Str(numIn)) сохраняет точку. - person T.M.; 07.07.2018
comment
@Т.М. хорошее мышление, иногда я не думаю об интернационализации (и Список свойств Application.International.) Не стесняйтесь редактировать мой ответ если хочешь! (при условии, что ваше изменение проверено). - person ashleedawg; 07.07.2018
comment
@ashleedawg, это действительно потрясающе, хотя я обнаружил, что он не работает должным образом для десятичных знаков с любым нулем сразу после десятичной точки, например 123,01 (вместо ноль один он дает точка один ). Я добавил Case 0: GetDigit = "Zero" к Function GetDigit, и теперь все работает нормально. - person Bartek Nowakowski; 30.07.2020