CodeNet / Приложения / Алгоритмы / Математика
Интерпретатор математических формул на VB
12 августа 2004 года
Преамбула
Как-то взялся я писать программу. Ничего особенного, программа, как программа, база данных, интерфейс, стандартный учет стандартной информации. Да вот только было в ней и кое-что интересное. Некоторые поля должны были рассчитываться на основе других, причем заранее не известно, на каких именно и каким образом. Т.е. надо было писать редактор формул, в котором можно было бы указывать какие поля взять и что с ними делать. Соответственно, потом по этим формулам надо было рассчитывать новое значение и заносить в таблицу.
С первой частью справиться труда не составило, и часов через несколько редактор был готов. Пользователь брал поля, которые были основой для расчета, компоновал из них формулу, разделяя все скобками и знаками арифметических действий. Короче, все кто видел построитель выражений в Microsoft программах, поймет, что у меня получилось в итоге.
Ну все хорошо, формула есть, только как же теперь ее посчитать? Первой возникшей мыслью было то, что задача не нова, и наверняка кто-то где-то уже делал подобное. Поиск в Интернете принес некоторые результаты, но, честно говоря, огорчил меня тем, что все что я нашел было либо основной идейной линией, которая была и так понятна, либо это были готовые программы, типа строкового калькулятора.
Полезным оказался только один пример на Дельфи, в котором данная задача решалась, но во-первых, все равно надо было переписывать на VB, а во-вторых, там было все на столько сложно сделано, что удивительно, как это вообще работает.
По этому, было принято решение писать самостоятельно. И вот что из этого получилось.
Амбула
Итак, что сбой представляет любая формула, с математическими функциями, возведениями в степень и скобками? Это последовательность действий, которые необходимо осуществить с элементами формулы, чтобы получить в итоге результат. Все действия имеют разный приоритет. Нам необходимо научить компьютер осуществлять эти действия.
Любая формула, сколько бы в ней элементов и операций не было, всегда упрощается до простой арифметики с четырьмя типами математических операций: +,-,/,*.
Например:
50+sin(1)*cos(0)/(450/(78+45)) рассчитывая последовательно выражения внутри скобок и функции получим:
sin(1)=0 cos(0)=1 (78+45)=123 450/123=3.65
50+0*1/3.65 - вот функция, упрощенная до простых арифметических операций.
Вывод: первое, что необходимо запрограммировать - "движок", который бы умел считать такие выражения с четырьмя простейшими арифметическими действиями.
Нам необходимо представить текстовую строку, которая не имеет никакой смысловой нагрузки для VB в таком виде, чтобы мы могли работать с элементами формулы как с отдельными самостоятельными элементами, т.е. цифрами и знаками арифметических выражений. Причем должна сохраняться связь между ними и порядок действий. Первый выход, который показался мне реальным - массив. Итак,
Public Const opPlus As String = "+"
Public Const opMinus As String = "-"
Public Const opDivide As String = "/"
Public Const opMult As String = "*"
'Объявим константы - арифметические действия.
'Интерпретацией будет заниматься функция,
'аргумент которой есть текстовая строка-функция
Public Function Interpritate(strLine As String) As Double
On Error GoTo err_:
Dim tmp As String, mdr As String, stack() As String
'Текстовый динамический массив - это формула,
' с которой будет работать интерпретатор.
Dim i As Integer, st As Integer, t As Integer, i1 As Integer, t1 As Integer
st = 1 'Кол-во элементов формулы
For i = 1 To Len(strLine)
mdr = Mid$(strLine, i, 1)
If mdr = opPlus Or mdr = opMinus Or mdr = opDivide Or mdr = opMult Then
'Если в формуле есть знак арифметического действия, то
st = st + 1 'занесем
ReDim Preserve stack(st) 'предыдущий элемент и
stack(st) = mdr1 'знак в стек
stack(st - 1) = Trim(tmp)
tmp = ""
st = st + 1
Else
'Продолжаем считывать число
tmp = tmp & mdr
End If
Next i
'Занесем последний элемент в стек
ReDim Preserve stack(st)
stack(st) = tmp
'Все, массив готов. Можно интерпретировать ;-)
For i1 = 1 To 4
Select Case i1 'Порядок арифметических действий
Case 1
tmp1 = opMult
Case 2
tmp = opDivide
Case 3
tmp = opPlus
Case 4
tmp = opMinus
End Select
i = 1
While i <= st - 1
If stack(i) = tmp Then
Select Case tmp
Case opMult
stack(i - 1) = CDbl(stack(i - 1)) * _
CDbl(stack(i + 1))
Case opDivide
stack(i - 1) = CDbl(stack(i - 1)) / _
CDbl(stack(i + 1))
Case opPlus
stack(i - 1) = CDbl(stack(i - 1)) + _
CDbl(stack(i + 1))
Case opMinus
stack(i - 1) = CDbl(stack(i - 1)) - _
CDbl(stack(i + 1))
End Select
'Запомним рассчитанное значение, а ячейки, на основе которых велся
'расчет, обнулим
stack(i) = ""
stack(i + 1) = ""
'Сдвинем остальные элементы к началу массива
For t = i To st - 2
stack(t) = stack(t + 2)
Next t
stack(st - 1) = ""
stack(st) = ""
i = i - 1
End If
i = i + 1
Wend
Next i1
'В результате сдвига рассчитанных значений в начало массива, результат выражения
'находится в первой ячейке массива.
Interpritate = CDbl(stack(1))
Exit Function
err_:
MsgBox "Ошибка #" & err.Number & vbCrLf & err.Description, vbCritical, "Внимание!"
Interpritate = 0
End Function
P.S.: Движок готов. Все реально работает, но:
Во-первых: Статья была написана только в образовательных целях, поэтому просто скопировать текст и вставить в модуль не получится. Я намеренно изменил кое-что в коде, но если голова на месте, поняв принцип работы интерпретатора, исправите сами и все заработает.
Во-вторых: Это только движок, и формулы со скобками и функциями он не считает. Чтобы работать со скобками, необходимо последовательно интерпретировать выражения внутри скобок, заменять их в формуле полученным результатом, и, в итоге, интерпретировать упрощенное выражение. С функциями то же самое.
В-третьих: Подумайте сами, как это все заставить работать с отрицательными числами.
P.P.S.: У меня все это работает (включая скобки и т.д.). Будут вопросы - пишите.
