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.: У меня все это работает (включая скобки и т.д.). Будут вопросы - пишите.