1부터 100까지의 합(또는 특정구간)을 구하기 위한 최적화

개요

1부터 100까지의 합을 구하기 위한 어셈블리 구현 예제 입니다.
여기서는 2가지를 선보입니다.
한가지는 그냥 상식선에서 1부터 차례차례 더하는 것이고
또 다른 한가지는 수열을 정리해서 반복문을 제거한 방법을 구현한 것입니다.

순차적으로 합을 구하는 방법

DEF_STACKSIZ기E = 1024

           ASSUME CS:CODE_SUM, DS:NOTHING, ES:NOTHING, SS:NOTHING
CODE_SUM   SEGMENT PARA PUBLIC USE16 'CODE'
           ORG 100H
L_START:
           ; Setup stack
           CLI
           MOV AX, CS
           MOV SS, AX
           MOV SP, OFFSET L_STACK_ENTRY
           STI

           XOR AX, AX
           XOR BX, BX
L_SUM_LOOP:
           INC BX
           ADD AX, BX
           CMP BX, 100
           JNE L_SUM_LOOP

           MOV BX, 10
           XOR CX, CX
L_PRINT_DIGIT_0:
           XOR DX, DX
           DIV BX
           ADD DX, '0'
           PUSH DX
           INC CX
           OR AX, AX
           JNZ L_PRINT_DIGIT_0
L_PRINT_DIGIT_1:
           POP DX
           MOV AH, 02H
           INT 21H
           LOOP L_PRINT_DIGIT_1

           MOV AX, 04C00H
           INT 21H


           DB DEF_STACKSIZE DUP (?)
L_STACK_ENTRY:
CODE_SUM   ENDS

           END L_START

# End of source


수열정리를 이용한 방법

일단 누적합의 경우는 루프가 많다는 점이 가장 최적화 해야 할 부분이라고 생각했습니다. 그래서 먼저 수열을 정리하면서 풀어해쳤더니 복잡한 중간과정은 생략하고
항상 y = (end + start) * ((end - start + 1) / 2) 이라는 공식이 먼저 산출되었습니다.
하지만 이것은 실수계산인 경우이겠죠.

우리의 컴퓨터는 야속하게도 정수계산을 더욱 좋아하기 때문에 이를 정수계산만으로 해결하는 방법을 위해 다시 계산 공식을 수정해야 합니다.
그래서 한번 얼핏 상상해봤습니다. 그랬더니 start 부터 end 까지의 숫자갯수가 짝수인 경우는 그냥 위의 공식이 그대로 될법 했습니다.
하지만 홀수는 0.5씩 값이 정수계산시에 loss 납니다.
결국 어째저째 하여 정수계산시 발생하는 loss 는 start + ((end - start + 1) / 2) 라는 결론이 났습니다.

일단 공식을 좀더 정리해볼수 있을법 하지만 일단 이 이상은 머리아파서 그만 정리하겠습니다.

이제 코드로 만들어 내면 되는데 다음과 같이 우선 C로 해봤습니다.
int s_result, s_quota, s_range;

s_range = s_end - s_start + 1;
s_quota = s_range >> 1;
s_result = (s_end + s_start) * s_quota;
if(s_range & 1)s_result += s_quota + s_start;
잘됩니다. 드디어 루프없이 특정범위의 합을 계산할수 있는것이 가능하다는 증명이 되었습니다. ㅎㅎㅎ

이제 아래처럼 어셈블리로 코딩하였습니다. 역시 빠릅니다. 루프는 이제 누적합에서 사용하지 않아도 된다는 결론입니다.
.MODEL SMALL
.RADIX 0AH

; 테스트는 여러가지로 아래의 값을 변경하면서 해보세요.
DEF_BEGIN_VALUE = 1                 ; 시작값
DEF_END_VALUE   = 100               ; 끝값

        ASSUME CS:SEG_CODE,DS:SEG_CODE,ES:NOTHING,SS:SEG_STACK
SEG_CODE SEGMENT PARA PUBLIC USE16 'CLASS_CODE'
L_ENTRY:
        CLI
        MOV AX, SEG_STACK
        MOV SS, AX
        MOV SP, OFFSET SEG_STACK:L_STACK_EOP
        STI
        MOV AX, SEG_CODE
        MOV DS, AX
        MOV ES, AX

        ; "DEF_BEGIN_VALUE"부터 "DEF_END_VALUE"까지 더하는 테스트
        XOR CX, CX
        MOV AX, DEF_END_VALUE
        MOV BX, DEF_BEGIN_VALUE
        ADD BX, AX
        SUB AX, DEF_BEGIN_VALUE
        INC AX
        SHR AX, 1
        JNC L_EVEN ; 짝수일때 L_EVEN으로 분기
        ADD CX, AX
        ADD CX, DEF_BEGIN_VALUE
L_EVEN:
        MUL BX
        ADD AX, CX
L_END_TEST:

        ; 결과값 AX의 확인을 위해 화면에 출력
        MOV BX, 10
        XOR CX, CX
L_PRINT_0:
        XOR DX, DX
        DIV BX
        ADD DX, '0'
        PUSH DX
        INC CX
        OR AX, AX
        JNZ L_PRINT_0
L_PRINT_1:
        POP DX
        MOV AH, 02H
        INT 21H
        LOOP L_PRINT_1

        ; 프로그램 종료
        MOV AX, 4C00H
        INT 21H
        JMP $
SEG_CODE ENDS

        ASSUME CS:SEG_CODE,DS:SEG_CODE,ES:NOTHING,SS:SEG_STACK
SEG_STACK SEGMENT PARA STACK USE16 'CLASS_STACK'
        DB 1024 DUP (?) ; 스택 공간 확보
        L_STACK_EOP:
SEG_STACK ENDS
        END L_ENTRY ; 시작점

; End of source


C로 정리
#include <stdio.h>

int mysum(int s_start, int s_end)
{
    int s_result, s_quota, s_range;

    s_range = s_end - s_start + 1;
    s_quota = s_range >> 1;
    s_result = (s_end + s_start) * s_quota;
    if(s_range & 1)s_result += s_quota + s_start;

    return(s_result);
}

int main(void)
{
    (void)fprintf(stdout, "1 부터 1000 까지 합 구하기 : %d\n", mysum(1, 1000));

    return(0);
}



/*
[ FrontPage | PrintView | RawView | RSS ]

Copyright ⓒ MINZKN.COM
All Rights Reserved.

MINZKN
*/