01/10/2018, 11:14

Lỗi khi cài đặt thuật toán Rabin - Miller

Vì sao 93719377 = 4139 * 22643 lại ra true nhỉ mọi người, trên wiki ghi 2, 3, 5, 7, 11 là dư rồi. (gcc >= 5)

long long modPow(int base, int expo, long long modulo) {
	unsigned __int128 res=1;
	unsigned __int128 tmp=base;
	while(expo > 0) {
		if(expo%2) res = (res*tmp) % (unsigned __int128) modulo;
		tmp = (tmp*tmp) % (unsigned __int128) modulo;
		expo/=2;
	}
	return (long long) tmp;
}

int isPrime(long long n)
{
	if((n==23) || (n==3137) || (n==8389) || (n==157163)) return true;
	/* for(int i=0; i<PREFILTER_LIMIT; ++i) {
		if(n % primeList[i] == 0) return false;
	} */
	
	int base[] = {2, 3, 5, 7, 11};
	int tn = n-1; //chết vì câu này (dành cho bạn nào đến sau)
	int exp_2 = 0;
	while(tn % 2 == 0) { tn/=2; ++exp_2; }
	printf("%lld ", tn);
	
	for(int i=0; i<5; ++i) {
		unsigned __int128 tmp = modPow(base[i],tn,n);
		if(tmp == n-1 || tmp == 1) continue;
                 int j;
		for(int j=exp_2; j>=0; --j) { //vòng này chạy exp_2 + 1 lần => FAIL!
            //viết ngược lại từ j = 0 đến < exp_2 - 1 thôi. int j ở đây là sai.
			tmp = (tmp*tmp) % (unsigned __int128) n;
			if(tmp == 1) return false;
			else if(tmp == n-1) break;
		}
                if(j > 0 && tmp != 1) return false; //sai ở đây nữa, != n-1 mới đúng!
	}
	return true;
}
HK boy viết 13:18 ngày 01/10/2018
else if(tmp == n-1) break;
    		}
    	}
    	return true;
    }

Dòng return true ở cuối phải là return false chứ nhỉ? Em chạy return false thì ra. Kể cả mấy trường hợp bác đặt ở trong if kia cũng đúng luôn.

Code của em chả khác mấy so với code của bác:

#define ll long long

const int base[] = {2, 3, 5, 7, 11, 13}; // bỏ 13 đi cũng ok

ll powM(ll bs, ll pwr, ll mod) {
	ll func = 1LL;
	int bfst=64 - __builtin_clzll(pwr);
	
	for (int i=bfst-1; i>=0; i--) {
		ll pp = func;
		func = (func * func) % mod;
		if (pwr & (1LL<<i)) func = (func * bs) % mod;
	}
	return func;
}

bool isPrime(ll n) {
	ll _n = n-1;
	int exp_2 = 0;
	while (_n % 2 == 0) _n /= 2, exp_2++;

	for (int i=0; i<sizeof(base)/sizeof(base[0]); i++) {
		if (powM(base[i], _n, n) == 1) return true;

		for (int r=0; r<exp_2; r++)
			if (powM(base[i], (1LL << r) * _n, n) == n - 1) return true;
	}
	return false;
}
Dark.Hades viết 13:26 ngày 01/10/2018

// Vote typedef thay cho define

rogp10 viết 13:29 ngày 01/10/2018

Mình đã sửa lại hàm mũ mod theo bạn và giờ đã tính đúng nhưng còn hàm chính thì dùng phép bình phương vẫn sai, ra toàn false với n đủ lớn.

HK boy viết 13:31 ngày 01/10/2018

nhưng còn hàm chính thì dùng phép bình phương vẫn sai, ra toàn false với n đủ lớn.

Em vừa thử với 191507399987 và ra false, mặc dù nó đúng là số nguyên tố. Em phát hiện ra lúc nhân đã bị tràn số.

Thêm đoạn nhân mod ngoài là ngon ngay:

const ll m = 100000000LL;

ll mulM(ll a, ll b, ll mod) {
	if (a <= m && b <= m)
		return (a * b) % mod;
	if (a == 0 || b == 0)
		return 0LL;

	ll a1 = a / m, a2 = a % m;
	ll b1 = b / m, b2 = b % m;
	// a*b = (a1*m + a2)*(b1*m + b2) = a1*b1*m*m + m*(a1*b2 + a2*b1) + a2*b2

	return (mulM((a1*b1)%mod, (m*m)%mod, mod)
	+ mulM(m, mulM(a1, b2, mod) + mulM(a2, b1, mod), mod)
	+ (a2*b2)%mod) % mod;
}

