BigInteger 클래스 인 스 턴 스 의 구조 과정 - JDK 소스 코드 분석

최근 JDK 1.6 버 전의 BigInteger 류 를 살 펴 보고 큰 정수 인 스 턴 스 의 구조 과정 을 자세히 연구 한 결과, 이 제 는 자신의 소득 을 공유 하고 자 합 니 다.    
        우선, 왜 큰 정수 류 가 필요 합 니까?쉽게 말 하면 내부 의 데이터 형식 이 표시 할 수 있 는 최대 수 는 64 비트 길이 이기 때문에 더 큰 길이 의 데이터 가 필요 할 때 기본 적 인 데이터 형식 은 처리 할 수 없다. 암호학 과 관련 된 암호 화 알고리즘 은 수백 비트 의 정수 와 관련 되 기 때문에 효과 적 인 데이터 구 조 를 설계 하여 이러한 수 요 를 만족 시 킬 수 있어 야 한다.
        사실은 큰 정수 류 를 실현 하 는 것 도 어렵 지 않 습 니 다. 쉽게 생각해 보면 우 리 는 아주 긴 수 를 여러 개의 짧 은 수로 나 누 어 한 배열 에 저장 할 수 있 습 니 다. 대수 간 의 사 칙 연산 과 다른 연산 은 모두 배열 을 통 해 이 루어 집 니 다. JDK 는 이렇게 이 루어 집 니 다. JDK 의 BigInteger 류 에서 하나의 int 배열 로 데 이 터 를 저장 합 니 다.
/**
     * The magnitude of this BigInteger, in big-endian order: the
     * zeroth element of this array is the most-significant int of the
     * magnitude.  The magnitude must be "minimal" in that the most-significant
     * int (mag[0]) must be non-zero.  This is necessary to
     * ensure that there is exactly one representation for each BigInteger
     * value.  Note that this implies that the BigInteger zero has a
     * zero-length mag array.
     */
    int[] mag;

이 int 배열 은 '0' 요소 로 시작 하지 않 습 니 다. 또한 이 종 류 는 이 수의 양 과 음 을 나타 내 는 속성 이 있 습 니 다.
/**
     * The signum of this BigInteger: -1 for negative, 0 for zero, or
     * 1 for positive.  Note that the BigInteger zero must have
     * a signum of 0.  This is necessary to ensures that there is exactly one
     * representation for each BigInteger value.
     *
     * @serial
     */
    int signum;

