Так вот получилось, что недавно пришлось заняться разработкой небольшой библиотеки математических функций, которые или слабо известны или же очень редко упоминаются. Естественно, разработка такой библиотеки довольно непростое занятие (приходиться вспоминать математику или искать разложение в ряды некоторых довольно специфических функций) тем более, что библиотека пишется не для классического Icon, а для его объектно-ориентированной версии (т.е для ObjectIcon).
И вот, засучив рукава, я сделал около 30 различных функций, реализованных в виде статических методов (public static) одного класса под названием ExtMath (Extended Mathematics — расширенная математика).
Класс, конечно, хороший и все считается на ура (проверял на схожих функциях из других языков программирования), но все равно даже затабулировав функции никакой наглядности не получишь…
Исходя из этого, я вспомнил одну интересную идею, которая у меня как-то была реализована для ObjectIcon. Идея была в том, чтобы написать небольшую программку демонстрирующую графики различных функций. Однако, программка писалась на скорую руку и как следствие абсолютно нечитаема и не обладает универсальностью.
Тогда мне пришла в голову мысль — переработать этот код и показать при этом некоторые нюансы ObjectIcon.
Графика в ObjectIcon работает совершенно иначе, чем в Icon. Прежде всего, это связано с тем, что в ObjectIcon библиотеки, ответственные за графику полностью переработаны и перемещены в модуль graphics.
Например, чтобы открыть окно необходимо импортировать модуль graphics и выполнить примерно такую последовательность действий:
w := Window(). set_size(200,200). set_fg("red"). set_bg("light grey"). set_font("serif,24"). erase_area(). set_canvas("normal") | stop(&why)
т.е необходимо создать объект класса Window (да это объект, а не файл, как в Icon) и
с помощью методов установить нужные для окна параметры (размер, цвет фона, цвет
тона, режим холста и так далее). С этим в принципе все ясно и понятно.
Но вот в чем ложка дегтя… После запуска этой последовательности операций окно
появиться и тут же закроется. Для того, чтобы этого не произошло мы воспользуемся
бесконечным циклом для перехватывания различных событий:
repeat { while e:=w.event() do { case e[1] of { Mouse.LEFT_PRESS : write(e[2],",",e[3]) Mouse.RIGHT_PRESS : { w.set_fg("blue") w.draw_string(10,30,"Function: " || read()) } "q" : exit() } } }
Также нетрудно заметить, что перехватываемые события значительно отличаются от
тех, что приняты в классическом Icon (это события Mouse.LEFT_PRESS и Mouse.RIGHT_
PRESS). На нажатие левой клавиши мыши завязана выдача текущих координат курсора,
а на правую клавишу — считывание подписи для графика и вывода ее в окно.
Но, вставлять такой цикл разумно только после выполнения программой основной работы, т.е после вычерчивания графика. Для выполнения же этой задачи создается вот такой класс:
class Plot() public static grid(w) local i every i:=0 to 500 by 10 do { w.draw_line(i,0,i,500) w.draw_line(0,i,500,i) } w.set_fg("black") w.draw_line(250,0,250,500) w.draw_line(0,250,500,250) end public static gplot(w,func) local i,s,k i := -250 while i < 250 do { s := 150 * func(i/20.0) k := 250-s gradient(w,s) w.draw_point(250+i,k) w.draw_line(250+i,k,250+i,250) i +:= 1 } end public static plot(w,func,col,dx) local i,s,k i := -250 /col := "red" /dx := 0.001 w.set_fg(col) while i < 250 do { s := 150 * func(i/20.0) k := 250-s w.draw_point(250+i,k) i +:= dx } end private static gradient(w,s) if s>=200 then w.set_fg("black") else { if 150<=s<200 then w.set_fg("violet") else { if 100<=s<150 then w.set_fg("blue") else { if 50<=s<100 then w.set_fg("light blue") else { if 0<=s<50 then w.set_fg("cyan") else { if -50<=s<0 then w.set_fg("green") else { if -100<=s< -50 then w.set_fg("yellow") else { if -150<=s< -100 then w.set_fg("orange") else { if -200<=s< -150 then w.set_fg("pink") else { if s< -201 then w.set_fg("red") } } } } } } } } } end end
Теперь, небольшие пояснения по работе этого класса. Метод grid построит координатную сетку и выделит оси, идущие из начала координат. Как и другие методы класса, он требует в качестве обязательного аргумента переменную, содержащую объект класcа Window.
Метод plot требует несколько аргументов: окно для отрисовки, функция для отрисовки графиков, цвет графика и "разрешающая способность" графика, причем цвет и "разрешающая способность" являются необязательными аргументами.
Метод gplot требует два параметра - окно для отрисовки и функцию, но работает этот метод очень интересным образом: процедура раскрашивает площадь под графиком в разные цвета в зависимости от того, какое значение принимает функция в этих точках, т.е происходит так называемое градиентное окрашивание.
Для подобного действия используется приватный метод (т.е внутренний метод класса) gradient, производящий оценку значения и исходя из этого задающий цвет тона.
Ну а напоследок, парочка интересных функций в градиентной окраске.
Обратная функция Гудермана:
Дробная часть числа:
Функция Гудермана:
Ненормированный кардинальный синус:
Треугольная функция:
Прямоугольная функция:
Пила:
Секанс:
Синус интегральный:
Не забудьте после строчек импорта поставить invocable all. Размещаю код класса ExtMath, который был использован:
[accordion][panel intro="Код класса под спойлером. Кликните для просмотра."]
package ext.Math import util(Math), ipl.factors(factorial), ipl.math class ExtMath() public static const syms, eiler private static init() syms := &digits++&ucase eiler := 0.5772156649015328606065120 end public static to_sys(x,n) local r,s,i,t,res s := list() t := list() while x >= 1 do { r := x%n put(s,r) x := integer(x/n) } every i := *s to 1 by -1 do { put(t,syms[s[i]+1]) } res:="" every i := 1 to *t do { res ||:= t[i] } return res end public static from_sys(x,n) local r,t,i r := 0 x := reverse(x) every i := 1 to *x do { if x[i] == !syms then t := find(x[i],syms)-1 r +:= t*(n^(i-1)) } return r end public static to_sys2(x,n1,n2) local tmp tmp := from_sys(x,n1) return to_sys(tmp,n2) end public static root(x,y) return x^(1.0/y) end public static discriminant(a,b,c) return (b^2) - 4 * a * c end public static sgn(x) if x < 0 then return -1 else { if x = 0 then return 0 else return 1 } end public static frac(x) return abs(x - integer(x)) end public static floor(x) return integer(x - frac(x)) end public static ceil(x) if frac(x) = 0 then return x else return 1 + floor(x) end public static heavyside(x) if x < 0 then return 0 else { if x = 0 then return 0.5 else return 1 } end public static heavyside2(x) if x < 0 then return 0 else return 1 end public static rect(x) if abs(x) > 0.5 then return 0 else { if abs(x) = 0.5 then return 0.5 else return 1 } end public static tri(x) if abs(x) < 1 then return 1 - abs(x) else return 0 end public static saw(x,a) local tmp /a := 1 tmp := x/a return tmp - floor(tmp + 0.5) end public static sinc(x) if x = 0 then return 1 else return Math.sin(Math.PI * x)/(Math.PI * x) end public static sinc2(x) if x = 0 then return 1 else return Math.sin(x)/x end public static weierstrass(a,b,x) local i,k,m,s i:=0 s:=0 while i < 10 do { k := Math.cos(Math.PI*x*(a^i)) m := b^i s +:= k*m i +:= 1 } return s end public static si(x) local i,g,s,k,v i := 0 s := 0 while i < 20 do { k := (2*i)+1 g := (-1)^i v := (g*(x^k))/(factorial(k)*k) s +:= v i +:= 1 } return s end public static ci(x) local i,s,k s := eiler+Math.log(x,Math.E) i :=1 while i<20 do { k := (((-1.0)^i)*(x^(2.0*i)))/(factorial(2.0*i)*2.0*i) s +:= k i +:= 1 } return s end public static erf(x) local a,b,c,s,i a := 2.0 / Math.sqrt(Math.PI) s := 0 every i:=0 to 20 do { b := 2*i + 1 c := ((-1)^i * (x^b)) / (b * factorial(i)) s +:= c } return a * s end public static erfc(x) return 1 - erf(x) end public static gd(x) return 2 * Math.atan(Math.exp(x)) - (Math.PI / 2.0) end public static arcgd(x) return 0.5 * Math.log((1.0+Math.sin(x))/(1.0-Math.sin(x))) end public static sec(x) return 1.0 / Math.cos(x) end public static cosec(x) return 1.0 / Math.sin(x) end public static ctg(x) return Math.cos(x) / Math.sin(x) end public static ei(x) local s,i,a s := Math.log(x) + eiler every i := 1 to 20 do { a := (x^i) / (factorial(i)*i) s +:= a } return s end public static li(x) return ei(Math.log(x)) end public static cth(x) return 1.0 / tanh(x) end public static sech(x) return 1.0 / cosh(x) end public static csch(x) return 1.0 / sinh(x) end end
[/panel][/accordion]
P.S: Класс ExtMath скоро попадет в библиотеку функций ObjectIcon.