셀빅을 위한 산술/행렬 등의 수학문제 풀이용 언어입니다. [Calc] 모드를 이용해서 계산기처럼 사용할 수도 있습니다.
[Calc], [Del], [New], [Dup], [Edit], [Run] 등
단, [[...]]와 같이 [[ ]]로 둘러싸인 경우 좀 더 특별한 의미를 가집니다.
예를 들어, solve(f(_x)[,x1[,x2[[n,tol,nbi,nbo,f]]]])와 같은 사용법은 우선 [,x1[,x2[[n,tol,nbi,nbo,f]]]]와 같이 ,x1[,x2[[n,tol,nbi,nbo,f]]]이 [ ]로 둘러싸였기 때문에 전체 생략 가능합니다.
만약 생략이 되지 않을 경우 ,x1는 생략할 수 없으며 ,x2[[n,tol,nbi,nbo,f]]가 다시 [ ]로 둘러싸였기 때문에 전체 생략 가능합니다.
만약 생략이 되지 않을 경우 n,tol,nbi,nbo,f가 [[ ]]로 둘러싸였습니다. 이 부분에서는 f의 값을 설정하고 싶은데 그 앞의 n,tol,nbi,nbo 값은 기본값으로 두고 싶을 경우 ,,,,f 와 같이 앞의 인자를 비워두면 비워둔 인자의 값은 기본값이 됩니다. ,,nbi,,f 처럼 사용합니다.
[n,tol,nbi,nbo,f]와 같이 되어 있다면 f값을 설정하기 위해서 ,,,,f 와 같이 하면 앞의 값들은 모두 0이 됩니다. 잘 구분해서 사용하세요.
regr(xy[m*2],n,{f,c})의 경우 regr(xy[],n,f), regr(xy[],n,,c), regr(xy[],n,f,c) 세가지 사용법이 가능합니다.
위 화면이 메인화면입니다. 보이는 리스트들은 모두 mafi 프로그램 소스의 첫줄이거나 프로그램 이름입니다. :로 시작하는 줄이 프로그램 이름입니다. 위쪽에 보시면 버튼이 있습니다.
아래에서 설명할 KBD를 호출합니다. 여기서는 [KBD]의 KBD와 다른 점만 설명합니다. 0~9 자판의 첫줄만 다릅니다.
[New] 또는 [Edit]를 실행하면 나오는 화면입니다.
[Run] 을 누르면 프로그램이 실행됩니다. 실행중인 결과를 계속 볼 수 있습니다. 실행이 마치면 나타나는 화면입니다.
실행 중일 경우 상단의 [OK] 버튼 오른쪽에 Running... 이라는 표시가 나타나며 하나의 명령 단위로 상단의 버튼이 있는 영역을 제외한 아래의 넓은 영역을 클릭하면 중지(Pause 표시)/ 계속(Running...)이 가능합니다. 실행중 [OK]를 누르면 메인화면으로 빠져나갑니다. 실행이 종료되면 Done 표시가 나타납니다.
그래프를 그리는 중에도 중지/계속이 가능합니다. 단 3차원 그래프는 하나의 라인을 기준으로 작동합니다. 그래프가 그려지는 영역을 누르면 중지/계속을 반복합니다.
그래핑 영역을 드래깅하면서 좌표축을 이동할 수 있습니다.
단, 3차원 그래프의 경우 회전도 지원하므로 좌표축 이동을 위해서는 그래핑 영역의 중앙 20*20 크기의 사각형 부분을 드래깅해야 합니다. 이 영역이 눌려지면 사각형이 반전됩니다. 그 바깥부분의 드래깅은 회전을 의미합니다.
mafi 키보드입니다. 처음 뜨는 자판은 0~9 자판이라고 이름 붙였으며 0에서 9까지의 숫자와 연산자 등을 모아 두었습니다. [A~Z] 라는 버튼이 오른쪽 상단에 보입니다. 이 버튼을 누르면 A~Z 자판이라고 이름 붙여진 자판으로 바뀝니다. A~Z 자판은 a에서 z까지의 알파벳과 문자열 입력에 필요한 기호들을 모아 두었습니다.
[A~Z] 버튼이 반전된 이유는 현재 보이는 0~9 자판을 계속 사용한다는 뜻입니다. 지금 0~9 자판에 있는 키를 입력하다가 단 한번의 A~Z 자판 사용이 필요할 경우 [A~Z] 버튼을 한번 누릅니다. 그러면 A~Z 자판이 나오면서 [0~9] 버튼은 반전되어 있지 않습니다. 현재 자판에서 다른 자판을 호출하는 버튼이 반전되어 있지 않다는 말은 현재의 자판은 단 한번 사용된다는 뜻입니다.
즉, 0~9 자판에서 [A~Z] 버튼이 반전되어 있지 않다면 0~9 자판에서 하나의 키를 누르고 나면 A~Z 자판으로 바로 넘어가 버립니다. 그 반대도 마찬가지입니다. 이는 한번의 다른 자판 입력을 편리하게 하기 위함입니다.
다른 자판을 계속 사용하기 위해서는 다른 자판이 나타나고 해당하는 [0~9] 또는 [A~Z] 버튼이 반전되어 있지 않을때 그 버튼을 다시 한번 누르면 반전이 됩니다. 이제부터는 새로운 자판을 계속 사용하게 됩니다.
0~9 자판에서 [Func] 버튼의 기능은 다음과 같습니다.
[Func] 버튼을 누르면 [Func] 버튼은 반전이 됩니다. 그리고 다른 버튼을 한번 누르면 기능을 다하고 정상으로 돌아옵니다. [Func] 을 누른 상태에서 취소하고 싶을 경우 상단의 KBD라는 타이틀을 찍으면 취소됩니다.
[Spc]는 스페이스, [Tab]은 탭, [Enter]는 엔터, [Back]은 백스페이스입니다.
[OK]는 입력을 완료하고 키보드를 호출한 화면으로 돌아갑니다.
A~Z 자판에서 [Func] 버튼의 기능은 다음과 같습니다.
KBD 타이틀바 오른쪽에 팝업이 있습니다. 이 팝업에는 mafi 프로그램 문법에서 지원하는 여러가지 예약어들이 준비되어 있습니다. 한 글자씩 입력할 필요없이 필요한 것은 찾아서 선택하면 입력이 됩니다.
내용중 (가 들어가는 것들은 (까지만 입력되고 그 이후는 함수의 사용법으로 참고하기 위해 넣어 둔 것입니다.
이렇게 입력된 문장은 [OK]를 누르면 실행이 됩니다. 실행화면이 보이면서 실행을 시작합니다. 종료 후 [OK]를 누르면 다시 키보드로 돌아옵니다. 이렇게 돌아와 보면 방금 입력했던 문장의 뒷부분이 ;;로 바뀐 것을 볼 수 있습니다. 이 표시는 사용자가 실행시켰던 문장들의 단위를 나타내 줍니다. 만약 사용자가 여러문장을 ;로 구분해서 입력한 후 실행했을 경우 맨 마지막 부분만 ;;로 바뀝니다. 그 사이의 ;는 그대로인 것은 사용자의 문장입력 단위에 포함되는 부분이기 때문입니다.
위와 같이 ;;로 구분된 문장들은 사용자가 이미 실행했던 문장을 다시 실행하기 위해서 사용됩니다.
a=123;
a*2;;
a=32;;
위의 예에서는 사용자가 두번의 실행을 했다는 것을 알 수 있습니다. 처음 문장은
a=123;
a*2;
두 번째 문장은
a=32;
가 되겠습니다.
이런 상황에서 다시 처음의 문장을 실행하고 싶다면 다시 입력할 필요없이 처음의 문장이 위치한 첫번째 ;;를 포함한 앞부분에 커서를 위치시키고 [OK]를 눌러줍니다. 그러면 커서가 위치한 곳에서 앞으로 검색을 시작합니다. ;;를 만나거나 입력의 첫부분을 만나게 되면 그 부분에서 커서 뒤쪽으로 처음 만나는 ;;까지 실행을 합니다.
즉,
a=123;
a*2;
가 실행됩니다.
위의 성질을 이용해서 이미 입력했던 문장들에 ;;를 삽입, 삭제하면서 문장의 실행단위를 바꿀 수 있습니다.
a=123;;
b=234;;
a=789;;
b=345;;
a=456;;
와 같은 상황일 경우 a=456, b=345일 것입니다. 이 때 b=234 부분에 커서를 두고
[OK]를 누르면 a=456, b=234가 됩니다. 이렇게 입력된 문장을 다음과 같이
고칩니다.
a=123;;
b=234;
a=789;;
b=345;;
a=456;;
b=234;;를 b=234;로 바꿨습니다. 이제 커서를 b=234 부분에 두고 [OK]를 누릅니다.
결과는 a=789, b=234가 됩니다. b=234 부분의 실행단위가 a=789;;까지 확장된
것입니다.
다음과 같은 상황에서는 주의하셔야 합니다.
"a=123";a=123;;
a;;
c=123;;
위에서 "a=123";a=123;; 을 다시 실행하기 위해서는 반드시 " "를 포함한 문자열을
제외한 부분에 커서를 위치시켜야 합니다. 그렇지 않으면 문자열의 범위를 찾기
위해 "의 짝을 찾는 과정에서 오류가 날 것입니다. "asd";; 와 같이 문자열 문장만
있을 경우 ;; 부분에 커서를 두면 됩니다.
프로그램의 이름은 프로그램 소스의 최초 문자가 :로 시작해야 하며 그 다음 공백없이 프로그램 이름이 옵니다. 프로그램 이름은 ;로 끝나며 다음부터 실행문장이 입력됩니다.
a=123;
a*2;
위와 같은 프로그램은 메인화면에서 a=123; 이라는 한줄만 보입니다. 따라서 어떤
작업을 하는지 알기 위해서는 [Edit]를 이용해서 전체 소스를 볼 수 밖에 없습니다.
하지만 아래와 같이 이름을 붙이면
:a*2;
a=123;
a*2;
메인화면에서 :a*2 처럼 보이므로 작업의 내용을 알기 쉽습니다. 제목줄은 실행되지
않습니다. 위의 프로그램을 call()을 이용해서 호출하기 위해서는 call(a*2) 처럼
입력하면 됩니다. a*2가 이름이지 :a*2가 이름이 아닙니다. 메인화면에는 이름인
것을 명시하기 위해 :a*2 처럼 보입니다.
C와 C++ 주석을 그대로 사용합니다. /* */ 는 여러줄 주석입니다. 단 C와의 차이점은 중첩된 주석을 허용합니다. /* 주석 1 /* 주석 2 */ 주석 3 */ 처럼요. // 는 한줄 주석입니다.
명령을 구분하는 구분자는 세미콜론(;)입니다. 이는 C의 문법과도 비슷합니다. 단 브레이스({ })로 둘러싸인 ; 로 구분된 명령들은 모두 하나의 그룹으로 간주되어 { 부터 } 까지가 하나의 명령이 됩니다.
이후에 설명할 문자열 내의 공백을 제외한 모든 공백은 무시되어 없어집니다. 따라서 a b c 처럼 입력되었을 경우 abc와 같이 되어버립니다. 이점 잘 이해하세요.
하나의 명령을 구분하는 것은 셀빅에서 아주 중요합니다. 왜냐하면 셀빅은 이벤트기반 OS를 가지고 있기때문에 이벤트를 발생시키지 못하는 구간을 이해해야만 '아~ 이거이 따운되겠구만' 하고 알 수가 있습니다. 현재 mafi는 하나의 명령 내에서는 어떤 이벤트도 받을 수가 없습니다.
여러명령들을 하나의 명령처럼 묶을 수 있습니다. 쉼표(,)로 여러명령들을 붙인 다음 마지막 명령 뒤에만 ; 를 붙여주면 됩니다. a=12,a; 처럼요.
대입연산자는 등호(=)입니다. 대입연산자는 스칼라변수, 행렬, 사용자 함수, 사용자 루틴 등을 대입, 생성하는데 사용됩니다.
C에서의 의미와 거의 같습니다.
이외의 일반적인 연산은 당근 지원합니다.
아래 두식의 차이를 이해하시겠는지요. ^^;
1 == 2 < 3;
1 == (2 < 3);
2진법, 8진법, 10진법, 16진법을 지원합니다. 10진수의 표현은 일반적인 실수표현과 동일합니다.
16진수의 표현은 0h와 함께 C에서의 0x도 지원하며, 알파벳의 대소문자는 구별하지 않습니다.
10진수를 제외한 진법에서의 실수표현 방법에는 fixword 표기법을 사용합니다. 2, 8, 16진법에서는 소수점을 사용할 수 없는 대신 fixword fraction bit라는 개념이 있습니다. 예를 들어 2, 8, 16진법의 수를 2진수로 나타내 보겠습니다.
0b1011110001
위와 같이 표현될 경우 fixword fraction bit(앞으로 frac)를 0으로 하면 전체가
정수부가 됩니다. frac가 4가 되면 최우측부터 4개의 비트가 소수부, 그 상위가
정수부가 됩니다. 위의 경우 frac가 4가 되면
0b101111(정수부), 0b0001(소수부)
위와 같이 됩니다. 하지만 0b101111 + 0.1*0b0001과 같은 식의 계산으로 값이
구해지지는 않습니다. 계산은 다음과 같습니다.
0b1011110001 / (1<<4)
즉 0b1011110001의 정수값을 1<<4의 값으로 나눕니다. 1<<4는 2^4와
같습니다. 정수부에는 0b101111의 값이 그대로 나타나게 됩니다. 하지만 소수부는
전혀 다른 모양이 됩니다.
위의 모든 frac 값들은 기본으로 0입니다.
입력은 위와 같이 하지만 기본 출력은 10진수입니다. 따라서 출력 진법을 바꾸기 위해서 _v_base 변수를 사용합니다.
기본 출력 진법은 10진법이며, 각 진법의 출력양식 지정방법은 다음과 같습니다.
위의 방법들을 사용해서 진법변환을 해보겠습니다.
_b_frac=0;
_v_base=2;
12345; // 10진수 12345를 2진수 0b11000000111001로 출력
_h_frac=0;
_v_base=16;
0b11000000111001; // 2진수 0b11000000111001을 16진수로 출력
위에서와 같이 _b_frac=0; _h_frac=0; 의 문장을 넣어주는 이유는 frac은 입출력
모두에 영향을 미치기 때문입니다. 입력받을 때 실수로 환산이 되므로 출력시 다시
해당 frac을 적용해야만 합니다.
f(x)=x-0xb;
f(0x123 + 0b11101 - 0o17 - 312);
위와 같이 각 진법의 수를 하나의 수식에서 함께 사용할 수 있습니다.
2, 8, 16진법에서의 음수를 이해하는 것 또한 과제입니다. mafi에서의 스칼라 값은 전체가 4바이트 길이입니다. 즉 32비트입니다.
-1을 2진수로 표현해 보겠습니다. 1은
00000000 00000000 00000000 00000001
이 되겠습니다. -1은
11111111 11111111 11111111 11111111
입니다. 자 여기서 두 2진수를 더해봅니다. 그러면
1 00000000 00000000 00000000 00000000
32비트 범위를 넘어갑니다.
-3을 2진수로 표현해 보겠습니다. 3은
00000000 00000000 00000000 00000011
이 되고 -3은
11111111 11111111 11111111 11111101
입니다. 다시 더해보면 역시
1 00000000 00000000 00000000 00000000
입니다.
그러면 음수의 표현은 어떻게 하고 있는 걸까요? 짐작이 갑니다. 2의 보수입니다.
양수 + 음수 = 1 00000000 00000000 00000000 00000000
의 공식에서
음수 = 1 00000000 00000000 00000000 00000000 - 양수
라는 공식이 나옵니다. 여기서 보면
1 00000000 00000000 00000000 00000000는 4바이트 범위를 넘어버리므로
4바아티까지만 읽어보면 0이 됩니다. 따라서
음수 = 0 - 양수
라는 식과 동일한 식이 됩니다. 차이점은 음수라는 개념이 없는 상태에서 음의
2진수를 구한다는 겁니다.
다른 식으로 표현해 보면
음수 = ~양수 + 1
입니다. ~는 비트를 반전하는 연산자입니다. 0111101은 1000010 처럼 말이죠.
이처럼, 2, 8, 16진법의 음수는 해당하는 수를 2진수로 변환한 다음 그 음수를 구해서 다시 원래의 진법으로 변환하는 방법을 사용해서 구합니다.
진법 설명에서도 이미 소개된 연산자가 몇 있습니다.
예를 들어봅니다.
0b110110 << 2 // 0b11011000
0b110110 >> 2 // 0b001101
-1 >> 2 // 0xffffffff
/*
-1 = 11111111 11111111 11111111 11111111
따라서 -1 >> 2는
11111111 11111111 11111111 11111111 >> 2
결과는
00111111 11111111 11111111 11111111
이 아니라
11111111 11111111 11111111 11111111
입니다. 부호비트가 계속 1인 상태로 shift되기 때문입니다.
C와 같습니다.
*/
0b110110 & 0b000010 // 0b000010
0b110110 | 0b101010 // 0b111110
0b110110 ' 0b101010 // 0b011100
~0b110110 // 0b11111111 11111111 11111111 11001001
여기서, ~는 전체 32비트를 대상으로 bit not을 계산하므로 주의해야 합니다.
행렬을 표현하기 위해서 브래킷([ ]), 쉼표(,), 세미콜론(;)을 사용합니다. [ ] 사이에 위치한 ; 는 명령을 구분하는 ; 와는 다릅니다. , 는 같은 행에서 열을 구분하고 ; 는 행을 구분합니다.
예를 들어 [ 1, 2, 3; 4, 5, 6; 7, 8, 9; 10, 11, 12] 처럼 입력이 되면 아래와 같은 행렬을 의미합니다.
[ 1 2 3 ]
[ 4 5 6 ]
[ 7 8 9 ]
[ 10 11 12 ]
4 * 3 행렬이 되죠. 한 줄에 입력하기에 헷갈리시면 다음과 같이 입력을 해도
괜찮습니다. 모든 공백은 무시된다고 했으니까요.
[ 1, 2, 3;
4, 5, 6;
7, 8, 9;
10, 11, 12 ]
행렬에 사용할 수 있는 연산들을 설명합니다.
문자열은 큰 따옴표(")로 둘러싸입니다.
"출력할 문자열을 이렇게 씁니다.
엔터를 치면 엔터쳐진 상태로 화면에 보입니다.
공백은 무시되지 않습니다."
문자열에서 줄바꿈을 위해서는 위의 예처럼 직접 엔터를 넣는 방법과 "\r"을 넣는
방법이 있습니다.
"이것은 \r엔터와 같습니다."
간단한 문자열 변수를 지원합니다. 현재의 문자열 변수는 간단한 대입, 출력, "..." 대체사용 정도만 지원합니다. 예를 중심으로 설명합니다.
a="Wow! a=%v"; // 문자열 변수 생성
$a; // 문자열 변수 출력
b="$a abc ${a}123, $a123"; // $a와 123 사이에 공백을 출력하고
// 싶지 않을 경우 $a123은 틀린
// 표현입니다. $a123이란 문자열을 찾게
// 됩니다. 따라서, ${a}123 처럼 문자열
// 변수명 앞뒤로 { }를 넣어줍니다.
$b;
printf($a);
a=123;
printf($a, a); // printf("Wow! a=%v", a);와 동일
sprintf(a, $a, a); // 첫번째 인자 a는 문자열 변수
// printf($a, a)의 결과가 $a 문자열
// 변수에 대입됩니다.
printf("\$a=\"$a\""); // 확인
b="123*2";
eval($b); // 123*2 를 실행
eval("1-2"); // 1-2 를 실행
eval("$b-1"); // 123*2-1 을 실행
위의 예에서 eval() 함수는 수식 안에서 사용될 수 없습니다.
a=123;
"a = "; a;
A=[1, 2; 3, 4];
"Matrix A = [\r"; A[]; "]\r";
"a=" 에는 줄바꿈이 없으므로 바로 뒤에 a의 값인 123이 옵니다. "Matrix A=\r"에는
줄바꿈이 있고 내부 행렬출력 루틴에 의해 행렬이 출력됩니다. 요소값들만
출력됩니다. 따라서 앞뒤로 [ ]를 넣어줬습니다. a;에 의해 a값이 출력된 다음
줄바꿈이 되죠? 만약 바로 뒤에 줄바꿈하지 않고 다른 값을 출력하고 싶으면 앞에서
설명한 , 를 사용하면 됩니다. "a = ", a, ", b = ", b; 처럼요. ,는 바로 앞이
변수출력이면 줄바꿈을 하지 않습니다.
여기서 행렬의 대입과 사용을 설명드리죠. 행렬의 표현은 이미 설명을 드렸습니다. 그렇게 표현된 행렬을 행렬변수에 대입하고 싶으면 그냥 원하는 "행렬이름=행렬표현" 라고 하면 됩니다. 사용할 때는 입력할 때와는 달리 행렬변수명 바로 뒤에 []를 붙여서 행렬이라는 것을 알려줘야 합니다.
위와 같이 한 이유는 행렬변수와 스칼라변수 간의 변수명에 서로 제약을 주지 않기 위해서 입니다. 즉 일반변수 a와 행렬변수 a가 공존할 수 있습니다. 완전히 별개입니다. 물론 대소문자 구분하져.. ^^
a=123;
a=[1,2];
a;
a[];
변수출력의 형식을 바꾸고 싶을 경우 _v_width, _v_prec 환경변수를 사용합니다. _v_width는 변수출력 폭을 지정합니다. _v_prec는 변수의 유효자리수를 지정합니다.
_v_width=10;
_v_prec=0;
123; // " 123"
_v_prec=4;
123; // " 123.0000"
12350000000000; // "12350000000000.0000"
_v_width=-4;
123; // " 123"
12350000000000; // "1.235e+13"
mafi 0.924부터 변수명으로서 한글의 사용이 가능합니다.
프로그램의 실행 도중 사용자의 입력을 받고 싶을 때가 있습니다.
만약 프롬프트를 지워버리고 c=123 이라는 문장을 입력하면 의도했던 a 값은 입력받지 못 하고 c=123 이라는 문장을 실행하게 됩니다.
a= 프롬프트를 그대로 두고 [OK]를 누르면 아무런 문장도 실행되지 않습니다.
"음.. 한문장"?;
위 문장은 "음.. 한문장" 이라는 프롬프트가 출력되고 사용자
입력을 대기합니다. 변수명=? 처럼 프롬프트를 변경시키면
안 됩니다. 프롬프트가 나타난 상태에서 커서가 위치한 앞쪽으로는
변경을 하면 안 됩니다. 변경하면 엉뚱한 문장이 실행될 수
있으므로 조심하세요.
"음.. 한문장" 이라는 프롬프트가 나타났을 때 a=123 이라는 문장을 입력해서 키보드에 "음.. 한문장a=123" 이라는 문장이 보인다고 가정했을 때 실행되는 문장은 "a=123"입니다. 즉, 프롬프트 바로 다음 글자부터 끝까지 실행을 하는 것입니다. 그러므로 프롬프트는 건드리면 안 되는 겁니다.
이 경우의 단점은 "음.. 한문장" 이라는 프롬프트 만으로는 어떤 변수의 값을 입력 받기를 원하는지 알 수 없다는 것입니다. 그렇다고 "음.. 한문장 a=" 이라는 프롬프트를 주게 되면 "123"을 입력해서 "음.. 한문장 a=123" 상태에서 [OK]를 누르게 됩니다. 그러면 프롬프트 바로 다음인 "123"을 실행하므로 원하는 작업이 아닙니다.
이런 문제는 "음.. 한문장;;\ra="? 와 같은 문장으로 해결할 수 있습니다. 여기서 프롬프트에 ;;가 포함되어 있습니다. 프롬프트에서 최초로 만나는 ;; 바로 다음부터 실행할 문장으로 인식하게 됩니다. 그러므로 프롬프트가 "음.. 한문장;;\ra="인 상태에서 "123"만 입력하게 되면 "음.. 한문장;;\ra=123"이 되며 ;; 다음부터인 "a=123"을 실행하게 됩니다.
예입니다.
a=12;
printf("a=%v 뭐 할래?;;\rx=", a)?;
x;
위에서의 printf()는 실행화면에 출력하지 않고 입력키보드에
출력합니다. 공백은 무시되므로 "\rx=123" 문장은 "x=123"이 되어
실행됩니다. 123이 출력되겠네요.
"음...", a, "뭐?\r"?; // 전체가 ?에 종속되어 모든 출력이
// 키보드로 갑니다.
cos(20)?; // cos(20)값이 키보드로 출력됩니다.
"음...", a, "뭐?\r",?; // ? 바로 앞에 ,가 있어서 앞문장은
// 실행화면에 출력, 프롬프트없이
// 물어봅니다.
"음...", a, "뭐?\r";?; // 마찬가지네요.
cos(20),?; // 마찬가지네요.
정리하면 하나의 문장 맨 마지막에 ?가 있으면 사용자 입력대기 문장입니다.
? 바로 앞이 , 나 ; 면 그 앞 문장과 ? 는 별개가 되어서 프롬프트는 없습니다.
? 바로 앞이 , 나 ; 가 아니면 앞의 문장은 프롬프트가 됩니다.
프롬프트 문장이 될 수 없는 명령은 Ctrl 에 있는 명령들입니다. 단, printf()는 예외입니다.
*: 셀빅의 픽셀단위 좌표 또한 x, y의 증가방향은 각각 오른쪽, 위쪽으로 같습니다. 최하단 왼쪽 값은 (0,0) 최상단 오른쪽 값은 (_width,_height)입니다.
x=12;
func1(x,y)=(x*y)/10;
func1(2,3);
x;
위는 func1()이라는 일반함수를 정의한 다음 사용하는 예제입니다. 위에서 함수 내의
x, y와 밖의 x, y는 무관하다는 것을 확인할 수 있습니다. 만약 그렇지 않다면 x는
실행후에는 2가 되어 있어야겠죠.
a=2;
f(x)=a*x; // 중요합니다. f(x)=2*x가 아니라 f(x)=a*x입니다.
f(2); // 4
a=3;
f(2); // 6
위와 같이 함수의 정의에 포함된 변수들은 함수를 사용할 때의 값들이 사용됩니다.
이를 피하기 위해서는 아래와 같이 변수의 값 자체를 함수 정의시에 사용해야 합니다.
a=2;
sprintf(tmp,"f(x)=%v*x",a);
eval($tmp);
f(2); // 4
a=3;
f(2); // 4
행렬을 결과값으로 가지는 행렬함수도 정의할 수 있습니다.
A[](n,v)=M[](n,n,v);
A=A[](4,12);
A[];
B[](x[],v)=v*x[];
B=M[](3,4,1);
B=B[](B[],4.5);
B[];
결과는 상상에 맡깁니다. ^^;
단, while(조건){...}을 구현하기 위해 for(;조건;){...}
do{...}while(조건)을 구현하기 위해 for(-;조건;){...}
fstr(a,f)는 f() 함수의 정의를 $a 문자열 변수에 대입합니다.
fstr(a,f[])는 f[]() 함수의 정의를 $a 문자열 변수에 대입합니다.
이렇게 얻은 문자열은 다른 함수를 정의하는데 사용할 수 있습니다.
f(x)=12*x;
fstr(a,f); // $a="12*x"
sprintf(b,"a(x)=%s",$a); // $b="a(x)=12*x"
eval($b);
f(x) 함수를 a(x) 함수로 복사했습니다.
call(다른_프로그램_이름)
sound([m|m[][[,style,offset,duration]]])
sound() Beep
sound(0) Sound Off
sound(1) ~ sound(63) Predefined sound
_melody ~ _melody+29 또는 34 ~ 63: Melody
sound(_melody,0) 생성 중이던 앞의 소리를 off하고 새로운 소리를
1회 생성
sound(_melody,1) 생성 중이던 앞의 소리를 off하고 새로운 소리를
sound(0)이 불려질 때까지 계속 생성
sound(_melody,2) 생성 중이던 앞의 소리를 중단하고 새로운 소리를
1회 생성한 후 다시 중단되었던 소리를 계속 생성
기본 style은 0입니다.
sound(m[]) m[] 행렬로 지정된 악보를 연주하며 악보의 길이는
1024가 최대
m[]은 n*2 크기의 행렬 또는 n*1 크기의 행렬로서
1. n*2 크기일 경우 1열은 note, 2열은 duration(단위 msec) 이며,
2. n*1 크기일 경우 1열은 note, duration은 따로 지정하며 지정하지
않을 경우 100 msec입니다.
note의 표현은 16진수로 나타내며 아래와 같습니다.
0x4c 4C
-0x4c 4C#
0x4d 4D
-0x4d 4D#
0x4e 4E
0x4f 4F
-0x4f 4F#
0x49 4G 0x4g는 없기 때문에 g와 비슷한 9를 씁니다. ^^;
-0x49 4G#
0x4a 4A
-0x4a 4A#
0x4b 4B
이렇게 0x4? 부터 0x7? 까지 48개의 음이 있습니다.
pause는 1이고 off는 0입니다.
sound(m[],,,500) m[]이 n*1 크기일 경우 모든 duration을
500msec으로 설정하고 style은 기본값인 0입니다.
임의의 행렬 m[]을 연주하기 위해서는 offset을 지정해줍니다.
sound(m[],,0,100) m[]이 n*1 크기이며 악보행렬이 아닐 경우 0값을
4C로 잡아서 1씩 증가시키며 음값을 구합니다.
offset-1은 pause, offset-2는 off입니다.
sound(m[],,0) m[]이 n*2 크기이며 임의의 행렬입니다.
sound(R[](50,1,480),,0)하면 아마 기계적인 소리가 들릴겁니다. :)
48의 범위를 넘어서면 51로 나눈 나머지를 이용해서 음을 다시 계산합니다.
음악 만들려고 구현한 것은 아닙니다. 지루한 계산이 끝나는
시점을 알려주기 위해 추가했습니다.
똑같은 순서의 난수들을 재현하고 싶을 때 동일한 seed를 이용해서 srand() 해주면 되겠습니다.
주의: { } 그룹내에서는 label이 작동하지 않습니다. 이는 그룹내에서의 local()의 마무리작업 시점을 모호하게 하기 때문입니다. 또한 구조화 프로그래밍에 필요한 요소들을 모두 갖추었기 때문에 더이상 goto의 사용을 권장하지 않습니다. 단 언제든지 중지 가능한 루프를 작성할 경우에는 그룹을 전혀 사용하지 않고 local() 또한 사용하지 않으면서 goto를 사용하여 구현할 수 있습니다.
a(x,y)=
{
printf("a(%v,%v)=\r",x,y);
sum=0;
for(i=0; i<1000; i++){
if(i==10)
continue;
sum+=i;
if(i==x*y)
break;
}
return(sum);
}
a(3,4);
실행해 보세요.
기본값은 _refresh=0; 입니다.
기본값은 _max_loop=0; 입니다.
단 _g_on=1 일 경우 중지/계속의 단위는 하나의 그래핑 명령이 됩니다. _g_on=0 일 경우는 2D는 점 단위, 3D는 선 단위입니다.
기본값은 _g_on=0; 입니다.
_g_save=1; 명령으로 grf(), grf3d() 명령들을 저장합니다. 하지만 이 명령만으로 그래프를 재현할 수 없을 경우 _g_save=2; 명령을 사용하여 이 명령 다음부터 _g_save=0; 명령까지의 모든 문장의 문번호를 저장하여 그래프의 정상적인 재현을 도웁니다.
하지만, 그래핑 명령을 저장하는데 많은 메모리를 소모하고 속도 또한 느려질 경우 _g_save=0; 을 이용하여 축소/확대/이동을 포기하고 속도를 향상시킬 수 있습니다.
_g_save=1; for(i=0;i<3;i++) grf(_x^i);와 _g_save=2; for(i=0;i<3;i++) grf(_x^i);를 비교해 보세요. 전자는 grf(_x^i) 명령만 for() 루프를 도는 동안 네번 저장하게 됩니다. 이 명령을 재현할 경우 i의 값은 최종 값인 4가 되므로 하나의 그래프만 네번 그리게 됩니다. 하지만 후자는 for() 문장 자체를 지정하는 문번호를 저장했으므로 실행시와 동일한 화면을 재현합니다.
단, [Calc] 모드에서는 _g_save=2; 는 사용할 수 없습니다. 내부적인 구현의 차이 때문입니다.
기본값은 _g_save=0; 입니다.
기본값은 _g_axes=1; 입니다.
만약 좌표축상에 5라는 값이 있을 경우 이 축이 10으로 로그스케일화 되어 있으면 10^5인 값입니다.
_g_on=1;
grf(10^_x);
_g_logy=10; // y축을 10으로 로그스케일합니다.
grf(10^_x);
두개의 그래프를 비교해 보세요.
_g_logx, _g_logy가 0일 경우 일반 그래프를 그립니다. 0이 아닌 실수일 경우 이 실수를 로그스케일의 밑으로 합니다.
주의: grf()에만 적용되며 그래핑 범위는 로그스케일되지 않습니다.
기본값은 _g_color=1; 입니다.
기본값은 _g_dot=1; 입니다.
기본값은 _g_line=0; 입니다.
기본값은 _g_style=_replace; 입니다.
기본값은 _g_strf=_eng7; 입니다.
기본값은 _g_strh=-1; 입니다.
기본값은 _g_strv=-1; 입니다.
기본값은 _g_stru=0; 입니다.
각각의 돌려 받은 값은 _ret, _ret[]에 있습니다.
_width=160, _height=143
_grid([[dgx,dgy,ogx,ogy]]),
_polar(Fr(_x[,_y])), _para(Fx(),Fy()[,Fz()]),
_dplot(P[]), _vplot(P[]), _dmesh([[x1[],x2[]]],y[][,d]), _vmesh([[x1[],x2[]]],y[][,w])
_str([P,]"str"),
_mv2(P), _dot([P1]), _line([P1,]P2), _rect({(x|y|z),}[P1,]P2),
_arc([x1,y1,]x2,y2), _circ([x1,y1,]r), _elps([x1,y1,]rx,ry),
_cube([x1,y1,z1,]x2,y2,z2,x3,y3,z3,x4,y4,z4, x5,y5,z5,x6,y6,z6,x7,y7,z7,x8,y8,z8),
_getpos()
_julia(cr,ci[,n=20]), _mandelbrot([n=20]),
기본 그래핑 함수들입니다.
위의 grf()와 grf3d()를 제외한 나머지 함수들에 해당하는 값들은 기본값이 적당히 잡혀있습니다. 그냥 그려봤을때 원하는 결과가 아닐때만 약간씩 조정하면서 쓰세요.
grf()의 부함수 _grid() 함수를 사용해서 그래프 좌표상에 그리드를 그려넣을 수 있습니다.
grf()와 grf3d()의 부함수 _polar(), _para()를 이용해서 Polar Plot과 Parametric Plot을 그릴 수 있습니다.
grf()와 grf3d()의 부함수 _dplot(), _vplot()을 이용해서 행렬형태의 좌표쌍들을 플로팅할 수 있습니다.
위와 같은 P[]행렬 내의 좌표쌍들을 점으로 플로팅합니다.
예를 들어 봅니다.
A=[ 1, 2; // (1,2)
3, 4; // (3,4)
5, 10; // (5,10)
7, 10 // (7,10)
];
grf(_dplot(A[])); // A[] 행렬을 점으로 해석하여 그립니다.
grf(_vplot(A[])); // A[] 행렬의 점들을 연결한 벡터를 그립니다.
A=[ 2; // (1,2)
3; // (2,3)
10; // (3,10)
40 // (4,40)
];
grf(_dplot(A[])); // 이경우 A[] 행렬에는 y값만 있는 것으로
// 가정합니다.
A=[ 1, 2, 3; // (1,2,3)
3, 4, 3; // (3,4,3)
5, 10, 1; // (5,10,1)
7, 10, 4 // (7,10,4)
];
grf3d(_dplot(A[])); // A[] 행렬을 점으로 해석하여 그립니다.
grf3d(_vplot(A[])); // A[] 행렬의 점들을 연결한 벡터를 그립니다.
A=[ 2; // (1,2,0)
3; // (2,3,0)
10; // (3,10,0)
40 // (4,40,0)
];
grf3d(_dplot(A[])); // 이경우 A[] 행렬에는 y값만 있는 것으로
// 가정합니다.
A=[ 1, 2; // (1,1,2)
3, 4; // (2,3,4)
5, 10; // (3,5,10)
7, 10 // (4,7,10)
];
grf3d(_dplot(A[])); // 이경우 A[] 행렬에는 y, z값만 있는 것으로
// 가정합니다.
grf()와 grf3d()의 부함수 _dmesh(), _vmesh()를 이용해서 이 후에 설명할 interp2() 함수에서 정의한 x1[], x2[], y[] 행렬을 도시합니다.
grf()에서는 2차원 x1-x2 좌표 상에서 y값의 크기를 나타내야 하므로 d값을 0보다 크게하여 최대값을 표현할 점의 크기를 지정합니다.
grf3d()에서는 3차원 x1-x2-y 좌표 상에서 y값을 표현하기 때문에 x1-x2 평면 위로 솟은 y 벡터의 길이로 그 크기를 알 수 있으나 벡터의 종단에 역시 점으로 그 크기를 도시할 수 있습니다.
xy에서의 x, y와 x1[], x2[], y[]에서의 x1, x2, y는 구분해야 합니다. 전자는 3차원 공간상에서의 기본좌표축을 의미하며 x1, x2, y는 해석상의 좌표축입니다. 기본개념은 x1은 x축, x2는 y축에 해당한다고 보면 됩니다.
예를 들어 봅니다.
x1=[1,2,3,4,5];
x2=[1;
2;
3;
4];
y=[ 1, 2, 3, 4, 5;
6, 7, 8, 9, 10;
11, 12, 13, 14, 15;
16, 17, 18, 19, 20];
grf(_dmesh(,,y[])); // grf(_dmesh(x1[],x2[],y[]));
grf(_vmesh(x1[],x2[],y[],x));
grf(_dmesh(,,y[],2)); // grf(_dmesh(x1[],x2[],y[],2));
grf3d(_dmesh(10*x1[],10*x2[],y[],10));
grf3d(_vmesh(10*x1[],10*x2[],y[]));
마지막의 grf3d() 명령들을 실행한 결과입니다.
grf()와 grf3d()의 부함수 _str()를 이용해서 그래핑 영역에 문자열을 출력할 수 있습니다.
예를 들어봅니다.
f(x)=10*sin(x/5);
fstr(a,f);
_g_on=1;
_g_save=2;
_g_line=0;
grf(f(_x));
_g_line=1;
grf(_line(mx(90),f(mx(90)),mx(100),my(10)));
_g_strf=_kor9;_g_stru=1;
grf(_str(mx(100),my(10),$a));
결과는 다음과 같습니다.
grf()와 grf3d()의 기타 부함수들을 이용해서 도형을 그릴 수 있습니다.
부함수 설명에서 P를 인자로 가지는 함수는 grf(), grf3d() 모두에 해당하는 부함수이며 P는 2차원 x,y, 3차원 x,y,z를 의미합니다.
예를 들어 _line([P1,]P2)의 의미는 grf()의 부함수로서는 _line([x1,y1,]x2,y2)입니다. 여기서 [x1,y1,]는 생략가능합니다. 따라서 _line(x1,y1,x2,y2) 또는 _line(x2,y2) 처럼 사용합니다.
grf3d()의 부함수로서는 _line([x1,y1,z1,]x2,y2,z2)입니다. 따라서 _line(x1,y1,z1,x2,y2,z2) 또는 _line(x2,y2,z2) 처럼 사용합니다.
앞으로 그리기 시작점 (x,y) 또는 (x,y,z)는 P로 표시합니다.
_dot(): P에 점을 그립니다.
_dot(P1): P1에 점을 그려넣고 P를 P1으로 이동합니다.
_line(P2): P에서 P2까지 선을 그리고 P를 P2로 이동합니다.
_line(P1,P2): P1에서 P2까지 선을 그리고 P를 P2로 이동합니다.
_rect(x2,y2): (x,y)와 (x2,y2)을 대각선으로 하는 사각형을
그리고 (x,y)를 (x2,y2)으로 이동합니다. (grf())
_rect(x1,y1,x2,y2): (x1,y1)과 (x2,y2)를 대각선으로 하는
사각형을 그리고 (x,y)를 (x2,y2)로 이동합니다. (grf())
_rect(x,x2,y2,z2): 첫번째 인자가 x 이므로 x와 수직인 평면 y-z
평면에 수직인 사각형을 그립니다. 그 사각형의 대각선이
(x,y,z)와 (x2,y2,z2)를 잇는 선입니다. (x,y,z)를 (x2,y2,z2)로
이동합니다. (grf3d())
_rect(y,x1,y1,z1,x2,y2,z2): 첫번째 인자가 y 이므로 y와 수직인
평면 x-z 평면에 수직인 사각형을 그립니다. 그 사각형의 대각선이
(x1,y1,z1)와 (x2,y2,z2)를 잇는 선입니다. (x,y,z)를 (x2,y2,z2)로
이동합니다. (grf3d())
_arc(x2,y2): (x,y)와 (x2,y2)을 잇는 원호를 그리고 (x,y)를
(x2,y2)으로 이동합니다.
_arc(x1,y1,x2,y2): (x1,y1)과 (x2,y2)를 잇는 원호를 그리고
(x,y)를 (x2,y2)로 이동합니다.
_circ(r): (x,y)를 중심으로 하는 반지름 r인 원을 그립니다.
_circ(x1,y1,r): (x1,y1)을 중심으로 하는 반지금 r인 원을 그리고
(x,y)를 (x1,y1)으로 이동합니다.
_elps(rx,ry): (x,y)를 중심으로 하는 x반지름 rx, y반지름 ry인
타원을 그립니다.
_elps(x1,y1,rx,ry): (x1,y1)를 중심으로 하는 x반지름 rx,
y반지름 ry인 타원을 그리고 (x,y)를 (x1,y1)으로 이동합니다.
_cube(x2,y2,z2,x3,y3,z3,x4,y4,z4,x5,y5,z5,x6,y6,z6, x7,y7,z7,x8,y8,z8): (x,y,z), (x2,y2,z2), (x3,y3,z3), (x4,y4,z4) 네 점을 잇는 사각형과 (x5,y5,z5), (x6,y6,z6), (x7,y7,z7), (x8,y8,z8) 네 점을 잇는 사각형을 그리고 (x,y,z)-(x5,y5,z5), (x2,y2,z2)-(x6,y6,z6), (x3,y3,z3)-(x7,y7,z7), (x4,y4,z4)-(x8,y8,z8) 네 선을 그려서 육면체를 그립니다. (x,y,z)를 (x8,y8,z8)으로 이동합니다.
_cube(x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4,x5,y5,z5,x6,y6,z6, x7,y7,z7,x8,y8,z8): (x1,y1,z1), (x2,y2,z2), (x3,y3,z3), (x4,y4,z4) 네 점을 잇는 사각형과 (x5,y5,z5), (x6,y6,z6), (x7,y7,z7), (x8,y8,z8) 네 점을 잇는 사각형을 그리고 (x1,y1,z1)-(x5,y5,z5), (x2,y2,z2)-(x6,y6,z6), (x3,y3,z3)-(x7,y7,z7), (x4,y4,z4)-(x8,y8,z8) 네 선을 그려서 육면체를 그립니다. (x,y,z)를 (x8,y8,z8)으로 이동합니다.
현재의 그리기 시작점을 _x, _y, _z 변수에 할당해 줍니다.
grf(_mv2(10,20));
grf(_line(20,20));
grf(_getpos());
_x;_y;
_x=20, _y=20이 되겠습니다.
grf3d(_mv2(10,20,2));
grf(_line(20,20,5));
grf(_getpos());
_x;_y;_z;
_x=20, _y=20, _z=5가 되겠습니다.
_julia(cr,ci): Julia 집합 공식의 상수 C의 실수부 cr, 허수부
ci를 지정하여 도시합니다. 기본 반복회수 20회입니다.
_julia(cr,ci,n): Julia 집합 공식의 상수 C의 실수부 cr, 허수부
ci를 지정하고 반복회수를 n으로 설정하여 도시합니다.
_mandelbrot(): Mandelbrot 집합을 복소평면에 도시합니다. 기본
반복회수는 20회입니다.
_mandelbrot(n): Mandelbrot 집합을 반복회수 n으로 설정하여
도시합니다.
_*_(): 위에서 _getpos()를 제외한 부함수 이름뒤에 _를 붙인 함수들은 P를 기준으로
상대좌표를 이용해서 그립니다.
예를 들어 _line_(dx1,dy1,dx2,dy2)는 (x+dx1,y+dy1)과 (x+dx2,y+dy2)를 잇는 선을
그리고 (x,y)를 (x+dx2,y+dy2)로 이동합니다.
rho는 물체와 시점간의 거리, phi는 z축과 시점간의 각도, theta는 x축과 시점간의 각도, d는 물체를 투영할 투영면과 시점간의 거리입니다.
d가 커지면 물체가 크게 보이겠죠? d가 rho보다 작으면 물체는 실물보다 작아집니다. rho가 커지면 물체는 작게 보이죠. rho가 작아지면 물체는 커지지만 물체의 왜곡이 심해집니다. 이 왜곡을 없애기위해 d를 크게 해줍니다.
Van Wijngaarden-Dekker-Brent Method를 이용한 Root Finding을 제공합니다.
사용법을 정리해 보면
solve(f(_x)) [-1, 1] 구간에서 시작
solve(f(_x),x1) [x1-1,x1+1] 구간에서 시작
solve(f(_x),x1,x2) [x1,x2] 구간에서 시작 (*)
solve(f(_x),x1,x2,n) [x1,x2] 구간에서 n번 반복시도
solve(f(_x),x1,x2,,tol) [x1,x2] 구간에서 오차 tol
solve(f(_x),x1,x2,n,tol) [x1,x2] 구간에서 n번 반복시도, 오차 tol
solve(f(_x),x1,x2,,,nbi) [x1,x2] 구간을 nbi로 나누어서 근이
있는 구간을 내부에서 찾아감
"look inward"
solve(f(_x),x1,x2,,,,nbo) [x1,x2] 구간에 근이 없을 경우 구간을
nbo회수만큼 확장하면서 근이 있는
구간을 찾아감
"look outward"
solve(f(_x),x1,x2,,,,,f) "look outward"에서의 확장계수
solve(f(_x),x1,x2) 까지의 세가지 방법만 사용하면 충분할 겁니다.
[[n,tol,nbi,nbo,f]] 파라미터들은 이미 설명했듯이 자기 앞의 값을 기본값으로 하기위해 빈 파라미터를 둘수 있는 파라미터들입니다.
기본값들입니다.
x1=-1, x2=1, n=100, tol=1.0e-7,
nbi=[x1,x2] 구간을 0.1로 나눈 수(dx=0.1),
nbo=200, f=1.6
Gauss-Legendre Method, Trapezoidal Method, Simple Method 중 한가지 기법으로 적분을 합니다.
사용법을 정리해 보면
integ(cos(_x)*sin(_x),0,5) cos(_x)*sin(_x)를 0에서 5까지 적분
integ(cos(_x)*sin(_x),0,5,10) 위와 같으며 0부터 5구간을 10으로
나눕니다. 이 값이 작으면 값의
정확도가 떨어집니다. 기본값은
50입니다.
integ(cos(_x)*sin(_x),0,5,,trap) Trapezoidal integration으로
적분합니다. 이 방법은 많이 느린 것
같습니다. 그냥 넣어두었습니다.
기본 적분기법은 Gauss-Legendre
integration 입니다.
integ(cos(_x)*sin(_x),0,5,,simp) 가장 간단하고 빠르지만 정확도가
떨어지는 기법입니다. 기법이라고 할
것도 없이 그냥 직사각형으로 나눠서
값들을 더합니다.
integ(cos(_x)*sin(_x),0,5,,gleg) 기본 적분기법인 Gauss-Legendre
integration 입니다. 첫줄의 예제와
같습니다. 이 기법의 특징은 구간값이
바뀔 때 최초 계산시간이 약간 걸리고
다음의 같은 구간값에서의 계산은
아주 빠르게 수행됩니다.
정확도도 쓸만 합니다.
기법의 지정에서 gleg, simp, trap 처럼 앞의 네글자만 비교합니다.
glegendre, simple, trapezd 등도 가능합니다.
Euler Method, 4th-Order Runge-Kutta Method 중 한가지 기법으로 상미분 방정식을 적분합니다.
F(_x,_y)는 도함수 y'을 x, y로 나타낸 함수이며 mafi에서 x, y는 각각 _x, _y로 대신합니다. y0=f(x0)인 초기조건이며 f(x)의 값을 구합니다.
n이 주어지면 x0와 x를 n등분해서 계산합니다. 기본 n값은 100입니다.
m은 euler 또는 runge이며 각각 Euler, 4차 Runge-Kutta 기법을 지정합니다. 기본기법은 Runge-Kutta 기법입니다.
odeint(1-_y^2, 0, 0, 1) 1-y^2은 tanh(x)의 상미분 방정식
즉, tanh(1)의 값을 구합니다.
Centered Difference Differentiation Using 5-Point Derivative Formulas 기법을 이용해서 3차 수치미분까지 제공합니다.
diff(cos(_x),4) -sin(4) 값을 구하겠네요.
즉, x=4에서의 cos(x)의 1차 미분값
diff(cos(_x),4,,2) -cos(4)
즉, x=4에서의 cos(x)의 2차 미분값
diff(cos(_x),4,,3) sin(4)
즉, x=4에서의 cos(x)의 3차 미분값
h의 기본값은 1.0e-3이며 이 값이 작다고 좋은 결과를 얻는 것이 아닙니다. 너무
작으면 오히려 연산과정에서 오차가 더 생깁니다.
xy[]는 n*2 크기의 좌표쌍으로 이루어진 행렬입니다. x는 실수입니다.
A=[ 1, 1; // (1, 1)
2, 2; // (2, 2)
3, 3 // (3, 3)
];
interp(A[], 10); // 너무 쉬운 예입니다만 답은 10입니다.
// 위의 세점을 이용해서 x=10에서 외삽한
// 결과입니다.
x1[]는 1*n 크기의 x1축 좌표들입니다. x2[]는 m*1 크기의 x2축 좌표들입니다. y[]는 m*n 크기의 y값들입니다. (x1, x2) 좌표에 대해 내삽, 외삽을 적용합니다.
이런 경우에 사용합니다.
|
사각형 네 꼭지점 좌표에서의 y값들을 알고 있을때 임의의 좌표
x에서의 y값을 알고자 한다. x의 좌표는 (x1, x2)이다.
(1,1,10), (1,2,10), (2,1,20), (2,2,30)값을 알고 있다. (1.2,1.3,?)를 구하라. |
A=[1, 2]; // (1,?), (2,?)
// x축에 해당하며 가로방향
B=[1; // (?,1)
2]; // (?,2)
// y축에 해당하며 세로방향
C=[10,10; // (1,1), (1,2)
20,30]; // (2,1), (2,2)
interp2(A[], B[], C[], 1.2, 1.3); // 12.6
interp2(A[], B[], C[], 1.9, 1.9); // 27.1
Linear Regression 기법을 이용해서 xy[] 자료를 선형보간합니다.
data=[ 2.3; // (1,2.3)
3.4; // (2,3.4)
4.5; // (3,4.5)
5.3; // (4,5.3)
6.7; // (5,6.7)
7.8; // (6,7.8)
8.0; // (7,8.0)
6.7; // (8,6.7)
5.6; // (9,5.6)
3.4 // (10,3.4)
];
regr(data[],3,f); // f(x)=1.753+0.304*x+0.292*x^2-0.031*x^3
// ~ 3차
data=[ 1, 2.3; // (1,2.3)
2, 3.4; // (2,3.4)
3, 4.5; // (3,4.5)
4, 5.3; // (4,5.3)
5, 6.7; // (5,6.7)
6, 7.8; // (6,7.8)
7, 8.0; // (7,8.0)
8, 6.7; // (8,6.7)
9, 5.6; // (9,5.6)
10, 3.4 // (10,3.4)
];
regr(data[],3,,c); // c=[ 1.753; 0.304; 0.292; -0.031 ]
regr(data[],3,f,c); // f(x)=1.753+0.304*x+0.292*x^2-0.031*x^3
// c=[ 1.753; 0.304; 0.292; -0.031 ]
regr()의 출력인 1 또는 0은 성공 여부를 나타냅니다. 1은 성공 0은 실패입니다.
물론 실패하면 에러가 나게 됩니다.
grfxyr(-2.0,1.5,-1.5); grf(_mandelbrot());
_g_axes=0; _g_save=1; grfxyr(-2.0,1.5,-1.5); grf(_mandelbrot(,2,5));
grfxyr(-2.0,1.5,-1.5); grf(_julia(0.3,-0.4));
order=10;
factor=0.7;
turn=0.7;
tree(n,l,a)=
{
local(dx,dy);
dx=l*sin(a);
dy=l*cos(a);
grf(_line_(dx,dy));
if(n>0){
tree(n-1,l*factor,a+turn);
tree(n-1,l*factor,a-turn);
}
grf(_mv2_(-dx,-dy));
}
_g_on=1;
_g_axes=0;
grfxr(0,6);
grfyr(0,4);
grf(_mv2(3,0));
tree(order,1,0);
order=10;
c(i,dx,dy)=
{
if(i==0)
grf(_line_(dx,dy));
else{
c(i-1,(dx+dy)/2,(dy-dx)/2);
c(i-1,(dx-dy)/2,(dy+dx)/2);
}
}
_g_on=1;
_g_axes=0;
grfxr(0,4);
grfyr(0,3);
grf(_mv2(1,2));
c(order,2,0);
koch()=
{
if(d<=0.005)
grf(_line_(d*ct[1,1+a%6],d*st[1,1+a%6]));
else{
d/=3;koch();a++;koch();
a+=4;koch();a++;koch();
d*=3;
}
}
_g_on=1;
_g_axes=0;
grfxyr(0,2,0);
ct=M[](1,6,0);
st=M[](1,6,0);
for(a=0;a<6;a++){
ct[1,a+1]=cos(a*_pi/3);
st[1,a+1]=sin(a*_pi/3);
}
grf(_mv2(0,0));
d=2;
a=0;
koch();
_g_axes=0; grf3dw(x); grfx(0,_pi); grfy(0,_pi); grfdxy(0.1,0.3); grf3d(_polar(20));
r(x,y)=5*y; _g_axes=0; grfx(2*_pi-3,2*_pi); grfy(0,2*_pi); grfdxy(0.2,0.2); grf3d(_polar(r(_x,_y)));
r(x,y)=5*y; _g_axes=0; grfx(0,2*_pi); grfy(0,2*_pi); grfdxy(0.5,0.5); _g_save=2; grf3d(_polar(r(_x,_y)));펜 드래깅으로 회전하고 있는 화면입니다. for()과 grf3dvp(), grfclr()를 사용하여 자동으로 회전할 수도 있겠네요.
r(x,y)=5*y;
_g_axes=0;
grfx(0,2*_pi);
grfy(0,2*_pi);
grfdxy(0.5,0.5);
for(theta=0;theta<=2*_pi;theta+=0.2){
grfclr();
grf3dvp(80,80,50,0.78,theta,100);
grf3d(_polar(r(_x,_y)));
}
_g_on=1; grfx(0,2*_pi); grfy(0,10); grfdxy(0.1,5); grf3d(_mv2(0,0,-10)); grf3d(_para_(10*cos(_x),10*sin(_x),_y)); grfy(0,2*_pi); grfdxy(0.5,0.5); grf3d(_mv2(5,0,5)); grf3d(_para_(_x*cos(_y),_x*sin(_y),_y));
_g_on=1;
_g_axes=0;
grfzoom(1.1);
grf3d(_cube(
0,0,0,
0,10,0,
10,10,0,
10,0,0,
0,0,10,
0,10,10,
10,10,10,
10,0,10
));
grf3d(_rect(x,10,10,10, 0,5,15));
grf3d(_rect(x,10,0,10, 0,5,15));
a(x,y)=x+y; // a() 함수 정의 grfxr(-10,10); // 셀빅화면에 보일 x의 범위 grfyr(-100,100); // 셀빅화면에 보일 y의 범위 grft(10,10); // 작은 눈금 간격 grftl(20,20); // 큰 눈금 간격 grf(_x*a(_x,12)*sin(_x)); // 그래프 1 grf(4*_x^3+2*_x^2+12); // 그래프 2
grfx(-20,20); // 실제로 그리기를 실시할 x의 범위
grfy(-20,20); // 실제로 그리기를 실시할 y의 범위
grfdxy(2,2); // x, y의 증가분
grf3dvp(80,80, // 셀빅화면의 중앙을 (0,0,0)점으로 잡음
30, // 물체와 시점과의 거리 30
40*_pi/180, // x축과 시점과의 각도 40도
60*_pi/180, // z축과 시점과의 각도 60도
100); // 투영면과 시점과의 거리 100
grf3d(30*exp(-0.1*(_x^2+_y^2))); // 그래프
grfx(-100,100);
grfy(-100,100);
grfdxy(2,2);
grf3dw(x); // x축만 증가시켜서 y와 평행한
// 벡터들만 그림
grf3dvp(80,80,30,0.785,0.785,100);
grf3d(cos(0.1*(_x^2+_y^2))); // 그래프
/* mafi test input file
by Huidae Cho */
"BASIC INPUT/OUTPUT
------------------
VALUE & FUNCTION\r";
// set variable
var = 10;
// show variable
var;
// set label and function
label:
func(x,y) = x+y;
// show function value
func(1,var);
//exit();
"\r\rMATRIX\r";
// set matrix
M = [func(1,0.1), func(1,1);
3, 4];
"\rM[]=\r"; M[];
// set matrix element
M[func(1,1),func(1,0)] = func(2,3);
M[1,2] = func(4,5);
// read matrix element
e = M[1,2]*4.3; e;
/* show function value with
matrix element parameters */
func(M[1,2],M[1,1]);
// show matrix
"\rM[]=\r"; M[];
// copy matrix
A = M[];
"\rA[]=\r"; A[];
A[1,1] = 0; A[];
"\rMATRIX MANIPULATION
-------------------\r";
// sign*matrix
"\r-M[]=\r"; -M[];
// scalar product
"\r-func(1,2)*M[]=\r"; -func(1,2)*M[];
"\r3*M[]*2=\r"; 3*M[]*2;
M = [1,2;3,4];
"\rM[]=\r"; M[];
"\r3*M[]*2=\r"; 3*M[]*2;
"\rM[]*[1,2;3,4]*M[]=\r";
M[]*[1,2;3,4]*M[];
"\rM[]+[1,2;5,6]=\r";
M[]+[1,2;5,6];
"\rM[]-[1,2;5,6]=\r";
M[]-[1,2;5,6];
"\rA=M[]-[1,2;5,6]*M[]\r";
A=M[]-[1,2;5,6]*M[];
A[];
"\rB=Inverse of A[]\r";
B=A[]^-1;
B[];
"\rC=-B[]/-2\r";
C=-B[]/-2;
C[];
"\rD[]=(-B[]/-2)^-1\r";
D=(-B[]/-2)^-1;
D[];
"\rC[]*D[]=\r";
C[]*D[];
a=[2,2,3,5; 3,4,5,6; 7,8,9,90];
b=[3,4,3,4; 1,1,1,1; 0,0,0,0];
"\ra[]=\r"; a[];
"\rb[]=\r"; b[];
"\rb[1,2]*a[]*3=\r";
b[1,2]*a[]*3;
"\rTranspose of a[]=\r"; a[]t;
"\r-a[]*b[]t=\r"; -a[]*b[]t;
t=2;
a=[12,23,12,3,t];
b=[2,34];
"\r------
BEFORE
------\r";
"t="; t;
"\ra[]=\r"; a[];
"\rb[]=\r"; b[];
"\r\rMFUNC...\r";
A[](a[],t,b[])=t*a[]+b[];
c=[1,2,3];
d=[4,5,6];
A[](c[],4,d[]);
"\r-----
AFTER
-----\r";
"t="; t;
"\ra[]=\r"; a[];
"\rb[]=\r"; b[];
A=M[](2,2,7); A[]f.(57-2);
A=[1, 1, 1; 1, -4, -2; 1, -2, 0]; B=[7; -15; -1]; 1/A[]*B[]; // 또는 A[]^-1*B[];
a(x)=5*x; k=[a(4),6]; k[];
a[](m[],v)=m[]f.v; a=M[](4,4,3); a=a[](a[],78); a[];
i=0;
sum=0;
start:
i++;
sum+=i;
if(i<10)
goto(start);
if(sum==55 && i==10)
"Oh! Good!\r";
else
"Hmm...\r";
a(x,y)=
{
"a(", x, ",", y, ")=\r";
sum=0;
for(i=0;i<1000;i++){
if(i==10)
continue;
sum+=i;
sum;
if(i==x*y)
break;
}
return(sum);
}
a(3,4);
위의 예제에서 a() 서브루틴은 전체가 하나의 명령입니다. 따라서 실수로
for() 문을 무한루프로 해버리면 안 되겠죠? 혹시나 모를 이런 사태에
대비해서 _max_loop 라는 변수가 있습니다. 이 값을 0으로 두면 for()에서
지정한 그대로 실행되지만 0보다 큰 값이 들어가면 for() 루프에서의
최대 반복회수가 됩니다.
그리고, 실행속도를 위해서 가능하면 for() 루프 내에서는 출력을 하지 않는게 좋겠죠. 꼭 필요하다면 실행중에 실시간으로 보지 않고 실행이 모두 마친 다음 결과에서만 그 출력들을 볼 수도 있습니다. _refresh라는 변수를 이용합니다. _refresh=0; 이라는 명령 이후로는 모든 출력명령은 실시간으로는 보여주지 않습니다. 결과에서만 보입니다. 다시 _refresh=1;을 만나면 실시간으로 보여줍니다. 기본으로는 실시간으로 보여주게 되어 있습니다. 속도가 많이 빨라질겁니다.