'프로그래밍'에 해당되는 글 5건

  1. 2009/01/04 [C#] Fraction
  2. 2008/12/04 중복게시물 검출 방법에 대한 이론
  3. 2008/04/13 shift 곱과 * 곱의 성능차를 측정한 결과 (1)
  4. 2008/01/20 void main()을 써서는 안 되는 이유
  5. 2007/09/09 JImage Component (1)

floating-point arithmetic의 오차 때문에, 누적시킬수록 오차가 커지는 문제를 해결하기 위하여 작성해 보았습니다. Value-type이므로 편하게 쓰시면 됩니다.

    struct Fraction
    {       
        private int numerator;
        private int denominator;

        public int Numerator { get { return numerator; } }
        public int Denominator { get { return denominator; } }

        public Fraction(int num) : this()
        {
            this.SetValue(num);
        }

        public Fraction(int numerator, int denominator) : this()
        {
            this.SetValue(numerator, denominator);           
        }

        // note that the change is irreversible.
        public static implicit operator float(Fraction f)
        {
            return (float)f.numerator / f.denominator;
        }

        public static implicit operator double(Fraction f)
        {
            return (double)f.numerator / f.denominator;
        }

        public static implicit operator Fraction(int i)
        {
            return new Fraction(i);
        }

        public static Fraction operator +(Fraction a, Fraction b)
        {
            return new Fraction(
                a.numerator * b.denominator + b.numerator * a.denominator,
                a.denominator * b.denominator);
        }

        public static Fraction operator -(Fraction a, Fraction b)
        {
            return new Fraction(
                a.numerator * b.denominator - b.numerator * a.denominator,
                a.denominator * b.denominator);
        }

        public static Fraction operator *(Fraction a, Fraction b)
        {
            return new Fraction(
                a.numerator * b.numerator,
                a.denominator * b.denominator);
        }

        public static Fraction operator /(Fraction a, Fraction b)
        {
            return new Fraction(
                a.numerator * b.denominator,
                a.denominator * b.numerator);
        }

        public void SetValue(int num)
        {
            this.numerator = num;
            this.denominator = 1;
        }

        public void SetValue(int numerator, int denominator)
        {
            this.numerator = numerator;
            this.denominator = denominator;

            Reduce();
        }

        private void Reduce()
        {
            // get GCD with Euclidean algorithm
            int a = Math.Abs(this.numerator);
            int b = Math.Abs(this.denominator);
            int temp;

            while (b != 0)
            {
                temp = a % b;
                a = b;                 
                b = temp;
            }

            this.numerator /= a;
            this.denominator /= a;
        }

        public Fraction Reciprocal()
        {
            return new Fraction(denominator, numerator);
        }

        public override string ToString()
        {
            double value = this;
            if (value == (int)value)
                return ""+value;

            return numerator+"/"+denominator;
        }

크리에이티브 커먼즈 라이센스
Creative Commons License
2009/01/04 23:27 2009/01/04 23:27

댓글을 달아 주세요

문득 떠올라서 적어봅니다.

필요조건: 관계형 데이터베이스

1. session id, unique id, expired 여부를 저장할 테이블을 만듭니다.
2. 글쓰기 요청시 각 session id에 대응하는 레코드를 만들고 unique id를 생성, 저장 후 글쓰기 페이지에 hidden attribute로 보냅니다.
3. 글이 POST될 때 해당 세션과 unique id로 AND 검색을 통해 레코드를 확인합니다. 일치하는 레코드가 존재하지 않거나 expired가 참이면 에러를 띄우고 돌려보냅니다.
4. 글을 등록하고 expired 필드의 값을 참으로 설정합니다.
 
submit 버튼을 여러번 클릭할 경우, POST로 날아오는 unique id가 동일하기 때문에 expired 된 글쓰기 세션은 사용할 수가 없게됩니다.

부가적으로는 글쓰기 페이지를 받아서 파싱하는 것은 어쩔 수 없겠지만 단순 자동 글쓰기 툴 정도는 차단할 수 있습니다.

발생할 수 있는 문제점: 레코드가 쌓여 table의 크기가 너무 커질 수 있음
해결 방안:
발급 일시에 대한 컬럼을 추가하고
1. maintenance script를 통한 주기적 정리
2. 익명 글쓰기를 허용하지 않는 경우 사용자 id 컬럼을 추가하여 처리

크리에이티브 커먼즈 라이센스
Creative Commons License
2008/12/04 21:24 2008/12/04 21:24

댓글을 달아 주세요

 

사용자 삽입 이미지


보시다시피 결과를 보면 아무 의미 없습니다.
오차의 크기를 봐도 도저히 차이가 있다고 생각할 수가 없습니다.
1천만번의 평균을 냈는데도 실행시마다 어느쪽이 빠른지 결과가 다릅니다.
사실 요즘은 컴퓨터가 하도 빨라져서 곱셈과 비트연산의 속도차는 티도 안 납니다.
함수 호출하고 포문 돌리면서 발생하는 오버헤드가 더 크다고 생각되네요.

크리에이티브 커먼즈 라이센스
Creative Commons License
2008/04/13 23:56 2008/04/13 23:56

댓글을 달아 주세요

  1. 홍민희 2008/12/02 16:29  댓글주소  수정/삭제  댓글쓰기

    사실 저런 수준의 코드 최적화를 손으로 하는 것이야 말로 정말 미련한 짓인데… 실제 컴파일된 결과를 보면 알겠지만, 요즘 컴파일러들은 정수에 대한 곱셈 연산을 내부적으로 shift 곱으로 바꿔버리기 때문. 즉 양쪽 모두 동일한 바이너리를 가지고 있을 거야.

많은 프로그래밍 강사들과 서적이 void main()으로 가르치고 있는 실정이다.
하지만 표준 C에서 main()의 원형은 int main(int argc, char **argv) 이다.
혹은 int main()으로 써도 무방한데, 호출 컨벤션이 cdecl이므로 호출자 측에서 스택을 클리어하기 때문이다.
하지만 void main()은 이야기가 다르다. 다음의 코드를 보자.

#include <windows.h>
#include <cstdio>
#include <cstdlib>

void voidTest()
{
    GetModuleHandle(NULL);
}

typedef unsigned int (*UIntFunc)();

int main()
{
    UIntFunc func = (UIntFunc)&voidTest;
    printf("voidTest returned: %d\n", func());
    printf("GetModuleHandle(NULL) returned: %d\n", GetModuleHandle(NULL));
    return EXIT_SUCCESS;
}

값을 리턴하지 않는 voidTest 함수를 선언하였다. 이 함수는 단지 내부적으로 GetModuleHandle Win32 API를 호출하고 끝나버린다. 그 밑에는 unsigned int를 리턴하는 함수 포인터 타입 UIntFunc를 선언하였다.
다음은 메인을 보자. UIntFunc 타입의 함수 포인터 변수를 선언하고, voidTest를 강제 캐스팅하여 집어넣었다. 그리고는 함수 포인터를 통해 voidTest를 호출한다. 밑에는 값의 비교를 위해 역시 GetModuleHandle을 호출하였다.
두 콜의 리턴값은 모두 printf로 출력되게 되어 있다. 그럼 출력 결과가 무엇이라고 생각하는가?

일단 밑의 GetModuleHandle(NULL)은 실행되고 있는 프로세스 모듈의 핸들을 리턴하므로 프로세스의 핸들이 출력될 것이다.
그럼 위의 voidTest는? voidTest는 값을 리턴하지 않는다. 단순히 void형 함수를 unsigned int를 리턴하듯이 캐스트했을 뿐이다. return 하는 값이 없으니 아마도 쓰레기값이 출력되지 않을까.

직접 실행해 본 혹자는 저 두 값이 동일하다는 데 경악을 금치 못했을 것이다. 어째서 두 값이 동일한가? return도 하지 않았는데 함수 내부에서 호출한 GetModuleHandle 값이 나올리는 없지 않은가?

상식적으로만 생각하면 당연하다. 하지만 한가지 우리가 여기서 간과하고 있는 사실이 있다. 컴퓨터가 읽어들이는 것은 기계어라는 사실. 함수 리턴부 근처의 기계어를 어셈블리로 변환하여 구조를 대충 보겠다.

push ebp
mov ebp, esp
; 레지스터 저장
push 0
call dword ptr GetModuleHandleA@4
; 레지스터 복원
pop ebp
ret
대충 이런 구조로 되어있을 것이다.
voidTest 함수의 구조이다.
이것만 봐서는 잘 모르니 한가지 더 보도록 하자.
DWORD dw = GetModuleHandle(NULL);로 변경했을 때의 코드이다.
push ebp
mov ebp, esp
; 레지스터 저장
sub esp, 4 ; 변수 공간 확보
push 0
call dword ptr GetModuleHandleA@4
mov [ebp-4], eax
; 레지스터 복원
mov esp, ebp ; 변수 공간 제거
pop ebp
ret
중간을 잘보면 mov [ebp-4], eax라는 구문이 보인다. 이 부분이 바로 GetModuleHandle(NULL)의 리턴값을 변수 공간에 저장하는 부분이다. [ebp-4]는 첫 번째 지역변수 주소이다. 참고로 mov 인스트럭션은 mov A, B 와 같은 형식으로 사용하며, 사용 효과는 B의 값을 A에 쓴다. 즉 eax 레지스터의 값을 첫 번째 지역변수에 복사한 것이다.

이것이 왜 중요하냐 하면, C/C++의 4대 호출 컨벤션인 cdecl, stdcall, fastcall, thiscall은 모두 리턴값을 저장하는 데 eax 레지스터를 사용하기 때문이다. 즉, 함수는 eax 레지스터에 리턴값을 기록할 의무가 있고 호출자는 eax 레지스터에 리턴값을 기대할 수 있다. 헌데, 위와 같은 상황이 되면 이야기가 조금 다르다. 실제로 언어 로직상 voidTest가 리턴한 값은 없지만, 마지막으로 기록된 eax 레지스터의 값이 GetModuleHandle(NULL)이 리턴한 값이기 때문에, voidTest()의 리턴값을 받아오게 되면 자연히 GetModuleHandle(NULL)의 리턴값이 출력되는 것이다.

main의 원형은 int main(int argc, char **argv) 이고 이 함수의 호출자는 어디까지나 이 원형 그대로를 기대한다. cdecl이므로 인자값은 받든 안받든 상관없지만, void main()으로 하는 경우 호출자가 int 리턴값을 사용하려고 하는 경우에 예상치 못한 값이 들어갈 수 있다. Windows에서는 대표적으로 배치 프로그램으로 실행하려고 할 때 문제가 되며, 리턴값이 256보다 크면 시스템에서 오류를 띄우는 운영체제도 있다. 따라서 항상 int main()으로 쓰는 것이 좋은 버릇일 것이다.

참고: 32비트 환경에서 32비트 레지스터의 허용치보다 큰 값을 리턴시에는, 예외적으로 파라미터에 주소값이 넘어가게 된다.
크리에이티브 커먼즈 라이센스
Creative Commons License
2008/01/20 14:14 2008/01/20 14:14

댓글을 달아 주세요

package ui;

import javax.swing.JComponent;
import javax.swing.border.Border;
import javax.swing.SwingConstants;
import java.awt.image.BufferedImage;
import java.awt.Image;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Dimension;
import java.awt.MediaTracker;
import java.awt.image.WritableRaster;

public class JImage extends JComponent implements SwingConstants {
 private BufferedImage img;
 private int mode;
 private int halign;
 private int valign;
 private Dimension preferredSize;
 
 public static final int SCALE_NONE = 0;
 public static final int SCALE_HORIZONTAL = 1;
 public static final int SCALE_VERTICAL = 2;
 public static final int SCALE_BOTH = 3;
 public static final int SCALE_PROPORTIONAL = 4;
 
 public JImage()
 {
  this.img = null;
  mode = SCALE_NONE;
  this.halign = CENTER;
  this.valign = CENTER;
  this.preferredSize = null;
 }
 
 public JImage(Image img)
 {
  this();
  this.setImage(img);
 }
 
 protected void paintComponent(Graphics g)
 {
  super.paintComponent(g);
 
  Insets i = this.getInsets();
  int cWidth = this.getWidth() - (i.left + i.right);
  int cHeight = this.getHeight() - (i.top + i.bottom);

  g.setColor(this.getBackground());
  g.fillRect(i.left, i.top, cWidth, cHeight);
 
  // Do nothing if the image is null
  if(img == null)
   return;

  int width = 0;
  int height = 0;

  int imgWidth = img.getWidth(null);
  int imgHeight = img.getHeight(null);
 
  if(imgWidth > 0 && imgHeight > 0)
  {
   switch(this.mode)
   {
   case SCALE_NONE:
    width = imgWidth;
    height = imgHeight;
    break;
   case SCALE_HORIZONTAL:
    width = cWidth;
    height = imgHeight;
    break;
   case SCALE_VERTICAL:
    width = imgWidth;
    height = cHeight;
    break;
   case SCALE_BOTH:
    width = cWidth;
    height = cHeight;
    break;
   case SCALE_PROPORTIONAL:
    float ratio = (float)imgWidth / imgHeight;
    float cRatio = (float)cWidth / cHeight;
   
    if(ratio > cRatio)
    {
     width = cWidth;
     height = (int)(cWidth / ratio);
    }
    else if(ratio < cRatio)
    {
     width = (int)(cHeight * ratio);
     height = cHeight;
    }
    else
    {
     width = cWidth;
     height = cHeight;
    }
    break;
   default:
    // Unknown mode; do nothing
   }
   
   boolean l2r = this.getComponentOrientation().isHorizontal();
   int x = 0;
   int y = 0;
   
   // Do alignment if necessary
   switch(this.halign)
   {
   case LEFT:
    x = 0;
    break;
   case CENTER:
    x = (cWidth - width) / 2;
    break;
   case RIGHT:
    x = cWidth - width;
    break;
   case LEADING:
    if(!l2r)
     x = cWidth - width;
    break;
   case TRAILING:
    if(l2r)
     x = cWidth - width;
    break;
   default:
   }
   
   switch(this.valign)
   {
   case TOP:
    y = 0;
    break;
   case CENTER:
    y = (cHeight - height) / 2;
    break;
   case BOTTOM:
    y = cHeight - height;
    break;
   default:
   }
   
   Image img = this.img;
   
   if(width != imgWidth || height != imgHeight)
   {
    img = this.img.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
   
    MediaTracker tracker = new MediaTracker(this);
    tracker.addImage(img, 0);
    try {
              tracker.waitForID(0);
          } catch (InterruptedException e) {
              return;
          }
          tracker.removeImage(img);
   }
   
   // Draw actual image
   g.drawImage(img, x + i.left, y + i.top, null);
  }
 }
 
 public void setScalingMode(int mode)
 {
  if(mode != SCALE_NONE &&
    mode != SCALE_HORIZONTAL &&
    mode != SCALE_VERTICAL &&
    mode != SCALE_BOTH &&
    mode != SCALE_PROPORTIONAL)
   throw new IllegalArgumentException("mode");
 
  if(this.mode != mode)
  {
   this.mode = mode;
   if(this.isDisplayable())
    this.repaint();
  }
 }
 
 public int getScalingMode()
 {
  return this.mode;
 }
 
 public void setImage(Image img)
 {
  boolean repaint = this.isDisplayable();
 
  if(img == null)
  {
   if(this.img == null)
    repaint = false;
   else
    this.img.flush();
   
   this.img = null;
  }
  else
  {
   if(img.equals(this.img))
    repaint = false;
   
   MediaTracker tracker = new MediaTracker(this);
   tracker.addImage(img, 0);
   try {
             tracker.waitForID(0);
         } catch (InterruptedException e) {
             return;
         }
         tracker.removeImage(img);
        
   int width = img.getWidth(null);
   int height = img.getHeight(null);
        
   if(img instanceof BufferedImage)
   {
    BufferedImage source = (BufferedImage)img;
    WritableRaster raster = source.copyData(null);
    this.img = new BufferedImage(
      source.getColorModel(),
      raster,
      source.isAlphaPremultiplied(),
      null);
   }
   else
   {
    this.img = new BufferedImage(width, height,
      BufferedImage.TYPE_INT_ARGB);
       Graphics bg = this.img.getGraphics();
       bg.drawImage(img, 0, 0, null);
       bg.dispose();
   }
        
   if(width > 0 && height > 0)
   {
    Insets i = this.getInsets();
    super.setPreferredSize(
      new Dimension(width + i.left + i.right,
        height + i.top + i.bottom));
   }
  }
 
  if(repaint)
   this.repaint();
 }
 
 public void setBorder(Border border)
 {
  super.setBorder(border);
 
  if(this.img != null)
  {
   int width = img.getWidth(null);
   int height = img.getHeight(null);
   if(width > 0 && height > 0)
   {
    Insets i = this.getInsets();
    super.setPreferredSize(new Dimension(width + i.left + i.right, height + i.top + i.bottom));
   }
  }
 }
 
 public void setHorizontalAlignment(int alignment)
 {
  this.halign = alignment;
 
  if(this.img != null && this.isDisplayable())
   this.repaint();
 }
 
 public int getHorizontalAlignment()
 {
  return this.halign;
 }
 
 public void setVerticalAlignment(int alignment)
 {
  this.valign = alignment;
 
  if(this.img != null && this.isDisplayable())
   this.repaint();
 }
 
 public int getVerticalAlignment()
 {
  return this.valign;
 }
 
 public void setPreferredSize(Dimension preferredSize)
 {
//  Insets i = this.getInsets();
 
  this.preferredSize = preferredSize;
//  this.preferredSize.width -= i.left + i.right;
//  this.preferredSize.height -= i.top + i.bottom;
 }
 
 public Dimension getPreferredSize()
 {
  if(this.preferredSize == null)
   return super.getPreferredSize();

//  Insets i = this.getInsets();
//  
//  Dimension prefSize = new Dimension();
//  prefSize.width = this.preferredSize.width + i.left + i.right;
//  prefSize.height = this.preferredSize.height + i.top + i.bottom;
//    
//  return prefSize;
  return this.preferredSize;
 }
}

크리에이티브 커먼즈 라이센스
Creative Commons License
2007/09/09 08:45 2007/09/09 08:45

댓글을 달아 주세요

  1. ㅇㅁㅇ 2007/11/14 00:41  댓글주소  수정/삭제  댓글쓰기

    내가 누군지 맞춰보라능 우어어어어어어
    힌트는 이미 주어졌다!!