1 은 이 수 를 플러스 로 나타 내 고 0 은 이 수 를 0 으로 나타 내 며 - 1 은 이 수 를 마이너스 로 나타 낸다.
본 고 에서 중점적으로 분석 한 구조 함 수 는 다음 과 같다.
/**
     * Translates the String representation of a BigInteger in the specified
     * radix into a BigInteger.  The String representation consists of an
     * optional minus sign followed by a sequence of one or more digits in the
     * specified radix.  The character-to-digit mapping is provided by
     * Character.digit.  The String may not contain any extraneous
     * characters (whitespace, for example).
     *
     * @param val String representation of BigInteger.
     * @param radix radix to be used in interpreting val.
     * @throws NumberFormatException val is not a valid representation
     *	       of a BigInteger in the specified radix, or radix is
     *	       outside the range from {@link Character#MIN_RADIX} to
     *	       {@link Character#MAX_RADIX}, inclusive.
     * @see    Character#digit
     */
    public BigInteger(String val, int radix) {

        이 구조 함 수 는 하나의 문자열 val 이 대표 하 는 큰 정 수 를 mag 배열 에 변환 하고 저장 하 는 것 입 니 다. 또한 val 이 대표 하 는 문자열 은 서로 다른 진법 (radix 결정) 일 수 있 습 니 다.
        이 구조 함수 소스 코드 를 분석 하기 전에 먼저 문 제 를 생각해 보 세 요. 하나의 큰 정 수 를 구성 하 는 데 가장 중요 한 문 제 는 하나의 큰 수 를 mag 배열 에 어떻게 저장 하 느 냐 하 는 것 입 니 다. 보통 우리 가 실현 하면 배열 의 각 블록 에 한 자릿수 (대수 가 10 진법 이 라 고 가정) 를 저장 할 수 있 습 니 다. 그러나 이런 생각 을 하면 공간 을 너무 낭비 한 다 는 것 을 알 수 있 습 니 다. 왜냐하면 하나의 int 값 은 한 개의 10 진수 만 저장 할 수 있 기 때 문 입 니 다.
        자바 언어 에서 모든 int 값 의 크기 범 위 는 - 2 ^ 31 ~ 2 ^ 31 - 1 즉 - 2147483648 ~ 2147483647 이 므 로 하나의 int 값 은 최대 10 비트 10 진법 의 정 수 를 저장 할 수 있 으 나 범 위 를 초과 하 는 것 을 방지 하기 위해 (22222222222 와 같은 int 는 저장 할 수 없습니다).보험 의 방식 은 모든 int 가 9 비트 의 10 진법 정 수 를 저장 하 는 것 이다. JDK 의 mag 배열 은 바로 이러한 보존 방식 이다. 따라서 한 줄 의 수가 189273483473895434934878 이면.
        구분 후: 18927348  |  347389543  |  834934878. mag [0] 는 18927348 을 저장 하고 mag [1] 는 347389543 을 저장 하 며 mag [2] 는 834934878 을 저장 합 니 다. 이렇게 구분 하면 모든 int 값 을 최대 로 이용 하여 mag 배열 이 더 작은 공간 을 차지 할 수 있 습 니 다. 물론 이것 은 첫 번 째 단계 입 니 다.
        구분 문 제 는 아직 끝나 지 않 았 습 니 다. 상기 구조 함 수 는 서로 다른 진법 의 수 를 지원 할 수 있 습 니 다. 최종 적 으로 mag 배열 안의 수 는 모두 10 진법 으로 바 뀌 었 습 니 다. 그러면 서로 다른 진법 의 큰 수 는 매번 에 구분 하 는 자릿수 가 다 릅 니 다. 만약 에 2 진법 을 선택 하면 매번 30 자 리 를 선택 하여 하나의 int 수 에 저장 할 수 있 습 니 다 (int 값 의 크기 범 위 는 - 2 ^ 31 에서 2 ^ 31 - 1). 만약 에 3 진법 3 ^ 19 < 21474836474847 < 3 ^ 20,따라서 매번 19 비트 를 선택 하여 하나의 int 수 에 저장 할 수 있 습 니 다. 서로 다른 진법 에서 선택 한 자릿수 가 다 르 기 때문에 하나의 배열 로 서로 다른 진법 이 선택 해 야 할 자릿수 를 저장 해 야 합 니 다. 그래서 다음 과 같 습 니 다.
 private static int digitsPerInt[] = {0, 0, 30, 19, 15, 13, 11,
        11, 10, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6,
        6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5};

이 배열 은 자바 가 지원 하 는 최대 최소 진법 에 대응 하 는 매번 구분 하 는 자릿수 를 저장 합 니 다.
이 구조 방법 에는 mag 배열 의 크기 를 초기 화 하 는 데 사용 되 는 배열 bits PerDigit 도 포함 되 어 있 습 니 다.
 // bitsPerDigit in the given radix times 1024
    // Rounded up to avoid underallocation.
    private static long bitsPerDigit[] = { 0, 0,
        1024, 1624, 2048, 2378, 2648, 2875, 3072, 3247, 3402, 3543, 3672,
        3790, 3899, 4001, 4096, 4186, 4271, 4350, 4426, 4498, 4567, 4633,
        4696, 4756, 4814, 4870, 4923, 4975, 5025, 5074, 5120, 5166, 5210,
                                           5253, 5295};

        자신 이 인터넷 에서 자 료 를 한참 동안 생각해 보고 나 서 야 알 게 되 었 는데, 지금 은 인터넷 의 한 단락 을 따 서 이 배열 의 의 미 를 설명 한다.
     "bitsPerDigit 는 radix 진수 m 의 유효한 숫자 를 계산 하 는 데 사 용 됩 니 다." 2 진법 에 필요 한 bit 비트 [가설 에 필요 한 x 비트] 로 변환 합 니 다. 계산 식 을 살 펴 보 겠 습 니 다. radix ^ m - 1 = 2 ^ x - 1, 이 방정식 을 풀 려 면 x = m * log 2 (radix) 입 니 다. 현재 m 는 몇 자리 의 유효 숫자 이 고 상수 는 log 2 (radix) 밖 에 없습니다.이것 은 소수 입 니 다. 이것 은 우리 가 좋아 하 는 것 이 아 닙 니 다. 그래서 우 리 는 하나의 정수 로 표시 하고 싶 습 니 다. 그래서 우 리 는 그 를 1024 배 확대 한 다음 에 정 리 했 습 니 다. 예 를 들 어 3 진 bits PerDigit [3] = 1624 (나 는 계산기 로 x = log 2 (3) * 1024 ~ = 1623. xxx)"우리 팀 의 이 수 를 정 리 했 는데 왜 1624 를 취 했 습 니까? 사실은 너무 많 지 않 으 면 됩 니 다. 당신 은 1620, 1600, 1610 으로 설정 할 수 있 습 니 다."
        즉, 일련의 수 (N 진법) 에 대해 서 는 바 이 너 리 의 자릿수 로 바 꾸 고 1024 를 곱 하면 bits PerDigit 배열 에 대응 하 는 데 이 터 를 곱 하고 1024 를 곱 하면 편 하 게 볼 수 있 습 니 다.
        이상 의 소개 가 있 으 면 우 리 는 지금 이 방법의 소스 코드 를 붙 여 자세히 볼 수 있다.
public BigInteger(String val, int radix) {
		
		int cursor = 0, numDigits;
        int len = val.length();//        

        //        
        if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
        	throw new NumberFormatException("Radix out of range");
        if (val.length() == 0)
        	throw new NumberFormatException("Zero length BigInteger");
        //    ,         "-"
        signum = 1;
        int index = val.lastIndexOf("-");
        if (index != -1) {
            if (index == 0) {
                if (val.length() == 1)
                    throw new NumberFormatException("Zero length BigInteger");
                signum = -1;
                cursor = 1;
            } else {
                throw new NumberFormatException("Illegal embedded minus sign");
            }
        }
        //     0
        while (cursor < len &&
                Character.digit(val.charAt(cursor),radix) == 0)
 	    cursor++;
        if (cursor == len) {//       0,    ZERO.mag
	 	    signum = 0;
	 	    mag = ZERO.mag;
	 	    return;
        } else {//numDigits        
        	numDigits = len - cursor;
        }
        //numDigits  radix      2       
        //bitsPerDigit         1024       10 (     1024),        
        //     ,   1       
        //  int 32bit,    32         mag     
        int numBits = (int)(((numDigits * bitsPerDigit[radix]) >>> 10) + 1);
        int numWords = (numBits + 31) /32;
        mag = new int[numWords];
        //    digitsPerInt        
        //   digitsPerInt[radix]       
        int firstGroupLen = numDigits % digitsPerInt[radix];
    	if (firstGroupLen == 0)
    	    firstGroupLen = digitsPerInt[radix];
    	//         mag       
    	String group = val.substring(cursor, cursor += firstGroupLen);
            mag[mag.length - 1] = Integer.parseInt(group, radix);
    	if (mag[mag.length - 1] < 0)
    	    throw new NumberFormatException("Illegal digit");
    	//        
    	int superRadix = intRadix[radix];
        int groupVal = 0;
        while (cursor < val.length()) {
		    group = val.substring(cursor, cursor += digitsPerInt[radix]);
		    groupVal = Integer.parseInt(group, radix);
		    if (groupVal < 0)
			throw new NumberFormatException("Illegal digit");
	            destructiveMulAdd(mag, superRadix, groupVal);
        }
        mag = trustedStripLeadingZeroInts(mag);
        
	}

        현재 제 가 마지막 몇 줄 에 대해 분석 하지 않 은 것 은 intRadix 배열 이 있 기 때 문 입 니 다. 우 리 는 아직 설명 하지 않 았 기 때 문 입 니 다. intRadix 배열 은 사실은 각종 radix 에 대응 하 는 가장 좋 은 진법 을 저장 한 표 입 니 다. 위 에서 우 리 는 10 진법 에 대해 9 자리 수 를 한꺼번에 자 르 는 것 을 선택 했다 고 말 했 습 니 다. 그러면 하나의 int 변 수 를 충분히 이용 할 수 있 고 int 의 범 위 를 초과 하지 않도록 보장 할 수 있 기 때문에 intRadix [10]=10^9=1000000000. intRadix[3]=3^19=1162261467. 즉, 매번 절 취 된 수 는 radix 가 대응 하 는 가장 좋 은 진법 을 초과 하지 않 는 다. 예 를 들 어 10 진법 수 189273483473895434934878 의 최종 전환 은:
        18927348 * (10 ^ 9) ^ 2 + 347389543 * (10 ^ 9) + 834934878, 최종 적 으로 mag 배열 은 10 ^ 9 진수 로 저 장 됩 니 다.
         intRadix 는 다음 과 같 습 니 다.
private static int intRadix[] = {0, 0,
        0x40000000, 0x4546b3db, 0x40000000, 0x48c27395, 0x159fd800,
        0x75db9c97, 0x40000000, 0x17179149, 0x3b9aca00, 0xcc6db61,
        0x19a10000, 0x309f1021, 0x57f6c100, 0xa2f1b6f,  0x10000000,
        0x18754571, 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d,
        0x6c20a40,  0x8d2d931,  0xb640000,  0xe8d4a51,  0x1269ae40,
        0x17179149, 0x1cb91000, 0x23744899, 0x2b73a840, 0x34e63b41,
        0x40000000, 0x4cfa3cc1, 0x5c13d840, 0x6d91b519, 0x39aa400
    };

        intRadix[10]=0x3b9aca00 = 1000000000; intRadix[3]=0x4546b3db=1162261467;       
        우 리 는 numWords = (numBits + 31) / 32. 초기 배열 의 크기 는 큰 정수 로 구 분 된 숫자 가 아니 라 큰 정수 에 대응 하 는 이 진 자릿수 (31 을 더 해 numWords 가 0 이상 확보) 를 계산 한 다음 에 32 로 나 누 어 얻 을 수 있 기 때문에 mag 배열 의 모든 int 수의 32 자 리 는 완전히 이용 된다 는 것 을 알 게 되 었 다.즉, 모든 int 수 를 부호 없 는 숫자 로 볼 수 있 습 니 다. int 의 32 비트 를 완전히 이용 하지 않 으 면 우 리 는 분 단 된 결과 에 따라 mag 배열 의 초기 크기 를 확정 할 수 있 습 니 다. 이전의 예: 18927348 | 347389543 | 834934878. 우 리 는 10 진수 가 매번 9 비트 를 선택 할 때마다 경 계 를 넘 지 않 는 다 는 것 을 알 고 있 습 니 다. 우 리 는 mag 배열 의 크기 를 직관 적 으로 3 으로 얻 을 수 있 습 니 다.그러나 이렇게 되면 모든 int 요 소 는 여전히 빈 자 리 를 이용 하지 않 습 니 다.
       따라서 우리 의 이전 구분 방법 은 전체 배열 이 초기 화 된 상상 에서 첫 번 째 단계 일 뿐 이 었 다. 이 예 는 numWords = (numBits + 31) / 32 로 계산 하면 마지막 에 얻 은 것 은 3 이 어야 한다. 그러나 좀 더 큰 꼬치 의 결과 가 반드시 같 지 않다 면 적 게 쌓 이 고 많은 꼬치 를 만 들 때 절약 하 는 공간 을 나 타 낼 수 있다.
       자바 에는 기호 int 수가 없 기 때문에 mag 배열 에 서 는 항상 기호 가 마이너스 인 요소 가 있 습 니 다. 마지막 으로 원래 의 큰 정 수 를 mag 배열 에 저 장 된 radix 에 대응 하 는 최 적 진수 로 바 꾸 는 과정 은 destructiveMul Add 로 이 루어 집 니 다. 현재 구조 함수 의 마지막 부분 과 방법 destructiveMul Add 의 해석 을 첨부 합 니 다.
int superRadix = intRadix[radix];
        int groupVal = 0;
        while (cursor < val.length()) {
        	//       
		    group = val.substring(cursor, cursor += digitsPerInt[radix]);
		    groupVal = Integer.parseInt(group, radix);//        
		    if (groupVal < 0)
			throw new NumberFormatException("Illegal digit");
		    //mag*superRadix+groupVal.   :18927348*10^9+347389543
	            destructiveMulAdd(mag, superRadix, groupVal);
        }
        //  mag     0,        0  .
        mag = trustedStripLeadingZeroInts(mag);

   private final static long LONG_MASK = 0xffffffffL;
	// Multiply x array times word y in place, and add word z
    private static void destructiveMulAdd(int[] x, int y, int z) {
        // Perform the multiplication word by word
    	// y z   long  
        long ylong = y & LONG_MASK;
        long zlong = z & LONG_MASK;
        int len = x.length;

        long product = 0;
        long carry = 0;
        //         y  ,          ,         .
        for (int i = len-1; i >= 0; i--) {
        	//      x[i]   long,   32              
            product = ylong * (x[i] & LONG_MASK) + carry;
            //x[i]     32 .
            x[i] = (int)product;
            // 32     ,        
            carry = product >>> 32;
        }

        // Perform the addition
        //   z
        //mag      long  z  
        long sum = (x[len-1] & LONG_MASK) + zlong;
        //mag           32 .
        x[len-1] = (int)sum;
        // 32      
        carry = sum >>> 32;
        //                
        for (int i = len-2; i >= 0; i--) {
            sum = (x[i] & LONG_MASK) + carry;
            x[i] = (int)sum;
            carry = sum >>> 32;
        }
    }

        전체 과정 에서 보존 하 는 방법 은 우리 의 머 릿 속 에 있 는 간단 한 저장 방법 과 다 르 기 때문에 최종 적 으로 mag 배열 의 요 소 는 원래 의 문자열 과 크게 다 를 것 이다. 그러나 실질 적 으로 똑 같은 수 를 나타 내 고 현재 189273483473895434934878 사례 의 구조 과정 을 보 여 준다.
        초기 화 후 numBits = 87 로 계 산 됩 니 다. 이렇게 배열 은 크기 numWords = 3 을 초기 화 합 니 다.        최종 순환 에 들 어가 기 전 mag 배열: [0] [0]  [18927348]        첫 번 째 순환 후: [0]  [4406866]   [-1295432089] (18927348*10^9+347389543)        두 번 째 순환 후: [1026053]  [-1675546271]   [440884830]. ((18927348*10^9+347389543)*10^9+834934878)        결국 우 리 는 189273483473895434934878 을 10 ^ 9 진법 으로 바 꾸 어 mag 배열 에 저장 했다. 최종 결 과 는 우리 에 게 익숙 하지 않 지만 그 중에서 몇 줄 로 나 누 는 방법 과 배열 이 공간 을 절약 하 는 사상 은 모두 배 울 만 한 것 이다. (잘 정리 되 어 수준 이 없 는 것 같다...)
       지금 마지막 문제 가 있 습 니 다. 어떻게 mag 배열 을 원래 의 문자열 로 바 꿉 니까?JDK 에 서 는 계속 나 누 기 를 통 해 이 루어 집 니 다. BigInteger 류 의 인 스 턴 스 는 toString 방법 을 호출 할 때 원래 의 문자열 을 되 돌려 줍 니 다. 코드 는 다음 과 같 습 니 다.
public String toString(int radix) {
	if (signum == 0)
	    return "0";
	if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
	    radix = 10;

	// Compute upper bound on number of digit groups and allocate space
        //           , mag        .
	int maxNumDigitGroups = (4*mag.length + 6)/7;
	String digitGroup[] = new String[maxNumDigitGroups];
        
	// Translate number to string, a digit group at a time
	BigInteger tmp = this.abs();
	int numGroups = 0;
	while (tmp.signum != 0) {
            BigInteger d = longRadix[radix];

            MutableBigInteger q = new MutableBigInteger(),
                              r = new MutableBigInteger(),
                              a = new MutableBigInteger(tmp.mag),
                              b = new MutableBigInteger(d.mag);
         //a  b    q ,     r 
            a.divide(b, q, r);
            BigInteger q2 = new BigInteger(q, tmp.signum * d.signum);
            BigInteger r2 = new BigInteger(r, tmp.signum * d.signum);
         //                  
            digitGroup[numGroups++] = Long.toString(r2.longValue(), radix);
         //      
	    tmp = q2;
	}
     // StringBuilder             ,              0
	// Put sign (if any) and first digit group into result buffer
	StringBuilder buf = new StringBuilder(numGroups*digitsPerLong[radix]+1);
	if (signum<0)
	    buf.append('-');
	buf.append(digitGroup[numGroups-1]);

	// Append remaining digit groups padded with leading zeros
	for (int i=numGroups-2; i>=0; i--) {
	    // Prepend (any) leading zeros for this digit group
	    int numLeadingZeros = digitsPerLong[radix]-digitGroup[i].length();
	    if (numLeadingZeros != 0)
		buf.append(zeros[numLeadingZeros]);
	    buf.append(digitGroup[i]);
	}
	return buf.toString();
    }

    /* zero[i] is a string of i consecutive zeros. */
    private static String zeros[] = new String[64];
    static {
	zeros[63] =
	    "000000000000000000000000000000000000000000000000000000000000000";
	for (int i=0; i<63; i++)
	    zeros[i] = zeros[63].substring(0, i);
    }

 
      상술 한 방법의 핵심 은 바로 a.divide(b, q, r). longRadix 배열 과 intRadix 배열 은 비슷 한 의 미 를 가진다.
       intRadix [10] = 10 ^ 9. 따라서 longRadix [10] = 10 ^ 18 은 intRadix 에 대한 제곱, 즉 long 유형 에 있어 서 가장 좋 은 진수 에 해당 합 니 다.
       간단하게 생각해 보면 알 수 있 듯 이 mag 배열 을 10 ^ 9 로 계속 나 누 면 834934878, 347389543, 18927348 을 얻 을 수 있 습 니 다. 10 ^ 18 (자바 가 이 수량 급 의 연산 을 지원 합 니 다) 을 나 누 면 두 번 에 각각 3473895431892348, 834934878 을 얻 을 수 있 기 때문에 longRadix 배열 로 연산 하 는 효율 이 더욱 높 습 니 다. 상기 방법 에 나타 난 Mutable BigInteger 는인터넷 의 한 단락 을 빌려 설명 하 는 것 이 내 가 말 한 것 보다 더 좋 을 것 이다. 
       "Mutable BigInteger 는 BigInteger 류 의 또 다른 버 전 입 니 다. 임시 대상 을 만 들 지 않 는 전제 에서 호출 프로그램 이 BigInteger 유형의 반환 값 을 얻 도록 하 는 것 이 특징 입 니 다. (가 변 대상 기술 이 라 고 함)... 큰 정수 나 누 기 는 대량의 다른 산술 조작 으로 이 루어 져 있 기 때문에 대량의 임시 대상 이 필요 하 며, 새로운 대상 을 만 들 지 않 고 많은 조작 을 완성 하면 프로그램의 성능 을 크게 개선 할 수 있 으 며, (대상 을 만 드 는 대가 가 매우 높 기 때문에) 자바 의 큰 정수 류 에 서 는 Mutable BigInteger 류 의 방법 으로 큰 정수 나 누 기 를 수행 할 수 있 습 니 다. " 
       가장 중요 한 divide 방법 은 죄송합니다. 제 가 오랫동안 봤 는데 도 코드 의 생각 을 이해 하지 못 했 습 니 다. 여러분 들 이 잘못된 방향 을 가르쳐 주 셨 으 면 좋 겠 습 니 다!
       JDK 의 BigInteger 류 에서 우리 가 볼 만 한 많은 방법 을 실 현 했 습 니 다. 기본 적 인 네 가지 요 소 를 제외 하고 그 안에 소 수 를 판단 하 는 방법 도 제 공 했 습 니 다. 멱, 모델 링, 역 원, 최대 공약 수 를 구 했 습 니 다. Miller - Rabin 알고리즘 을 사 용 했 습 니 다. 미끄럼 창 알고리즘 은 빠 른 속도 로 멱 을 구 했 습 니 다.3000 여 줄 의 코드... 관심 이 있다 면 그 중 어떤 방법 이 우리 에 게 깨 우 침 이 있 을 수 있 습 니 다.
       여러분 들 이 동의 하지 않 는 부분 이 있 으 면 지적 해 주시 기 바 랍 니 다. 많이 교류 하 세 요. 그리고 저 에 게 남 겨 진 divide 방법 을 설명해 주 시 는 것 이 좋 습 니 다. 저 는 오랫동안 보 았 더 니 징 그 러 워 요. 큰 신 이 어디 에 있 는 지!!!
       본문 에서 참고 한 문장 링크:http://www.iteye.com/topic/1118707 .

좋은 웹페이지 즐겨찾기