Sau đó các đoạn nhân ở hàm pow đều sửa lại

func = mulM(func, func, mod);
if (pwr & (1LL<<i)) func = mulM(func, bs, mod);

// Vote typedef thay cho define

Tại quen tay code define

viết 13:16 ngày 01/10/2018

đầu tiên chọn 1 cái thư viện số lớn đã rồi áp dụng, mấy kiểu int 32 64 bit này nhằm nhò gì @_@

viết 13:28 ngày 01/10/2018

à thấy lỗi sai rồi, ở 2 dòng này:
else if(tmp == n-1) break;
if(j > 0 && tmp != 1) return false;
trong wiki ghi là
continue WitnessLoop
return composite
chỗ continue WitnessLoop ko đơn giản là break, vì break xong gặp ngay câu if(j > 0 && tmp != 1) return false; ở dưới là return false luôn rồi, ko continue witness loop được nữa.

phải viết là
if (tmp != n-1) return false;
vì điều kiện dể continue witness loop là tmp == n-1.
xóa cái int j kia đi ko cần nó nữa. Hơn nữa j lặp r - 1 lần, ghi vòng lặp thế kia là r + 1 lần rồi @_@

hơn nữa a = 2,3,5,7,11 là chưa chắc đâu, vì cái “bảo đảm” này là áp dụng cho cái thuật toán xác định deterministic ở dưới, ko phải cho bản rabin miller này. a ở đây phải được chọn ngẫu nhiên trong đoạn [2, n-2]

rogp10 viết 13:21 ngày 01/10/2018

Mình sẽ dùng flag ntn:

   flag = false;
   {
    //...
       if(tmp == n-1) { flag=true; break; }
       else if(tmp == 1) {flag=false; break; }
    //...
    }
    if(!flag) return false;

còn base cố định là do mình biết max rồi

viết 13:26 ngày 01/10/2018

Chỗ tmp==1 return false luôn rồi cần gì flag nữa @@ hơn nữa flag tối nghĩa đặt cái tên nào khác như continuewitnessloop cho dễ hiểu

github.com

weidai11/cryptopp/blob/master/nbtheory.cpp#L151

  1. return n==2 || n==3;
  2. CRYPTOPP_ASSERT(n>3);
  3. Integer b;
  4. for (unsigned int i=0; i<rounds; i++)
  5. {
  6. b.Randomize(rng, 2, n-2);
  7. if (!IsStrongProbablePrime(n, b))
  8. return false;
  9. }
  10. return true;
  11. }
  12. bool IsLucasProbablePrime(const Integer &n)
  13. {
  14. if (n <= 1)
  15. return false;
  16. if (n.IsEven())
  17. return n==2;
github.com

openssl/openssl/blob/master/crypto/bn/bn_prime.c#L225

  1. if (!BN_MONT_CTX_set(mont, a, ctx))
  2. goto err;
  3. for (i = 0; i < checks; i++) {
  4. /* 1 < check < a-1 */
  5. if (!BN_priv_rand_range(check, A3) || !BN_add_word(check, 2))
  6. goto err;
  7. j = witness(check, a, A1, A1_odd, k, ctx, mont);
  8. if (j == -1)
  9. goto err;
  10. if (j) {
  11. ret = 0;
  12. goto err;
  13. }
  14. if (!BN_GENCB_call(cb, 1, i))
  15. goto err;
  16. }
  17. ret = 1;
  18. err:
  19. if (ctx != NULL) {

người ta tách cái witness test ra thành hàm riêng nè, khỏi cần flag

rogp10 viết 13:30 ngày 01/10/2018

Cuối cùng cũng đã tìm ra lỗi:
int tn = n-1;
vì n cỡ 64 bit thì không lí nào n-1 chỉ có 32 bit.

Cảm ơn mọi người

p/s: RM nhanh hơn chia trâu 4 lần.

viết 13:25 ngày 01/10/2018

4 lần? Chậm vậy, int 32 bit à? Quất lên 2048 bit luôn cho nó chạy nhanh hơn tỉ tỉ tỉ tỉ… lần

rogp10 viết 13:26 ngày 01/10/2018

À 9 lần: 0.078 -> 0.009s so với chỉ chia cho số nguyên tố :v (thực ra dãy số nguyên tố này là kết quả trung gian trong một bài code nên không tổng quát)

Bài liên quan
0