#include <stdio.h>
#include <stdlib.h>

#define HASH 100003
#define Mod 1000000007

typedef struct List {
	struct List *next;
	long long a, b;
	int num;
} list;

long long gcd(long long a, long long b)
{
	if (a > b) {
		a ^= b;
		b ^= a;
		a ^= b;
	}
	if (b % a == 0) return a;
	else return gcd(b % a, a);
}

int main()
{
	int h, i, N;
	long long A, B, G;
	list *p, *hash[HASH] = {}, d[200010];
	scanf("%d", &N);
	for (i = 1; i <= N; i++) {
		scanf("%lld %lld", &A, &B);
		if (A != 0 && B != 0) {
			G = gcd(llabs(A), llabs(B));
			A /= G;
			B /= G;
			if (A < 0) {
				A *= -1;
				B *= -1;
			}
		} else if (A != 0) A = 1;
		else if (B != 0) B = -1;

		h = (A % HASH - B % HASH * 397 + HASH * 397) % HASH;
		for (p = hash[h]; p != NULL; p = p->next) if (p->a == A && p->b == B) break;
		if (p != NULL) p->num++;
		else {
			d[i].a = A;
			d[i].b = B;
			d[i].num = 1;
			d[i].next = hash[h];
			hash[h] = &(d[i]);
		}
	}

	long long ans = 1, zero = 0, pow[200010], tmp;
	list *q, *r;
	for (i = 1, pow[0] = 1; i <= N; i++) pow[i] = pow[i-1] * 2 % Mod;
	for (h = 0; h < HASH; h++) {
		for (p = hash[h]; p != NULL; p = p->next) {
			if (p->num > 0) {
				if (p->a == 0 && p->b == 0) zero += p->num;
				else {
					tmp = (p->b < 0)? p->a: -(p->a);
					i = (llabs(p->b) % HASH - tmp % HASH * 397 + HASH * 397) % HASH;
					for (q = hash[i]; q != NULL; q = q->next) if (q->a == llabs(p->b) && q->b == tmp) break;
					if (q == NULL) ans = ans * pow[p->num] % Mod;
					else {
						ans = ans * (pow[p->num] + pow[q->num] - 1) % Mod;
						q->num = 0;
					}
				}
			}
		}
	}
	printf("%lld\n", (ans + zero + Mod - 1) % Mod);
	fflush(stdout);
	return 0;
}


#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define ll long long
#define INF 1<<31-1
#define LNF 9223372036854775807
#define PI 3.14159265358979
#define MIN(x,y) ((x)<(y)?(x):(y))
#define MAX(x,y) ((x)<(y)?(y):(x))
#define FOR(i,a,n) for(i=a;i<n;i++)
#define MOD 1000000007
//#define MOD 998244353
#define NUM 200010

void swap(int* a,int* b){int tmp=*a;*a=*b;*b=tmp;}
int abs(int a){if(a<0)return -a;return a;}
int dup(int a,int d){if(a%d==0)return a/d;else return a/d+1;}
int mod(int a,int m){if(a<0)return a%m+m;else return a%m;}

void swapl(ll* a,ll* b){ll tmp=*a;*a=*b;*b=tmp;}
ll absl(ll a){if(a<0)return -a;return a;}
ll dupl(ll a,ll d){if(a%d==0)return a/d;else return a/d+1;}
ll modl(ll a,ll m){if(a<0)return a%m+m;else return a%m;}
ll signl(ll a) {return a==0 ? 0 : (a>0? 1 : -1);}

ll GCD(ll a,ll b){ll c=1,tmp=MAX(a,b);b=MIN(a,b);a=tmp;while(c!=0){c=a%b;a=b;b=c;}return a;}
ll LCM(ll a,ll b){return a*b/GCD(a,b);}

ll factorial(ll n, ll m) {return n < m ? 1 : n*factorial(n-1, m);}

int upll(const void*a, const void*b){return*(ll*)a<*(ll*)b?-1:*(ll*)a>*(ll*)b?1:0;}
int downll(const void*a, const void*b){return*(ll*)a<*(ll*)b?1:*(ll*)a>*(ll*)b?-1:0;}
void sortup(ll*a,int n){qsort(a,n,sizeof(ll),upll);}
void sortdown(ll*a,int n){qsort(a,n,sizeof(ll),downll);}

typedef struct
{
    ll a;
    ll b;
} box;

int cmp(box *p,box *q){
	if((*(box*)p).a<(*(box*)q).a)return -1;
	if((*(box*)p).a>(*(box*)q).a)return 1;
	if((*(box*)p).b<(*(box*)q).b)return -1;
	if((*(box*)p).b>(*(box*)q).b)return 1;
	return 0;
}
int boxa_downll(const void*a, const void*b){return cmp((box*)a, (box*)b);}

ll pow2l(ll n){
    if (n == 0)
    {
        return 1;
    }
    return (2*pow2l(n-1)%MOD);
}

ll pom(ll a,ll n,int m){ll x=1;for(a%=m;n;n/=2)n&1?x=x*a%m:0,a=a*a%m;return x;}

int main(){
    box p[400000];
    box m[400000];
    ll pn=0, mn=0, zn0=0 , zn1=0, zn2=0, n, k;
    ll a, b, gcdab;
    ll i, j, ans=0;
    ll u, v;

    scanf("%lld", &n);

    for (i = 0; i < n; i++)
    {
        scanf("%lld%lld", &a, &b);

        if (signl(a) * signl(b) == 0) {
            if(a == 0 && b==0) zn0++;
            else if(a == 0) zn1++;
            else zn2++;
        }
        else if(signl(a) * signl(b) > 0) {
            a = absl(a), b = absl(b);
            gcdab = GCD(a, b);
            a /= gcdab;
            b /= gcdab;
            p[pn].a = a;
            p[pn].b = b;
            pn++;
        }
        else
        {
            a = absl(a), b = absl(b);
            gcdab = GCD(a, b);
            a /= gcdab;
            b /= gcdab;
            // uetohatigau
            m[mn].a = b;
            m[mn].b = a;
            mn++;
        }
    }

    qsort(p,pn,sizeof(box), boxa_downll);
    qsort(m,mn,sizeof(box), boxa_downll);

    ans = MOD - 1;
    ans = (ans + pow2l(zn1))%MOD;
    ans = (ans + pow2l(zn2))%MOD;

    i=j=0;
    while(i < pn && j < mn){
        if(cmp(&p[i], &m[j]) == 0) {
            for (u = 1; i + u < pn; u++) if (cmp(&p[i], &p[i+u]) != 0) break;
            for (v = 1; j + v < mn; v++) if (cmp(&m[j], &m[j+v]) != 0) break;

            k = MOD - 1;
            k = (k + pom(2, u, MOD))%MOD;
            k = (k + pom(2, v, MOD))%MOD;

            ans = ans * k % MOD;

            i += u;
            j += v;
        }
        else {
            ans = ans * 2 %MOD;

            if(cmp(&p[i], &m[j]) < 0) i++;
            else j++;
        }
    }

    ans = ans*pow2l(pn-i)%MOD;
    ans = ans*pow2l(mn-j)%MOD;

    // 0,0 の場合
    ans = (ans + zn0) % MOD;

    ans = (MOD + ans - 1) % MOD;

    printf("%lld\n", ans);

    return 0;
}


#include <float.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

// 内部定数
#define D_MOD			1000000007								// 除数(10の9乗+7)
#define D_TREE_MAX		200000									// 木の最大データ数
#define D_TREE_WCNT		2										// 木の方向数
#define D_TREE_LEFT		0										// 木の方向 - 左側
#define D_TREE_RIGHT	1										// 木の方向 - 右側

// 内部構造体 - 木構造
typedef struct Tree {
	long long mlKey1;											// キー１
	long long mlKey2;											// キー２
	int miCnt;													// データ数
	int mi1Height[D_TREE_WCNT];									// 木の高さ
	struct Tree *mzp1Child[D_TREE_WCNT];						// 子
} Tree;

// 内部変数
static FILE *szpFpI;											// 入力
static Tree sz1Tree[D_TREE_MAX];								// 木の実データ
static int siTCnt;												// 木の実データ数
static Tree *szpTop;											// 先頭の木データ

// 内部変数 - テスト用
#ifdef D_TEST
	static int siRes;
	static FILE *szpFpA;
	static int siTNo;
#endif

// １行出力
int
fOutLine(
	char *pcpLine				// <I> １行
)
{
	char lc1Buf[1024];

#ifdef D_TEST
	lc1Buf[0] = '\0';
	fgets(lc1Buf, sizeof(lc1Buf), szpFpA);
	if (strcmp(lc1Buf, pcpLine)) {
		siRes = -1;
	}
#else
	printf("%s", pcpLine);
#endif

	return 0;
}

// 値 - 入れ替え
int
fSwap(
	int *pipVal1				// <IO> 値１
	, int *pipVal2				// <IO> 値２
)
{
	int liWork = *pipVal1;
	*pipVal1 = *pipVal2;
	*pipVal2 = liWork;

	return 0;
}

// 和 - 取得
int
fGetModSum(
	int piVal1					// <I> 値１
	, int piVal2				// <I> 値２
)
{
	return (piVal1 + piVal2) % D_MOD;
}

// 積 - 取得
int
fGetModMulti(
	int piVal1					// <I> 値１
	, int piVal2				// <I> 値２
)
{
	return (int)((long long)piVal1 * (long long)piVal2 % D_MOD);
}

// べき乗 - 取得
int
fGetModPower(
	int piBase					// <I> 基数
	, int piIdx					// <I> 指数
)
{
	// 基数
	if (piBase == 0) {
		return 0;
	}

	// 平方リスト - 作成
	int li1Val[100];
	li1Val[0] = piBase;
	int liCnt = 1;
	int liIdx = 1;
	while (piIdx > liIdx) {
		li1Val[liCnt] = fGetModMulti(li1Val[liCnt - 1], li1Val[liCnt - 1]);
		liCnt++;
		liIdx += liIdx;
	}

	// べき乗 - 取得
	int liVal = 1;
	while (piIdx > 0) {
		if (piIdx >= liIdx) {
			piIdx -= liIdx;
			liVal = fGetModMulti(liVal, li1Val[liCnt - 1]);
		}
		liCnt--;
		liIdx /= 2;
	}

	return liVal;
}

// 最大公約数 - 取得
long long
fGetGcd(
	long long plVal1			// <I> 値１
	, long long plVal2			// <I> 値２
)
{
	long long llWork;

	// ユークリッドの互除法
	if (plVal1 < plVal2) {
		llWork = plVal1;
		plVal1 = plVal2;
		plVal2 = llWork;
	}
	while (plVal2 > 0) {
		llWork = plVal1 % plVal2;
		plVal1 = plVal2;
		plVal2 = llWork;
	}

	return plVal1;
}

// 木データ - 作成
Tree *
fTreeMake(
	long long plKey1			// <I> キー１
	, long long plKey2			// <I> キー２
	, int piCnt					// <I> データ数
)
{
	// 対象の木データ
	Tree *lzpTree = &(sz1Tree[siTCnt]);
	siTCnt++;

	// データセット
	memset(lzpTree, 0, sizeof(Tree));
	lzpTree->mlKey1 = plKey1;
	lzpTree->mlKey2 = plKey2;
	lzpTree->miCnt = piCnt;

	return lzpTree;
}

// 木データ - 比較 - キー昇順
int
fTreeCmp(
	Tree *pzpTree				// <I> 木データ
	, long long plKey1			// <I> キー１
	, long long plKey2			// <I> キー２
)
{
	// キー昇順
	if (plKey1 < pzpTree->mlKey1) {
		return -1;
	}
	else if (plKey1 > pzpTree->mlKey1) {
		return 1;
	}
	if (plKey2 < pzpTree->mlKey2) {
		return -1;
	}
	else if (plKey2 > pzpTree->mlKey2) {
		return 1;
	}

	return 0;
}

// 木データ - 検索
Tree *
fTreeSrh(
	long long plKey1			// <I> キー１
	, long long plKey2			// <I> キー２
)
{
	// 先頭の木データ
	Tree *lzpNow = szpTop;

	// 検索
	while (1) {

		// データ有無
		if (lzpNow == NULL) {
			return NULL;
		}

		// 比較
		int liRet = fTreeCmp(lzpNow, plKey1, plKey2);
		if (liRet == 0) {								// 一致
			return lzpNow;
		}

		// 子へ移動
		if (liRet < 0) {								// 左側
			lzpNow = lzpNow->mzp1Child[D_TREE_LEFT];
		}
		else {											// 右側
			lzpNow = lzpNow->mzp1Child[D_TREE_RIGHT];
		}
	}

	return NULL;
}

// 木データ - 高さ取得
int
fTreeGetHeight(
	Tree *pzpTree				// <I> 対象の木情報
)
{
	// データ有無
	if (pzpTree == NULL) {
		return 0;
	}

	if (pzpTree->mi1Height[D_TREE_LEFT] >= pzpTree->mi1Height[D_TREE_RIGHT]) {
		return pzpTree->mi1Height[D_TREE_LEFT] + 1;
	}
	else {
		return pzpTree->mi1Height[D_TREE_RIGHT] + 1;
	}
}

// 木データ - 右回転(親は子[左]の右下へ・子[左]は親の左上へ)
int
fTreeRttR(
	Tree **pzppTree				// <I> 回転対象
)
{
	// 現在の子[左]
	Tree *lzpChild = (*pzppTree)->mzp1Child[D_TREE_LEFT];

	// 右回転
	(*pzppTree)->mzp1Child[D_TREE_LEFT] = lzpChild->mzp1Child[D_TREE_RIGHT];	// 親の左側 = 子[左]の右側
	(*pzppTree)->mi1Height[D_TREE_LEFT] = lzpChild->mi1Height[D_TREE_RIGHT];	// 親の高さ(左) = 子[左]の高さ(右)
	lzpChild->mzp1Child[D_TREE_RIGHT] = *pzppTree;								// 子[左]の右側 = 親
	lzpChild->mi1Height[D_TREE_RIGHT] = fTreeGetHeight(*pzppTree);				// 子[左]の高さ(右) - 親の高さ
	*pzppTree = lzpChild;														// 親 = 子[左]

	return 0;
}

// 木データ - 左回転(親は子[右]の左下へ・子[右]は親の右上へ)
int
fTreeRttL(
	Tree **pzppTree				// <I> 回転対象
)
{
	// 現在の子[右]
	Tree *lzpChild = (*pzppTree)->mzp1Child[D_TREE_RIGHT];

	// 左回転
	(*pzppTree)->mzp1Child[D_TREE_RIGHT] = lzpChild->mzp1Child[D_TREE_LEFT];	// 親の右側 = 子[右]の左側
	(*pzppTree)->mi1Height[D_TREE_RIGHT] = lzpChild->mi1Height[D_TREE_LEFT];	// 親の高さ(右) = 子[右]の高さ(左)
	lzpChild->mzp1Child[D_TREE_LEFT] = *pzppTree;								// 子[右]の左側 = 親
	lzpChild->mi1Height[D_TREE_LEFT] = fTreeGetHeight(*pzppTree);				// 子[右]の高さ(左) - 親の高さ
	*pzppTree = lzpChild;														// 親 = 子[右]

	return 0;
}

// 木データ - 追加・削除の共通処理
// 戻り値：[2]高さの変更あり [1]高さの変更なし
int
fTreeComAddDel(
	Tree **pzppNow				// <I> 現在の木情報
	, int piWay					// <I> 対象の方向
)
{
	// 高さの変更があるかチェック
	int liNew = fTreeGetHeight((*pzppNow)->mzp1Child[piWay]);
	if ((*pzppNow)->mi1Height[piWay] == liNew) {												// 変化なし
		return 1;
	}
	(*pzppNow)->mi1Height[piWay] = liNew;														// 更新

	// 高さが離れている場合、回転
	if ((*pzppNow)->mi1Height[D_TREE_LEFT] - (*pzppNow)->mi1Height[D_TREE_RIGHT] > 1) {			// 左側が高い
		fTreeRttR(pzppNow);																			// 右回転
	}
	else if ((*pzppNow)->mi1Height[D_TREE_RIGHT] - (*pzppNow)->mi1Height[D_TREE_LEFT] > 1) {	// 右側が高い
		fTreeRttL(pzppNow);																			// 左回転
	}

	return 2;
}

// 木データ - 追加
// 戻り値：[2]高さの変更あり [1]高さの変更なし [0]データ数の追加のみ
int
fTreeAdd(
	Tree **pzppNow				// <I> 現在の木情報
	, long long plKey1			// <I> キー１
	, long long plKey2			// <I> キー２
	, int piCnt					// <I> データ数
)
{
	// 作成
	if (*pzppNow == NULL) {
		*pzppNow = fTreeMake(plKey1, plKey2, piCnt);
		return 2;
	}

	// 比較
	int liRet = fTreeCmp(*pzppNow, plKey1, plKey2);

	// 一致
	if (liRet == 0) {
		(*pzppNow)->miCnt += piCnt;
		return 0;
	}

	// 方向の判別
	int liWay;
	if (liRet < 0) {																// 左側
		liWay = D_TREE_LEFT;
	}
	else {																			// 右側
		liWay = D_TREE_RIGHT;
	}

	// 下位へ
	liRet = fTreeAdd(&((*pzppNow)->mzp1Child[liWay]), plKey1, plKey2, piCnt);
	if (liRet < 2) {																// 高さの変更あり以外
		return liRet;
	}

	// 追加・削除の共通処理
	return fTreeComAddDel(pzppNow, liWay);
}

// 同時不可のパターン数 - 取得
int
fGetCntSameNG(
	int piFCnt1					// <I> 魚数１
	, int piFCnt2				// <I> 魚数２
)
{
	if (piFCnt1 < piFCnt2) {
		fSwap(&piFCnt1, &piFCnt2);
	}

	// 共になし
	if (piFCnt1 == 0) {
		return 1;
	}
	piFCnt1 = fGetModPower(2, piFCnt1);

	// 片方のみ
	if (piFCnt2 == 0) {
		return piFCnt1;
	}
	piFCnt2 = fGetModPower(2, piFCnt2);

	// 両方あり
	return fGetModSum(piFCnt1, piFCnt2 - 1);
}

// 実行メイン
int
fMain(
)
{
	int i;
	char lc1Buf[1024];

	// データ - 初期化
	siTCnt = 0;
	szpTop = NULL;

	// 魚数 - 取得
	int liFCnt;
	fgets(lc1Buf, sizeof(lc1Buf), szpFpI);
	sscanf(lc1Buf, "%d", &liFCnt);

	// 魚 - 取得
	for (i = 0; i < liFCnt; i++) {
		long long llVal1, llVal2;
		fgets(lc1Buf, sizeof(lc1Buf), szpFpI);
		sscanf(lc1Buf, "%lld%lld", &llVal1, &llVal2);

		// 登録
		if (llVal1 == 0) {
			if (llVal2 == 0) {
				fTreeAdd(&szpTop, 0, 0, 1);
			}
			else {
				fTreeAdd(&szpTop, 0, 1, 1);
			}
		}
		else {
			if (llVal2 == 0) {
				fTreeAdd(&szpTop, 1, 0, 1);
			}
			else {

				// 符号
				long long llSign;
				if (llVal1 * llVal2 > 0) {
					llSign = 1;
				}
				else {
					llSign = -1;
					llVal1 = llabs(llVal1);
					llVal2 = llabs(llVal2);
				}

				// 最大公約数 - 取得
				long long llGcd = fGetGcd(llVal1, llVal2);
				llVal1 /= llGcd;
				llVal2 /= llGcd;

				// 登録
				fTreeAdd(&szpTop, llVal1, llVal2 * llSign, 1);
			}
		}
	}

	// パターン数 - 取得
	int liSum = 1;

	// パターン数 - 加算 - 0,0
	Tree *lzpTree = fTreeSrh(0, 0);
	if (lzpTree != NULL) {
		liSum = fGetModMulti(liSum, lzpTree->miCnt + 1);
		lzpTree->miCnt = 0;
	}

	// パターン数 - 加算 - 1,0 + 0,1
	int liFCnt1, liFCnt2;
	lzpTree = fTreeSrh(1, 0);
	if (lzpTree == NULL) {
		liFCnt1 = 0;
	}
	else {
		liFCnt1 = lzpTree->miCnt;
		lzpTree->miCnt = 0;
	}
	lzpTree = fTreeSrh(0, 1);
	if (lzpTree == NULL) {
		liFCnt2 = 0;
	}
	else {
		liFCnt2 = lzpTree->miCnt;
		lzpTree->miCnt = 0;
	}
	int liPCnt = fGetCntSameNG(liFCnt1, liFCnt2);
	liSum = fGetModMulti(liSum, liPCnt);

	// パターン数 - 加算 - x,y + y,-x
	for (i = 0; i < siTCnt; i++) {

		// 処理済
		if (sz1Tree[i].miCnt == 0) {
			continue;
		}

		// パターン数
		liFCnt1 = sz1Tree[i].miCnt;
		sz1Tree[i].miCnt = 0;
		if (sz1Tree[i].mlKey2 > 0) {
			lzpTree = fTreeSrh(sz1Tree[i].mlKey2, -sz1Tree[i].mlKey1);
		}
		else {
			lzpTree = fTreeSrh(-sz1Tree[i].mlKey2, sz1Tree[i].mlKey1);
		}
		if (lzpTree == NULL) {
			liFCnt2 = 0;
		}
		else {
			liFCnt2 = lzpTree->miCnt;
			lzpTree->miCnt = 0;
		}
		liPCnt = fGetCntSameNG(liFCnt1, liFCnt2);
		liSum = fGetModMulti(liSum, liPCnt);
	}

	return liSum - 1;
}

// １回実行
int
fOne(
)
{
	int liRet;
	char lc1Buf[1024];

	// 入力 - セット
#ifdef D_TEST
	sprintf(lc1Buf, ".\\Test\\T%d.txt", siTNo);
	szpFpI = fopen(lc1Buf, "r");
	sprintf(lc1Buf, ".\\Test\\A%d.txt", siTNo);
	szpFpA = fopen(lc1Buf, "r");
	siRes = 0;
#else
	szpFpI = stdin;
#endif

	// 実行メイン
	liRet = fMain();

	// 出力
	sprintf(lc1Buf, "%d\n", liRet);
	fOutLine(lc1Buf);

	// 残データ有無
#ifdef D_TEST
	lc1Buf[0] = '\0';
	fgets(lc1Buf, sizeof(lc1Buf), szpFpA);
	if (strcmp(lc1Buf, "")) {
		siRes = -1;
	}
#endif

	// テストファイルクローズ
#ifdef D_TEST
	fclose(szpFpI);
	fclose(szpFpA);
#endif

	// テスト結果
#ifdef D_TEST
	if (siRes == 0) {
		printf("OK %d\n", siTNo);
	}
	else {
		printf("NG %d\n", siTNo);
	}
#endif

	return 0;
}

// プログラム開始
int
main()
{

#ifdef D_TEST
	int i;
	for (i = D_TEST_SNO; i <= D_TEST_ENO; i++) {
		siTNo = i;
		fOne();
	}
#else
	fOne();
#endif

	return 0;
}


#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
const long mod = 1000000007;
long gcd(long a, long b){return b?gcd(b,a%b):a;}
long modpow(long x, long y, long m){
	long res = 1, tmp = x;
	while(y){
		if(y&1)res = res * tmp % m;
		tmp = tmp * tmp % m;
		y >>= 1;
	}
	return res;
}
long pom(long x, long y){return modpow(x, y, mod);}
typedef struct{
	long x, y;
}cor;
bool cor_lt(cor u, cor v){
	if(u.x < v.x)return true;
	if(u.x == v.x && u.y > v.y)return true;
	return false;
}
bool cor_eq(cor u, cor v){
	return u.x==v.x && u.y==v.y;
}
bool cor_gt(cor u, cor v){
	return !cor_lt(u, v) && !cor_eq(u, v);
}
int cmp(const void *a, const void *b){
	cor u = *(cor*)a, v = *(cor*)b;
	return cor_lt(u, v)?1:cor_eq(u, v)?0:-1;
}
int N, cnt_zero = 0;
long ans = 1;
cor A[1<<18];
int lower_bound(cor u){
	int l = -1, r = N;
	while(r - l > 1){
		int mid = (r + l) / 2;
		if(cor_gt(A[mid], u))l = mid;
		else r = mid;
	}
	return r;
}
int upper_bound(cor u){
	int l = -1, r = N;
	while(r - l > 1){
		int mid = (r + l) / 2;
		if(cor_lt(A[mid], u))r = mid;
		else l = mid;
	}
	return r;
}
int main(){
	scanf("%d", &N);
	for(int i=0; i<N; i++){
		long s, t;
		scanf("%ld%ld", &s, &t);
		if(t < 0){
			s *= -1;
			t *= -1;
		}
		if(s == 0){
			if(t) t = 1;
			else {
				cnt_zero++;
				i--;
				N--;
			}
		}else if(t == 0){
			s = 1;
		}else {
			long x = gcd(labs(s), labs(t));
			s /= x;
			t /= x;
		}
		if(true){
			A[i].x = s;
			A[i].y = t;
		}
	}
	qsort(A, N, sizeof(cor), cmp);
	if(N){
		int cnt = 1, i = 1;
		cor tmp;
		for(; i<N && A[i].x > 0; i++){
			if(cor_eq(A[i], A[i-1]))cnt++;
			else{
				tmp.x = A[i-1].y;
				tmp.y = -A[i-1].x;
				if(tmp.y < 0){
					tmp.x *= -1;
					tmp.y *= -1;
				}
				ans = ans * (pom(2, cnt) + pom(2, upper_bound(tmp) - lower_bound(tmp)) - 1) % mod;
				cnt = 1;
			}
		}
		tmp.x = A[i-1].y;
		tmp.y = -A[i-1].x;
		if(tmp.y < 0){
			tmp.x *= -1;
			tmp.y *= -1;
		}
		ans = ans * (pom(2, cnt) + pom(2, upper_bound(tmp) - lower_bound(tmp)) - 1) % mod;
		cnt = 1;
		for(i += A[i-1].x > 0; i<N; i++){
			if(cor_eq(A[i], A[i-1]))cnt++;
			else{
				tmp.x = A[i-1].y;
				tmp.y = -A[i-1].x;
				if(tmp.y < 0){
					tmp.x *= -1;
					tmp.y *= -1;
				}
				if(upper_bound(tmp) == lower_bound(tmp)){
					ans = ans * pom(2, cnt) % mod;
				}
				cnt = 1;
			}
		}
		tmp.x = A[i-1].y;
		tmp.y = -A[i-1].x;
		if(tmp.y < 0){
			tmp.x *= -1;
			tmp.y *= -1;
		}
		if(upper_bound(tmp) == lower_bound(tmp))ans = ans * pom(2, cnt) % mod;
	}
	ans = (ans + cnt_zero - 1 + mod) % mod;
	printf("%ld\n", ans);
	return 0;
}


#include <stdio.h>
#include <stdlib.h>

#define HASH 100003
#define Mod 1000000007

typedef struct List {
	struct List *next;
	long long a, b;
	int num;
} list;

long long gcd(long long a, long long b)
{
	if (a > b) {
		a ^= b;
		b ^= a;
		a ^= b;
	}
	if (b % a == 0) return a;
	else return gcd(b % a, a);
}

int main()
{
	int h, i, N;
	long long A, B, G;
	list *p, *hash[HASH] = {}, d[200010];
	scanf("%d", &N);
	for (i = 1; i <= N; i++) {
		scanf("%lld %lld", &A, &B);
		if (A != 0 && B != 0) {
			G = gcd(llabs(A), llabs(B));
			A /= G;
			B /= G;
			if (A < 0) {
				A *= -1;
				B *= -1;
			}
		} else if (A != 0) A = 1;
		else if (B != 0) B = -1;

		h = (A % HASH - B % HASH * 397 + HASH * 397) % HASH;
		for (p = hash[h]; p != NULL; p = p->next) if (p->a == A && p->b == B) break;
		if (p != NULL) p->num++;
		else {
			d[i].a = A;
			d[i].b = B;
			d[i].num = 1;
			d[i].next = hash[h];
			hash[h] = &(d[i]);
		}
	}

	long long ans = 1, pow[200010], tmp;
	list *q, *r;
	for (i = 1, pow[0] = 1; i <= N; i++) pow[i] = pow[i-1] * 2 % Mod;
	for (h = 0; h < HASH; h++) {
		for (p = hash[h]; p != NULL; p = p->next) {
			if (p->num > 0) {
				if (p->a == 0 && p->b == 0) {
					for (q = hash[1]; q != NULL; q = q->next) if (q->b == 0) break;
					for (r = hash[397]; r != NULL; r = r->next) if (r->a == 0) break;
					tmp = p->num + 1;
					if (q != NULL) {
						tmp += pow[q->num] - 1;
						q->num = 0;
					}
					if (r != NULL) {
						tmp += pow[r->num] - 1;
						r->num = 0;
					}
					ans = ans * tmp % Mod;
				} else {
					tmp = (p->b < 0)? p->a: -(p->a);
					i = (llabs(p->b) % HASH - tmp % HASH * 397 + HASH * 397) % HASH;
					for (q = hash[i]; q != NULL; q = q->next) if (q->a == llabs(p->b) && q->b == tmp) break;
					if (q == NULL) ans = ans * pow[p->num] % Mod;
					else {
						ans = ans * (pow[p->num] + pow[q->num] - 1) % Mod;
						q->num = 0;
					}
				}
			}
		}
	}
	printf("%lld\n", (ans + Mod - 1) % Mod);
	fflush(stdout);
	return 0;
}


"use strict";
var input=require("fs").readFileSync("/dev/stdin","utf8");
var cin=input.split(/ |\n/),cid=0;
function next(){return +cin[cid++];}
function nextstr(){return cin[cid++];}
function nextbig(){return BigInt(cin[cid++]);}
function nexts(n,a){return a?cin.slice(cid,cid+=n):cin.slice(cid,cid+=n).map(a=>+a);}
function nextm(h,w,a){var r=[],i=0;if(a)for(;i<h;i++)r.push(cin.slice(cid,cid+=w));else for(;i<h;i++)r.push(cin.slice(cid,cid+=w).map(a=>+a));return r;}
function xArray(v){var a=arguments,l=a.length,r="Array(a["+--l+"]).fill().map(x=>{return "+v+";})";while(--l)r="Array(a["+l+"]).fill().map(x=>"+r+")";return eval(r);}

function gcd(a,b){return b?gcd(b,a%b):a;}

var mod = 1e9+7;
function mul(a,b){return ((a>>16)*b%mod*65536+(a&65535)*b)%mod;}
function pow(a,n){for(var r=1;n;a=mul(a,a),n>>=1)if(n&1)r=mul(a,r);return r;}
function mput(n){return (n%mod+mod)%mod;}

var myOut = main();
if(myOut !== undefined)console.log(myOut);

function main(){
  var n = next();
  var az = 0, bz = 0, z = 0;
  var map = new Map();
  for(var i = 0; i < n; i++){
    var a = nextbig(), b = nextbig();
    if(a === 0n && b === 0n){z++; continue;}
    if(a === 0n){az++; continue;}
    if(b === 0n){bz++; continue;}
    var c = gcd(a,b);
    a /= c, b /= c;
    if(a < 0)a = -a, b = -b;
    if(map.has(a)){
      if(map.get(a).has(b))map.get(a).set(b,map.get(a).get(b)+1);
      else map.get(a).set(b,1);
    }else{
      map.set(a,new Map());
      map.get(a).set(b,1);
    }
  }
  var ans = pow(2,az) + pow(2,bz) - 1;
  for(var [i0,i1] of map){
    for(var [j0,j1] of i1){
      var t = 0;
      if(j0 < 0){
        if(map.has(-j0) && map.get(-j0).has(i0))continue;
      }else{
        if(map.has(j0) && map.get(j0).has(-i0))t = pow(2,map.get(j0).get(-i0))-1;
      }
      t += pow(2,j1);
      ans = mul(ans,t);
    }
  }
  return mput(ans-1+z);
}


"use strict";
var input=require("fs").readFileSync("/dev/stdin","utf8");
var cin=input.split(/ |\n/),cid=0;
function next(){return +cin[cid++];}
function nextstr(){return cin[cid++];}
function nextbig(){return BigInt(cin[cid++]);}
function nexts(n,a){return a?cin.slice(cid,cid+=n):cin.slice(cid,cid+=n).map(a=>+a);}
function nextm(h,w,a){var r=[],i=0;if(a)for(;i<h;i++)r.push(cin.slice(cid,cid+=w));else for(;i<h;i++)r.push(cin.slice(cid,cid+=w).map(a=>+a));return r;}
function xArray(v){var a=arguments,l=a.length,r="Array(a["+--l+"]).fill().map(x=>{return "+v+";})";while(--l)r="Array(a["+l+"]).fill().map(x=>"+r+")";return eval(r);}

function gcd(a,b){return b?gcd(b,a%b):a;}

var mod = 1e9+7;
function mul(a,b){return ((a>>16)*b%mod*65536+(a&65535)*b)%mod;}
function pow(a,n){for(var r=1;n;a=mul(a,a),n>>=1)if(n&1)r=mul(a,r);return r;}
function mput(n){return (n%mod+mod)%mod;}

var myOut = main();
if(myOut !== undefined)console.log(myOut);

function main(){
  var n = next();
  var az = 0, bz = 0, z = 0;
  var map = new Map();
  for(var i = 0; i < n; i++){
    var a = nextbig(), b = nextbig();
    if(a === 0n && b === 0n){z++; continue;}
    if(a === 0n){az++; continue;}
    if(b === 0n){bz++; continue;}
    var c = gcd(a,b);
    a /= c, b /= c;
    if(a < 0)a = -a, b = -b;
    if(map.has(a)){
      if(map.get(a).has(b))map.get(a).set(b,map.get(a).get(b)+1);
      else map.get(a).set(b,1);
    }else{
      map.set(a,new Map());
      map.get(a).set(b,1);
    }
  }
  var ans = pow(2,az) + pow(2,bz) - 1;
  for(var i of map){
    for(var j of i[1]){
      var t = 0;
      if(j[0] < 0){
        if(map.has(-j[0]) && map.get(-j[0]).has(i[0]))continue;
      }else{
        if(map.has(j[0]) && map.get(j[0]).has(-i[0]))t = pow(2,map.get(j[0]).get(-i[0]))-1;
      }
      t += pow(2,map.get(i[0]).get(j[0]));
      ans = mul(ans,t);
    }
  }
  return mput(ans-1+z);
}


"use strict";
var input=require("fs").readFileSync("/dev/stdin","utf8");
var cin=input.split(/ |\n/),cid=0;
function next(){return +cin[cid++];}
function nextstr(){return cin[cid++];}
function nextbig(){return BigInt(cin[cid++]);}
function nexts(n,a){return a?cin.slice(cid,cid+=n):cin.slice(cid,cid+=n).map(a=>+a);}
function nextm(h,w,a){var r=[],i=0;if(a)for(;i<h;i++)r.push(cin.slice(cid,cid+=w));else for(;i<h;i++)r.push(cin.slice(cid,cid+=w).map(a=>+a));return r;}
function xArray(v){var a=arguments,l=a.length,r="Array(a["+--l+"]).fill().map(x=>{return "+v+";})";while(--l)r="Array(a["+l+"]).fill().map(x=>"+r+")";return eval(r);}

function gcd(a,b){return b?gcd(b,a%b):a;}

var mod = 1e9+7;
function mul(a,b){return ((a>>16)*b%mod*65536+(a&65535)*b)%mod;}
function pow(a,n){for(var r=1;n;a=mul(a,a),n>>=1)if(n&1)r=mul(a,r);return r;}
function mput(n){return (n%mod+mod)%mod;}

var myOut = main();
if(myOut !== undefined)console.log(myOut);

function main(){
  var n = next();
  var az = 0, bz = 0, z = 0;
  var map = {};
  for(var i = 0; i < n; i++){
    var a = nextbig(), b = nextbig();
    if(a === 0n && b === 0n){z++; continue;}
    if(a === 0n){az++; continue;}
    if(b === 0n){bz++; continue;}
    var c = gcd(a,b);
    a /= c, b /= c;
    if(a < 0)a = -a, b = -b;
    if(map[a]){
      if(map[a][b])map[a][b]++;
      else map[a][b] = 1;
    }else{
      map[a] = {};
      map[a][b] = 1;
    }
  }
  var ans = pow(2,az) + pow(2,bz) - 1;
  for(var i in map){
    for(var j in map[i]){
      var t = 0;
      if(j < 0){
        if(map[j.slice(1)] && map[j.slice(1)][i])continue;
      }else{
        if(map[j] && map[j]["-"+i])t = pow(2,map[j]["-"+i])-1;
      }
      t += pow(2,map[i][j]);
      ans = mul(ans,t);
    }
  }
  return mput(ans-1+z);
}


"use strict";
var input=require("fs").readFileSync("/dev/stdin","utf8");
var cin=input.split(/ |\n/),cid=0;
function next(){return +cin[cid++];}
function nextstr(){return cin[cid++];}
function nextbig(){return BigInt(cin[cid++]);}
function nexts(n,a){return a?cin.slice(cid,cid+=n):cin.slice(cid,cid+=n).map(a=>+a);}
function nextm(h,w,a){var r=[],i=0;if(a)for(;i<h;i++)r.push(cin.slice(cid,cid+=w));else for(;i<h;i++)r.push(cin.slice(cid,cid+=w).map(a=>+a));return r;}
function xArray(v){var a=arguments,l=a.length,r="Array(a["+--l+"]).fill().map(x=>{return "+v+";})";while(--l)r="Array(a["+l+"]).fill().map(x=>"+r+")";return eval(r);}

function gcd(a,b){return b?gcd(b,a%b):a;}

var mod = 1e9+7;
function mul(a,b){return ((a>>16)*b%mod*65536+(a&65535)*b)%mod;}
function pow(a,n){for(var r=1;n;a=mul(a,a),n>>=1)if(n&1)r=mul(a,r);return r;}
function mput(n){return (n%mod+mod)%mod;}

var myOut = main();
if(myOut !== undefined)console.log(myOut);

function main(){
  const n = next();
  let az = 0, bz = 0, z = 0;
  const map = {};
  for(let i = 0; i < n; i++){
    let a = nextbig(), b = nextbig();
    if(a === 0n && b === 0n){z++; continue;}
    if(a === 0n){az++; continue;}
    if(b === 0n){bz++; continue;}
    const c = gcd(a,b);
    a /= c, b /= c;
    if(a < 0)a = -a, b = -b;
    if(map[a]){
      if(map[a][b])map[a][b]++;
      else map[a][b] = 1;
    }else{
      map[a] = {};
      map[a][b] = 1;
    }
  }
  let ans = pow(2,az) + pow(2,bz) - 1;
  for(let i in map){
    for(let j in map[i]){
      let t = 0;
      if(j < 0){
        if(map[j.slice(1)] && map[j.slice(1)][i])continue;
      }else{
        if(map[j] && map[j]["-"+i])t = pow(2,map[j]["-"+i])-1;
      }
      t += pow(2,map[i][j]);
      ans = mul(ans,t);
    }
  }
  return mput(ans-1+z);
}


"use strict";
var input=require("fs").readFileSync("/dev/stdin","utf8");
var cin=input.split(/ |\n/),cid=0;
function next(){return +cin[cid++];}
function nextstr(){return cin[cid++];}
function nextbig(){return BigInt(cin[cid++]);}
function nexts(n,a){return a?cin.slice(cid,cid+=n):cin.slice(cid,cid+=n).map(a=>+a);}
function nextm(h,w,a){var r=[],i=0;if(a)for(;i<h;i++)r.push(cin.slice(cid,cid+=w));else for(;i<h;i++)r.push(cin.slice(cid,cid+=w).map(a=>+a));return r;}
function xArray(v){var a=arguments,l=a.length,r="Array(a["+--l+"]).fill().map(x=>{return "+v+";})";while(--l)r="Array(a["+l+"]).fill().map(x=>"+r+")";return eval(r);}

function gcd(a,b){return b?gcd(b,a%b):a;}

var mod = 1e9+7;
function mul(a,b){return ((a>>16)*b%mod*65536+(a&65535)*b)%mod;}
function pow(a,n){for(var r=1;n;a=mul(a,a),n>>=1)if(n&1)r=mul(a,r);return r;}
function mput(n){return (n%mod+mod)%mod;}

var myOut = main();
if(myOut !== undefined)console.log(myOut);

function main(){
  var n = next();
  var az = 0, bz = 0, z = 0;
  var map = {};
  for(var i = 0; i < n; i++){
    var a = nextbig(), b = nextbig();
    if(a === 0n && b === 0n){z++; continue;}
    if(a === 0n){az++; continue;}
    if(b === 0n){bz++; continue;}
    var c = gcd(a,b);
    a /= c, b /= c;
    if(a < 0)a = -a, b = -b;
    if(map[a]){
      if(map[a][b]){
        map[a][b] *= 2;
        if(map[a][b] >= mod)map[a][b] -= mod;
      }
      else map[a][b] = 2;
    }else{
      map[a] = {};
      map[a][b] = 2;
    }
  }
  var ans = pow(2,az) + pow(2,bz) - 1;
  for(var i in map){
    for(var j in map[i]){
      var t = 0;
      if(j < 0){
        if(map[j.slice(1)] && map[j.slice(1)][i])continue;
      }else{
        if(map[j] && map[j]["-"+i])t = map[j]["-"+i]-1;
      }
      t += map[i][j];
      ans = mul(ans,t);
    }
  }
  return mput(ans-1+z);
}


"use strict";
var input=require("fs").readFileSync("/dev/stdin","utf8");
var cin=input.split(/ |\n/),cid=0;
function next(){return +cin[cid++];}
function nextstr(){return cin[cid++];}
function nextbig(){return BigInt(cin[cid++]);}
function nexts(n,a){return a?cin.slice(cid,cid+=n):cin.slice(cid,cid+=n).map(a=>+a);}
function nextm(h,w,a){var r=[],i=0;if(a)for(;i<h;i++)r.push(cin.slice(cid,cid+=w));else for(;i<h;i++)r.push(cin.slice(cid,cid+=w).map(a=>+a));return r;}
function xArray(v){var a=arguments,l=a.length,r="Array(a["+--l+"]).fill().map(x=>{return "+v+";})";while(--l)r="Array(a["+l+"]).fill().map(x=>"+r+")";return eval(r);}

function gcd(a,b){return b?gcd(b,a%b):a;}

var mod = 1e9+7;
function mul(){for(var a=arguments,r=a[0],i=a.length;--i;)r=((r>>16)*a[i]%mod*65536+(r&65535)*a[i])%mod;return r;}
function pow(a,n){for(var r=1;n;a=mul(a,a),n>>=1)if(n&1)r=mul(a,r);return r;}
function mput(n){return (n%mod+mod)%mod;}

var myOut = main();
if(myOut !== undefined)console.log(myOut);

function main(){
  var n = next();
  var az = 0, bz = 0, z = 0;
  var map = {};
  for(var i = 0; i < n; i++){
    var a = nextbig(), b = nextbig();
    if(a === 0n && b === 0n){z++; continue;}
    if(a === 0n){az++; continue;}
    if(b === 0n){bz++; continue;}
    var c = gcd(a,b);
    a /= c, b /= c;
    if(a < 0)a = -a, b = -b;
    if(b < 0){
      var t = -b;
      b = -a, a = t;
    }
    if(map[a]){
      if(map[a][b])map[a][b]++;
      else map[a][b] = 1;
    }else{
      map[a] = {};
      map[a][b] = 1;
    }
  }
  var ans = pow(2,az) + pow(2,bz) - 1;
  for(var i in map){
    for(var j in map[i]){
      var t = 0;
      if(j < 0){
        if(map[i][j.slice(1)])continue;
      }else{
        if(map[i]["-"+j])t = pow(2,map[i]["-"+j])-1;
      }
      t += pow(2,map[i][j]);
      ans = mul(ans,t);
    }
  }
  ans += z-1;
  return ans >= mod ? ans-mod : ans;
}


package main

import (
	. "fmt"
	"io"
	"os"
)

// github.com/EndlessCheng/codeforces-go
func run(_r io.Reader, _w io.Writer) {
	buf := make([]byte, 4096)
	_i := len(buf)
	rc := func() byte {
		if _i == len(buf) {
			_r.Read(buf)
			_i = 0
		}
		b := buf[_i]
		_i++
		return b
	}
	r := func() (x int) {
		b := rc()
		neg := false
		for ; '0' > b || b > '9'; b = rc() {
			if b == '-' {
				neg = true
			}
		}
		for ; '0' <= b && b <= '9'; b = rc() {
			x = x*10 + int(b&15)
		}
		if neg {
			return -x
		}
		return
	}
	abs := func(x int) int {
		if x < 0 {
			return -x
		}
		return x
	}
	gcd := func(a, b int) int {
		for a != 0 {
			a, b = b%a, a
		}
		return b
	}
	const mod int = 1e9 + 7
	pow := func(n int) int {
		res, x := 1, 2
		for ; n > 0; n >>= 1 {
			if n&1 == 1 {
				res = res * x % mod
			}
			x = x * x % mod
		}
		return res
	}

	type pair struct{ x, y int }
	cntO := 0
	cnt := map[pair]int{}
	cnt2 := map[pair]int{}
	n := r()
	for i := 0; i < n; i++ {
		x, y := r(), r()
		if x == 0 && y == 0 {
			cntO++
			continue
		}
		if y < 0 {
			x = -x
			y = -y
		} else if y == 0 {
			x = abs(x)
		}
		g := gcd(abs(x), y)
		x /= g
		y /= g
		if x > 0 {
			cnt[pair{x, y}]++
		} else {
			cnt2[pair{y, -x}]++
		}
	}

	ans := 1
	for p, c := range cnt {
		ans = ans * (pow(c) + pow(cnt2[p]) - 1) % mod
		delete(cnt2, p)
	}
	for _, c := range cnt2 {
		ans = ans * (pow(c)) % mod
	}
	Fprint(_w, (ans+cntO-1+mod)%mod)
}

func main() { run(os.Stdin, os.Stdout) }


package main

import (
	. "fmt"
	"io"
	"os"
)

// github.com/EndlessCheng/codeforces-go
func run(_r io.Reader, _w io.Writer) {
	buf := make([]byte, 4096)
	_i := len(buf)
	rc := func() byte {
		if _i == len(buf) {
			_r.Read(buf)
			_i = 0
		}
		b := buf[_i]
		_i++
		return b
	}
	r := func() (x int) {
		b := rc()
		neg := false
		for ; '0' > b; b = rc() {
			if b == '-' {
				neg = true
			}
		}
		for ; '0' <= b; b = rc() {
			x = x*10 + int(b&15)
		}
		if neg {
			return -x
		}
		return
	}
	abs := func(x int) int {
		if x < 0 {
			return -x
		}
		return x
	}
	gcd := func(a, b int) int {
		for a != 0 {
			a, b = b%a, a
		}
		return b
	}
	const mod int = 1e9 + 7
	pow := func(n int) int {
		res, x := 1, 2
		for ; n > 0; n >>= 1 {
			if n&1 == 1 {
				res = res * x % mod
			}
			x = x * x % mod
		}
		return res
	}

	type pair struct{ x, y int }
	cntO := 0
	cnt := map[pair]int{}
	cnt2 := map[pair]int{}
	n := r()
	for i := 0; i < n; i++ {
		x, y := r(), r()
		if x == 0 && y == 0 {
			cntO++
			continue
		}
		if y < 0 {
			x = -x
			y = -y
		} else if y == 0 {
			x = abs(x)
		}
		g := gcd(abs(x), y)
		x /= g
		y /= g
		if x > 0 {
			cnt[pair{x, y}]++
		} else {
			cnt2[pair{y, -x}]++
		}
	}

	ans := 1
	for p, c := range cnt {
		ans = ans * (pow(c) + pow(cnt2[p]) - 1) % mod
		delete(cnt2, p)
	}
	for _, c := range cnt2 {
		ans = ans * (pow(c)) % mod
	}
	Fprint(_w, (ans+cntO-1+mod)%mod)
}

func main() { run(os.Stdin, os.Stdout) }


package main

import (
	"bufio"
	"fmt"
	"os"
	"sort"
	"strconv"
)

func out(x ...interface{}) {
	fmt.Println(x...)
}

var sc = bufio.NewScanner(os.Stdin)

func getInt() int {
	sc.Scan()
	i, e := strconv.Atoi(sc.Text())
	if e != nil {
		panic(e)
	}
	return i
}

func getString() string {
	sc.Scan()
	return sc.Text()
}

// min, max, asub, absなど基本関数
func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func asub(a, b int) int {
	if a > b {
		return a - b
	}
	return b - a
}

func abs(a int) int {
	if a >= 0 {
		return a
	}
	return -a
}

func lowerBound(a []int, x int) int {
	idx := sort.Search(len(a), func(i int) bool {
		return a[i] >= x
	})
	return idx
}

func upperBound(a []int, x int) int {
	idx := sort.Search(len(a), func(i int) bool {
		return a[i] > x
	})
	return idx
}

const mod = 1000000007

func mpow(a, b int) int {
	if b == 0 {
		return 1
	} else if b%2 == 0 {
		tmp := mpow(a, b/2)
		return tmp * tmp % mod
	}
	return mpow(a, b-1) * a % mod
}

var fracMemo = []int{1, 1}

func mfrac(n int) int {
	if len(fracMemo) > n {
		return fracMemo[n]
	}
	if len(fracMemo) == 0 {
		fracMemo = append(fracMemo, 1)
	}
	for len(fracMemo) <= n {
		size := len(fracMemo)
		fracMemo = append(fracMemo, fracMemo[size-1]*size%mod)
	}
	return fracMemo[n]
}

var ifracMemo = []int{1, 1}

func mifrac(n int) int {
	if len(ifracMemo) > n {
		return ifracMemo[n]
	}
	if len(ifracMemo) == 0 {
		fracMemo = append(ifracMemo, 1)
	}
	for len(ifracMemo) <= n {
		size := len(ifracMemo)
		ifracMemo = append(ifracMemo, ifracMemo[size-1]*mpow(size, mod-2)%mod)
	}
	return ifracMemo[n]
}

func nCr(n, r int) int {
	if n == r {
		return 1
	}
	if n < r || r < 0 {
		return 0
	}
	ret := 1
	ret *= mfrac(n)
	ret %= mod
	ret *= mifrac(r)
	ret %= mod
	ret *= mifrac(n - r)
	ret %= mod
	return (ret)
}

func gcd(a, b int) int {
	for b != 0 {
		a, b = b, a%b
	}
	return a
}

type pair struct {
	f, s int
}

func main() {
	sc.Split(bufio.ScanWords)
	N := getInt()
	a := make([]int, N)
	b := make([]int, N)
	for i := 0; i < N; i++ {
		a[i], b[i] = getInt(), getInt()
	}

	zero := 0
	m := make(map[pair]pair)
	for i := 0; i < N; i++ {
		if a[i] == 0 && b[i] == 0 {
			zero++
			continue
		}
		g := gcd(abs(a[i]), abs(b[i]))
		a[i] /= g
		b[i] /= g
		if a[i] == 0 {
			x := m[pair{0, 1}]
			x.f++
			m[pair{0, 1}] = x
		} else if b[i] == 0 {
			x := m[pair{0, 1}]
			x.s++
			m[pair{0, 1}] = x
		} else if a[i] > 0 && b[i] > 0 {
			x := m[pair{a[i], b[i]}]
			x.f++
			m[pair{a[i], b[i]}] = x
		} else if a[i] < 0 && b[i] < 0 {
			a[i] = -a[i]
			b[i] = -b[i]
			x := m[pair{a[i], b[i]}]
			x.f++
			m[pair{a[i], b[i]}] = x
		} else if a[i] < 0 {
			a[i] = -a[i]
			b[i] = -b[i]
			x := m[pair{-b[i], a[i]}]
			x.s++
			m[pair{-b[i], a[i]}] = x
		} else {
			x := m[pair{-b[i], a[i]}]
			x.s++
			m[pair{-b[i], a[i]}] = x
		}
	}

	// out(zero)
	// out(m)
	// for i, v := range m {
	// 	out(i, v)
	// }
	ans := 1
	for _, v := range m {
		x := mpow(2, v.f)
		x += mpow(2, v.s)
		x %= mod
		x--
		if x < 0 {
			x += mod
		}
		x %= mod
		// out(x)
		ans *= x
		ans %= mod
	}
	ans += zero - 1
	if ans < 0 {
		ans += mod
	}
	ans %= mod
	out(ans)
}


package main

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"math"
	"os"
	"strconv"
)

func main() {
	sc := NewScanner(os.Stdin)
	n, _ := sc.NextInt()
	a, b := make([]int, n), make([]int, n)
	for i := 0; i < n; i++ {
		a[i], _ = sc.NextInt()
		b[i], _ = sc.NextInt()
	}
	ans := solve(n, a, b)
	fmt.Println(ans)
}

const mod = 1000000007

type pair struct {
	x, y int
}

func solve(n int, a, b []int) (ans uint) {
	zero := uint(0)
	count := map[pair]*pair{}
	for i := 0; i < n; i++ {
		x, y := a[i], b[i]
		if x == 0 && y == 0 {
			zero++
			continue
		}
		if y < 0 || y == 0 && x < 0 {
			x, y = rotate180(x, y)
		}
		rot90 := x <= 0
		if rot90 {
			x, y = rotate90(x, y)
		}
		g := gcd(x, y)
		x, y = x/g, y/g
		key := pair{x, y}
		if count[key] == nil {
			count[key] = &pair{}
		}
		if rot90 {
			count[key].y++
		} else {
			count[key].x++
		}
	}

	ans = 1
	for _, v := range count {
		c1, c2 := v.x, v.y
		now := uint(1)
		now = (now + ModPow(2, uint(c1), mod) - 1) % mod
		now = (now + ModPow(2, uint(c2), mod) - 1) % mod
		ans = ModMul(ans, now, mod)
	}
	if ans == 0 {
		panic("")
	}
	ans = (ans + zero - 1 + mod) % mod
	return ans
}

func rotate180(x1, y1 int) (x2, y2 int) {
	return -x1, -y1
}

func rotate90(x1, y1 int) (x2, y2 int) {
	return y1, -x1
}

func gcd(x, y int) int {
	if x < y {
		x, y = y, x
	}
	if y == 0 {
		return x
	}
	return gcd(y, x%y)
}

// Scanner is a wrapper of bufio.Scanner which is customized for competitive programing.
type Scanner struct {
	bufio.Scanner
}

// NewScanner is a constructor for Scanner.
func NewScanner(r io.Reader) *Scanner {
	sc := Scanner{*bufio.NewScanner(r)}
	sc.Buffer([]byte{}, math.MaxInt64)
	sc.Split(bufio.ScanWords)
	return &sc
}

// NextInt reads a integer from io stream.
func (sc *Scanner) NextInt() (int, error) {
	if !sc.Scan() {
		return -1, errors.New("failed to scan")
	}
	return strconv.Atoi(sc.Text())
}

// ModMul returns a*b with mod
func ModMul(a, b, mod uint) uint {
	if b == 0 {
		return 0
	}
	if a*b/b == a {
		return a * b % mod
	}
	panic("overflow")
}

// ModPow returns base^n with mod
func ModPow(base, n, mod uint) uint {
	ans := uint(1)
	for ; n > 0; n >>= 1 {
		if n&1 == 1 {
			ans = ModMul(ans, base, mod)
		}
		base = ModMul(base, base, mod)
	}
	return ans
}


package main

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"math"
	"os"
	"strconv"
)

func main() {
	sc := NewScanner(os.Stdin)
	n, _ := sc.NextInt()
	a, b := make([]int, n), make([]int, n)
	for i := 0; i < n; i++ {
		a[i], _ = sc.NextInt()
		b[i], _ = sc.NextInt()
	}
	ans := solve(n, a, b)
	fmt.Println(ans)
}

const mod = 1000000007

type pair struct {
	x, y int
}

func solve(n int, a, b []int) (ans uint) {
	zero := uint(0)
	count := map[pair]*pair{}
	for i := 0; i < n; i++ {
		x, y := a[i], b[i]
		if x == 0 && y == 0 {
			zero++
			continue
		}
		if y < 0 || y == 0 && x < 0 {
			x, y = rotate180(x, y)
		}
		rot90 := x <= 0
		if rot90 {
			x, y = rotate90(x, y)
		}
		g := gcd(x, y)
		x, y = x/g, y/g
		key := pair{x, y}
		if count[key] == nil {
			count[key] = &pair{}
		}
		if rot90 {
			count[key].y++
		} else {
			count[key].x++
		}
	}

	ans = 1
	for _, v := range count {
		c1, c2 := v.x, v.y
		now := uint(1)
		now = (now + ModPow(2, uint(c1), mod) - 1) % mod
		now = (now + ModPow(2, uint(c2), mod) - 1) % mod
		ans = ModMul(ans, now, mod)
	}
	ans = (ans + zero - 1) % mod
	return ans
}

func rotate180(x1, y1 int) (x2, y2 int) {
	return -x1, -y1
}

func rotate90(x1, y1 int) (x2, y2 int) {
	return y1, -x1
}

func gcd(x, y int) int {
	if x < y {
		x, y = y, x
	}
	if y == 0 {
		return x
	}
	return gcd(y, x%y)
}

// ModMul returns a*b with mod
func ModMul(a, b, mod uint) uint {
	if b == 0 {
		return 0
	}
	if a*b/b == a {
		return a * b % mod
	}
	panic("overflow")
}

// ModPow returns base^n with mod
func ModPow(base, n, mod uint) uint {
	ans := uint(1)
	for ; n > 0; n >>= 1 {
		if n&1 == 1 {
			ans = ModMul(ans, base, mod)
		}
		base = ModMul(base, base, mod)
	}
	return ans
}

// Scanner is a wrapper of bufio.Scanner which is customized for competitive programing.
type Scanner struct {
	bufio.Scanner
}

// NewScanner is a constructor for Scanner.
func NewScanner(r io.Reader) *Scanner {
	sc := Scanner{*bufio.NewScanner(r)}
	sc.Buffer([]byte{}, math.MaxInt64)
	sc.Split(bufio.ScanWords)
	return &sc
}

// NextInt reads a integer from io stream.
func (sc *Scanner) NextInt() (int, error) {
	if !sc.Scan() {
		return -1, errors.New("failed to scan")
	}
	return strconv.Atoi(sc.Text())
}


package main

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"math"
	"os"
	"strconv"
)

func main() {
	sc := NewScanner(os.Stdin)
	n, _ := sc.NextInt()
	a, b := make([]int, n), make([]int, n)
	for i := 0; i < n; i++ {
		a[i], _ = sc.NextInt()
		b[i], _ = sc.NextInt()
	}
	ans := solve(n, a, b)
	fmt.Println(ans)
}

const mod = 1000000007

type pair struct {
	x, y int
}

func solve(n int, a, b []int) (ans int) {
	zero := 0
	count := map[pair]*pair{}
	for i := 0; i < n; i++ {
		x, y := a[i], b[i]
		if x == 0 && y == 0 {
			zero++
			continue
		}
		if y < 0 || y == 0 && x < 0 {
			x, y = rotate180(x, y)
		}
		rot90 := x <= 0
		if rot90 {
			x, y = rotate90(x, y)
		}
		g := gcd(x, y)
		x, y = x/g, y/g
		key := pair{x, y}
		if count[key] == nil {
			count[key] = &pair{}
		}
		if rot90 {
			count[key].y++
		} else {
			count[key].x++
		}
	}

	ans = 1
	for _, v := range count {
		c1, c2 := v.x, v.y
		now := 1
		now = (now + ModPow(2, c1, mod) - 1) % mod
		now = (now + ModPow(2, c2, mod) - 1) % mod
		ans = ModMul(ans, now, mod)
	}
	ans = (ans + zero - 1) % mod
	return ans
}

func rotate180(x1, y1 int) (x2, y2 int) {
	return -x1, -y1
}

func rotate90(x1, y1 int) (x2, y2 int) {
	return y1, -x1
}

func gcd(x, y int) int {
	if x < y {
		x, y = y, x
	}
	if y == 0 {
		return x
	}
	return gcd(y, x%y)
}

// ModMul returns a*b with mod
func ModMul(a, b, mod int) int {
	ua, ub, umod := uint(a), uint(b), uint(mod)
	if ub == 0 {
		return 0
	}
	if ua*ub/ub == ua {
		return int(ua * ub % umod)
	}
	panic("overflow")
}

// ModPow returns base^n with mod
func ModPow(base, n, mod int) int {
	ans := 1
	for ; n > 0; n >>= 1 {
		if n&1 == 1 {
			ans = ModMul(ans, base, mod)
		}
		base = ModMul(base, base, mod)
	}
	return ans
}

// Scanner is a wrapper of bufio.Scanner which is customized for competitive programing.
type Scanner struct {
	bufio.Scanner
}

// NewScanner is a constructor for Scanner.
func NewScanner(r io.Reader) *Scanner {
	sc := Scanner{*bufio.NewScanner(r)}
	sc.Buffer([]byte{}, math.MaxInt64)
	sc.Split(bufio.ScanWords)
	return &sc
}

// NextInt reads a integer from io stream.
func (sc *Scanner) NextInt() (int, error) {
	if !sc.Scan() {
		return -1, errors.New("failed to scan")
	}
	return strconv.Atoi(sc.Text())
}


mod = 1_000_000_007

gets

h = Hash.new{|h, k| h[k] = [0, 0] }
genten_iwashi = 0

$<.read.split.map(&:to_i).each_slice(2) do |(a,b)|

  if 0 == a | b
    genten_iwashi += 1
  else
    f = a.zero? || (a > 0 && b > 0) || (a < 0 && b < 0)
    r = ( f ? Rational(a,b) : -Rational(b,a) )
    h[r][ f ? 0 : 1 ] += 1
  end
end

ans = 1
heiwa_iwashi = 0
h.values.each do |(s, t)|
  if s == 0 || t == 0
    heiwa_iwashi += (s + t)
  else
    ans *= ( 2.pow(s,mod) + 2.pow(t, mod) - 1)
    ans %= mod
  end
end
ans = ( ans * 2.pow(heiwa_iwashi, mod) ) % mod
ans = ( ans - 1 + genten_iwashi ) % mod

puts ans


class ExponentiationTable

  def initialize(x, mod = nil )
    @x = x
    @mod = mod
    @table = [x]
  end

  def pow(n)
    res = 1
    i = 0
    while n > 0
      if (n & 1) != 0
        res *= @table[i]
        res %= @mod if @mod
      end
      i += 1
      @table[i] ||= ( @mod ? @table[i-1] ** 2 % @mod : @table[i-1] ** 2 )
      n >>= 1
    end
    # p @table
    res
  end
end

mod = 1_000_000_007

gets

h = Hash.new{|h, k| h[k] = [0, 0] }
genten_iwashi = 0

$<.read.split.map(&:to_i).each_slice(2) do |(a,b)|

  if 0 == a | b
    genten_iwashi += 1
  else
    f = a.zero? || (a > 0 && b > 0) || (a < 0 && b < 0)
    r = ( f ? Rational(a,b) : -Rational(b,a) )
    h[r][ f ? 0 : 1 ] += 1
  end
end

ans = 1
heiwa_iwashi = 0
two = ExponentiationTable.new(2, mod)

h.values.each do |(s, t)|
  if s == 0 || t == 0
    heiwa_iwashi += (s + t)
  else
    ans *= ( two.pow(s) + two.pow(t) - 1)
    ans %= mod
  end
end
ans = ( ans * two.pow(heiwa_iwashi) ) % mod
ans = ( ans - 1 + genten_iwashi ) % mod

puts ans


class ExponentiationTable

  def initialize(x, mod = nil )
    @x = x
    @mod = mod
    @table = [x]
  end

  def pow(n)
    res = 1
    i = 0
    while n > 0
      if (n & 1) != 0
        res *= @table[i]
        res %= @mod if @mod
      end
      i += 1
      @table[i] ||= ( @table[i-1] ** 2)
      @table[i] %= @mod if @mod
      n >>= 1
    end
    # p @table
    res
  end
end

mod = 1_000_000_007

gets

h = Hash.new{|h, k| h[k] = [0, 0] }
genten_iwashi = 0

$<.read.split.map(&:to_i).each_slice(2) do |(a,b)|

  if 0 == a | b
    genten_iwashi += 1
  else
    f = a.zero? || (a > 0 && b > 0) || (a < 0 && b < 0)
    r = ( f ? Rational(a,b) : -Rational(b,a) )
    h[r][ f ? 0 : 1 ] += 1
  end
end

ans = 1
heiwa_iwashi = 0
two = ExponentiationTable.new(2, mod)

h.values.each do |(s, t)|
  if s == 0 || t == 0
    heiwa_iwashi += (s + t)
  else
    ans *= ( two.pow(s) + two.pow(t) - 1)
    ans %= mod
  end
end
ans = ( ans * two.pow(heiwa_iwashi) ) % mod
ans = ( ans - 1 + genten_iwashi ) % mod

puts ans


MOD = 10 ** 9 + 7
N = gets.to_i
A = []
B = []
R = Hash.new { |h, k| h[k] = [] }

N.times do |i|
  a, b = gets.split.map(&:to_i)

  A << a
  B << b

  if b == 0
    R[Float::INFINITY] << i
  else
    R[Rational(a, b)] << i
  end
end

E = Hash.new { |h, k| h[k] = [] }

N.times do |u|
  a = A[u]
  b = B[u]

  r = (b == 0) ? Float::INFINITY : -Rational(b, a)

  R[r].each do |v|
    E[u] << v
  end
end

tree_id = 0
tree_ids = Hash.new
nums = []

N.times do |i|
  next if tree_ids[i]

  queue = []
  queue << [i, 0]
  colors = Hash.new(0)

  until queue.empty?
    v, c = queue.shift

    next if tree_ids[v]
    tree_ids[v] = tree_id
    colors[c] += 1

    E[v].each do |u|
      queue << [u, (c + 1) % 2]
    end
  end

  cnt = 0

  colors.each_value do |n|
    cnt += n * (n + 1) / 2
  end

  nums << cnt + 1

  tree_id += 1
end

ans = 1

nums.each do |n|
  ans *= n
  ans %= MOD
end

puts ans - 1


MOD = 10 ** 9 + 7
N = gets.to_i
A = []
B = []
R = Hash.new { |h, k| h[k] = [] }

N.times do |i|
  a, b = gets.split.map(&:to_i)

  A << a
  B << b

  if b == 0
    R[0] << i
  else
    R[Rational(a, b)] << i
  end
end

E = Hash.new { |h, k| h[k] = [] }

N.times do |u|
  a = A[u]
  b = B[u]

  R[-Rational(b, a)].each do |v|
    E[u] << v
  end
end

tree_id = 0
tree_ids = Hash.new
nums = []

N.times do |i|
  next if tree_ids[i]

  queue = []
  queue << [i, 0]
  colors = Hash.new(0)

  until queue.empty?
    v, c = queue.shift

    next if tree_ids[v]
    tree_ids[v] = tree_id
    colors[c] += 1

    E[v].each do |u|
      queue << [u, (c + 1) % 2]
    end
  end

  cnt = 0

  colors.each_value do |n|
    cnt += n * (n + 1) / 2
  end

  nums << cnt + 1

  tree_id += 1
end

ans = 1

nums.each do |n|
  ans *= n
  ans %= MOD
end

puts ans - 1


MOD = 10 ** 9 + 7
N = gets.to_i
A = []
B = []
R = Hash.new { |h, k| h[k] = [] }

N.times do |i|
  a, b = gets.split.map(&:to_i)

  A << a
  B << b

  R[Rational(a, b)] << i
end

E = Hash.new { |h, k| h[k] = [] }

N.times do |u|
  a = A[u]
  b = B[u]

  R[-Rational(b, a)].each do |v|
    E[u] << v
  end
end

tree_id = 0
tree_ids = Hash.new
nums = []

N.times do |i|
  next if tree_ids[i]

  queue = []
  queue << [i, 0]
  colors = Hash.new(0)

  until queue.empty?
    v, c = queue.shift

    next if tree_ids[v]
    tree_ids[v] = tree_id
    colors[c] += 1

    E[v].each do |u|
      queue << [u, (c + 1) % 2]
    end
  end

  cnt = 0

  colors.each_value do |n|
    cnt += n * (n + 1) / 2
  end

  nums << cnt + 1

  tree_id += 1
end

ans = 1

nums.each do |n|
  ans *= n
  ans %= MOD
end

puts ans - 1


use std::io::Read;
use std::collections::HashMap;
use num_integer::gcd_lcm;

fn main() {
    let mut buf = String::new();
    std::io::stdin().read_to_string(&mut buf).unwrap();

    let answer = solve(&buf);

    println!("{}", answer);
}

fn solve(input: &str) -> String {
    let mut iterator = input.split_whitespace();

    let n: usize = iterator.next().unwrap().parse().unwrap();
    let mut zero: usize = 0;
    let mut mp: HashMap<(isize, isize), (isize, isize)> = HashMap::new();
    for _ in 0..n {
        let mut x: isize = iterator.next().unwrap().parse().unwrap();
        let mut y: isize = iterator.next().unwrap().parse().unwrap();
        if x == 0 && y == 0 {
            zero += 1;
            continue;
        }
        let g: isize = gcd_lcm(x, y).0;
        x /= g;
        y /= g;
        if y < 0 {
            x = -x;
            y = -y;
        }
        if y == 0 && x < 0 {
            x = -x;
            y = -y;
        }
        let rot90: bool = x <= 0;
        if rot90 {
            let tmp = y;
            y = -x;
            x = tmp;
        }
        if rot90 {
            (*mp.entry((x, y)).or_insert((0, 0))).0 += 1;
        } else {
            (*mp.entry((x, y)).or_insert((0, 0))).1 += 1;
        }
    }

    let mut ans: usize = 1;
    for x in mp {
        let s = (x.1).0;
        let t = (x.1).1;
        let mut now = 1;
        now += mod_pow(2, s as usize) - 1;
        now %= MOD;
        now += mod_pow(2, t as usize) - 1;
        now %= MOD;
        ans = ans * now % MOD;
    }
    if ans == 0 {
        ans = MOD - 1;
    } else {
        ans -= 1;
    }
    ans = ans + zero % MOD;
    return ans.to_string();

    // let mut signs: Vec<isize> = Vec::new();
    // let mut a: Vec<usize> = Vec::new();
    // let mut b: Vec<usize> = Vec::new();
    // for _ in 0..n {
    //     let mut sign = 1;
    //     let mut ai: isize = iterator.next().unwrap().parse().unwrap();
    //     if ai < 0 {
    //         sign *= -1;
    //         ai *= -1;
    //     }
    //     let mut bi: isize = iterator.next().unwrap().parse().unwrap();
    //     if bi < 0 {
    //         sign *= -1;
    //         bi *= -1;
    //     }
    //     a.push(ai as usize);
    //     b.push(bi as usize);
    //     signs.push(sign);
    // }
    //
    // let mut counts: HashMap<(isize, usize, usize), usize> = HashMap::new();
    // for i in 0..n {
    //     let g = gcd(a[i], b[i]);
    //     let p = (signs[i], a[i] / g, b[i] / g);
    //     *counts.entry(p).or_insert(0) += 1;
    // }
    // println!("{:?}", counts);
    //
    // let mc = ModComb::init(n);
    //
    // let mut ans: usize = 0;
    // for i in 0..n {
    //     ans = (ans + 1) % MOD;
    //     let g = gcd(a[i], b[i]);
    //     let d: usize= *counts.get(&(-signs[i], b[i] / g, a[i] / g)).unwrap_or(&0);
    //     println!("{} {}", i, d);
    //     for r in 2..=(n - d - 1) {
    //         ans = (ans + mc.calc(n - d - 1, r - 1)) % MOD;
    //     }
    // }
    // return ans.to_string();
}

const MOD: usize = 1_000_000_007;

fn mod_pow(a: usize, b: usize) -> usize {
    let mut result = 1;
    for _ in 0..b {
        result = result * a % MOD;
    }
    return result
}


const M:u64=1000000007;
fn pow(a:u64,b:u64)->u64{return if b!=0{if b%2==1{pow(a,b-1)*a%M}else{pow(a*a%M,b/2)}}else{1}}
fn main(){
    proconio::input!{
        n:usize,
        abs:[(i64,i64);n]
    }
    let mut o=M-1;
    let mut mp=Vec::new();
    for ab in abs{
        let (mut a,mut b)=ab;
        if a==0&&b==0{o+=1}
        else
        {
            let g=num::integer::gcd(a,b);
            a/=g;
            b/=g;
            if a<0||a==0&&b>0{a=-a;b=-b}
            let mut f=0;
            if b<0{
                let t=a;
                a=-b;
                b=t;
                f=1;
            }
            mp.push((a,b,f))
        }
    }
    mp.sort();
    let mut i=0usize;
    let mut ans=1;
    while i<mp.len(){
        let j=i;
        let mut x=0;
        let mut y=0;
        while i<mp.len()&&mp[j].0==mp[i].0&&mp[j].1==mp[i].1{
            if mp[i].2==0{x+=1}else{y+=1}
            i+=1
        }
        ans=ans*(pow(2,x)+pow(2,y)-1)%M;
    }
    println!("{}",(ans+o)%M)
}


const M:u64=1000000007;
fn main(){
    proconio::input!{
        n:usize,
        abs:[(i64,i64);n]
    }
    let mut o=M-1;
    let mut mp=Vec::new();
    for ab in abs{
        let (mut a,mut b)=ab;
        if a==0&&b==0{o+=1}
        else
        {
            let g=num::integer::gcd(a,b);
            a/=g;
            b/=g;
            if a<0||a==0&&b>0{a=-a;b=-b}
            let mut f=0;
            if b<0{
                let t=a;
                a=-b;
                b=t;
                f=1;
            }
            mp.push((a,b,f))
        }
    }
    mp.sort();
    let mut i=0usize;
    let mut ans=1;
    while i<mp.len(){
        let j=i;
        let mut x=1;
        let mut y=1;
        while i<mp.len()&&mp[j].0==mp[i].0&&mp[j].1==mp[i].1{
            if mp[i].2==0{x=x*2%M}else{y=y*2%M}
            i+=1
        }
        ans=ans*(x+y-1)%M;
    }
    println!("{}",(ans+o)%M)
}


#![allow(unused_imports)]
use {
    itertools::Itertools,
    proconio::{fastout, input, marker::*},
    std::cmp::*,
    std::collections::*,
    std::ops::*,
    std::io::Write,
};

#[allow(unused_macros)]
macro_rules !dbg {($($e :expr ) ,*) =>{#[cfg (debug_assertions ) ] $({let (e ,mut err ) =(stringify !($e ) ,std ::io ::stderr () ) ;writeln !(err ,"{} = {:?}" ,e ,$e ) .unwrap () } ) *} ;}
#[allow(unused_macros)]
macro_rules !min {($a :expr $(,) *) =>{{$a } } ;($a :expr ,$b :expr $(,) *) =>{{std ::cmp ::min ($a ,$b ) } } ;($a :expr ,$($rest :expr ) ,+$(,) *) =>{{std ::cmp ::min ($a ,min !($($rest ) ,+) ) } } ;}
#[allow(unused_macros)]
macro_rules !chmin {($base :expr ,$($cmps :expr ) ,+$(,) *) =>{{let cmp_min =min !($($cmps ) ,+) ;if $base >cmp_min {$base =cmp_min ;true } else {false } } } ;}

fn gcd(a: i64, b: i64) -> i64 {
    if b == 0 {
        a
    } else {
        gcd(b, a % b)
    }
}

#[fastout]
fn main() {
    input! {
         n: usize,
        ab: [(i64, i64); n],
    }

    let mut zz = 0;
    let mut za = 0;
    let mut zb = 0;
    let mut pos = std::collections::BTreeMap::<(i64, i64), (usize, usize)>::new();
    for &(a, b) in ab.iter() {
        if a == 0 && b == 0 {
            zz += 1;
            continue;
        }
        if a == 0 {
            za += 1;
            continue;
        }
        if b == 0 {
            zb += 1;
            continue;
        }
        if a * b > 0 {
            let a = a.abs();
            let b = b.abs();
            let m = gcd(a, b);
            pos.entry((a / m, b / m)).or_insert((0, 0)).0 += 1;
        } else {
            let a = a.abs();
            let b = b.abs();
            let m = gcd(a, b);
            pos.entry((b / m, a / m)).or_insert((0, 0)).1 += 1;
        }
    }

    let mut ans = Mint(1);
    ans *= Mint(2).pow(za) + Mint(2).pow(zb) - 1;
    for (k, &(x, y)) in pos.iter() {
        ans *= Mint(2).pow(x) + Mint(2).pow(y) - 1;
    }
    ans -= 1;
    ans += Mint(zz as i64);
    println!("{}", ans);
}

const MOD: i64 = 1000000007;
#[derive(Copy, Clone)]
pub struct Mint(i64);
impl Mint {
    fn new(x: i64) -> Self {
        Mint(x.rem_euclid(MOD))
    }
    fn pow(self, n: usize) -> Self {
        match n {
            0 => Mint::new(1),
            _ => {
                let mut a = self.pow(n >> 1);
                a *= a;
                if n & 1 == 1 {
                    a *= self;
                }
                a
            }
        }
    }
    fn inv(self) -> Self {
        self.pow((MOD - 2) as usize)
    }
}
impl std::ops::Neg for Mint {
    type Output = Mint;
    fn neg(self) -> Self::Output {
        Self::new(-self.0)
    }
}
impl std::ops::AddAssign<Mint> for Mint {
    fn add_assign(&mut self, rhs: Self) {
        self.0 += rhs.0;
        self.0 %= MOD;
    }
}
impl std::ops::AddAssign<i64> for Mint {
    fn add_assign(&mut self, rhs: i64) {
        *self += Mint::new(rhs);
    }
}
impl<T> std::ops::Add<T> for Mint
where
    Mint: std::ops::AddAssign<T>,
{
    type Output = Self;
    fn add(self, other: T) -> Self {
        let mut res = self;
        res += other;
        res
    }
}
impl std::ops::SubAssign<Mint> for Mint {
    fn sub_assign(&mut self, rhs: Self) {
        self.0 -= rhs.0;
        if self.0 < 0 {
            self.0 += MOD;
        }
    }
}
impl std::ops::SubAssign<i64> for Mint {
    fn sub_assign(&mut self, rhs: i64) {
        *self -= Mint::new(rhs);
    }
}
impl<T> std::ops::Sub<T> for Mint
where
    Mint: std::ops::SubAssign<T>,
{
    type Output = Self;
    fn sub(self, other: T) -> Self {
        let mut res = self;
        res -= other;
        res
    }
}
impl std::ops::MulAssign<Mint> for Mint {
    fn mul_assign(&mut self, rhs: Self) {
        self.0 *= rhs.0;
        self.0 %= MOD;
    }
}
impl std::ops::MulAssign<i64> for Mint {
    fn mul_assign(&mut self, rhs: i64) {
        *self *= Mint::new(rhs);
    }
}
impl<T> std::ops::Mul<T> for Mint
where
    Mint: std::ops::MulAssign<T>,
{
    type Output = Self;
    fn mul(self, other: T) -> Self {
        let mut res = self;
        res *= other;
        res
    }
}
impl std::ops::DivAssign<Mint> for Mint {
    fn div_assign(&mut self, rhs: Self) {
        *self *= rhs.inv();
    }
}
impl std::ops::DivAssign<i64> for Mint {
    fn div_assign(&mut self, rhs: i64) {
        *self /= Mint::new(rhs);
    }
}
impl<T> std::ops::Div<T> for Mint
where
    Mint: std::ops::DivAssign<T>,
{
    type Output = Self;
    fn div(self, other: T) -> Self {
        let mut res = self;
        res /= other;
        res
    }
}
impl std::fmt::Display for Mint {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}
impl std::ops::Deref for Mint {
    type Target = i64;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
impl std::ops::DerefMut for Mint {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}


use proconio::input;

#[allow(unused_imports)]
use proconio::marker::{Bytes, Chars, Usize1};
#[allow(unused_imports)]
use std::cmp::*;
#[allow(unused_imports)]
use std::collections::*;
#[allow(unused_imports)]
use std::ops::*;

#[derive(Clone, Debug, Default)]
struct Struct;

const M: i64 = 1000000007;

fn gcd(a: i64, b: i64) -> i64 {
    if a < b {
        gcd(b, a)
    } else {
        let k = a / b;
        if a - k * b == 0 {
            b
        } else {
            gcd(b, a - k * b)
        }
    }
}

//
// ModInt
//

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct ModInt {
    val: i64,
}
#[allow(dead_code)]
const M0: ModInt = ModInt { val: 0 };
const M1: ModInt = ModInt { val: 1 };
//const M_ERROR: ModInt = ModInt { val: -1 };

impl ModInt {
    fn new(v: i64) -> ModInt {
        ModInt {
            val: ModInt::val_from_i64(v),
        }
    }
    fn val_from_i64(v: i64) -> i64 {
        let t = v % M;
        if t < 0 {
            t + M
        } else {
            t
        }
    }
    fn val_from_addval(t: i64) -> i64 {
        if t >= M {
            t - M
        } else {
            t
        }
    }

    fn pow(self, exp: i64) -> ModInt {
        if exp == 0 {
            M1
        } else {
            let x = self.pow(exp / 2);
            if exp % 2 == 0 {
                x * x
            } else {
                self * x * x
            }
        }
    }

    fn inv(self) -> ModInt {
        self.pow(M - 2)
    }
}

impl std::ops::Add for ModInt {
    type Output = ModInt;
    fn add(self, rhs: ModInt) -> ModInt {
        ModInt {
            val: ModInt::val_from_addval(self.val + rhs.val),
        }
    }
}
impl std::ops::Add<i64> for ModInt {
    type Output = ModInt;
    fn add(self, rhs: i64) -> ModInt {
        ModInt {
            val: ModInt::val_from_i64(self.val + rhs),
        }
    }
}
impl std::ops::AddAssign for ModInt {
    fn add_assign(&mut self, other: ModInt) {
        self.val = ModInt::val_from_addval(self.val + other.val);
    }
}
impl std::ops::AddAssign<i64> for ModInt {
    fn add_assign(&mut self, other: i64) {
        self.val = ModInt::val_from_i64(self.val + other);
    }
}

impl std::ops::Sub for ModInt {
    type Output = ModInt;
    fn sub(self, rhs: ModInt) -> ModInt {
        ModInt {
            val: ModInt::val_from_addval(self.val - rhs.val + M),
        }
    }
}
impl std::ops::Sub<i64> for ModInt {
    type Output = ModInt;
    fn sub(self, rhs: i64) -> ModInt {
        self - ModInt::new(rhs)
    }
}
impl std::ops::SubAssign for ModInt {
    fn sub_assign(&mut self, other: ModInt) {
        self.val = ModInt::val_from_addval(self.val - other.val + M);
    }
}
impl std::ops::SubAssign<i64> for ModInt {
    fn sub_assign(&mut self, other: i64) {
        self.val = ModInt::val_from_i64(self.val - other);
    }
}

impl std::ops::Neg for ModInt {
    type Output = ModInt;
    fn neg(self) -> ModInt {
        ModInt {
            val: if self.val == 0 { 0 } else { M - self.val },
        }
    }
}

impl std::ops::Mul for ModInt {
    type Output = ModInt;
    fn mul(self, rhs: ModInt) -> ModInt {
        ModInt {
            val: self.val * rhs.val % M,
        }
    }
}
impl std::ops::Mul<i64> for ModInt {
    type Output = ModInt;
    fn mul(self, rhs: i64) -> ModInt {
        self * ModInt::new(rhs)
    }
}
impl std::ops::MulAssign for ModInt {
    fn mul_assign(&mut self, other: ModInt) {
        self.val = self.val * other.val % M;
    }
}
impl std::ops::MulAssign<i64> for ModInt {
    fn mul_assign(&mut self, other: i64) {
        self.val = self.val * (other % M) % M;
    }
}

impl std::ops::Div for ModInt {
    type Output = ModInt;
    fn div(self, rhs: ModInt) -> ModInt {
        self * rhs.inv()
    }
}
impl std::ops::Div<i64> for ModInt {
    type Output = ModInt;
    fn div(self, rhs: i64) -> ModInt {
        self / ModInt::new(rhs)
    }
}

impl std::fmt::Display for ModInt {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.val)
    }
}

//#[proconio::fastout]
fn main() {
    input! {
        n:usize,
        v:[(i64,i64);n],
    }

    // (x,y) と (-ay,ax) は仲が悪い
    let mut m: BTreeMap<(i64, i64), i64> = BTreeMap::new();
    let mut ans_bad_iwasi = 0;
    for (a, b) in &v {
        let k;
        if *a == 0 && *b == 0 {
            ans_bad_iwasi = 1;
            continue;
        } else if *a == 0 {
            k = (0, 1);
        } else if *b == 0 {
            k = (1, 0);
        } else {
            let g = gcd((*a).abs(), (*b).abs());
            let neg = if *a < 0 { -1 } else { 1 };
            k = (*a / g * neg, *b / g * neg);
        }
        m.entry(k).and_modify(|e| *e += 1).or_insert(1);
    }

    let mut ans = M1;
    let m2 = ModInt::new(2);

    while !m.is_empty() {
        if let Some((k, ct)) = m.iter().next() {
            let (k, ct) = (*k, *ct);
            let anti_k = if k.1 < 0 { (-k.1, k.0) } else { (k.1, -k.0) };
            if let Some(anti_ct) = m.get(&anti_k) {
                ans *= m2.pow(ct) + m2.pow(*anti_ct) - 1;
                m.remove(&anti_k);
            } else {
                ans *= m2.pow(ct);
            }
            m.remove(&k);
        }
    }

    println!("{}", ans - 1 + ans_bad_iwasi);
}


use proconio::{fastout, input};

#[allow(unused_imports)]
use std::cmp::*;
#[allow(unused_imports)]
use std::collections::*;

const MOD: usize = 1e9 as usize + 7;

// mod p で x^n
fn repeated_squaring(x: usize, n: usize, p: usize) -> usize {
    if n == 0 {
        1
    } else if n % 2 == 0 {
        repeated_squaring(x * x % p, n / 2, p)
    } else {
        (x * repeated_squaring(x, n - 1, p)) % p
    }
}

#[fastout]
fn main() {
    input! {
        n: usize,
        ab: [(isize, isize); n],
    }

    let gcd = num::integer::gcd;

    let mut hash: HashMap<(isize, isize), (usize, usize)> = HashMap::new();

    let mut zero = 0;
    for (a, b) in &ab {
        if *a == 0 && *b == 0 {
            zero += 1;
        } else {
            let c = gcd(*a, *b);
            let mut aa: isize = (*a).abs() / c;
            let mut bb: isize = (*b).abs() / c;

            let mut plus = true;
            if (*a < 0 && *b > 0) || (*a > 0 && *b < 0) {
                plus = false;
                let tmp = aa;
                aa = bb;
                bb = tmp;
            }

            if !hash.contains_key(&(aa, bb)) {
                if plus {
                    hash.insert((aa, bb), (1, 0));
                } else {
                    hash.insert((aa, bb), (0, 1));
                }
            } else {
                let (pi, ni) = hash[&(aa, bb)];
                if plus {
                    hash.insert((aa, bb), (pi + 1, ni));
                } else {
                    hash.insert((aa, bb), (pi, ni + 1));
                }
            }
        }
    }

    let mut ans = 1;
    for (_, v) in &hash {
        ans *= repeated_squaring(2, (*v).0, MOD) + repeated_squaring(2, (*v).1, MOD) - 1;
        ans %= MOD;
    }

    println!("{}", ans + zero - 1);
}


#if os(Linux)
import Glibc
#else
import Darwin.C
#endif

let INPUT = stdin

func scanInts(count: Int) -> [Int] {
    let format: String
    switch count {
        case 1: format = "%lld"
        case 2: format = "%lld %lld"
        case 3: format = "%lld %lld %lld"
        case 4: format = "%lld %lld %lld %lld"
        case 5: format = "%lld %lld %lld %lld %lld"
        case 6: format = "%lld %lld %lld %lld %lld %lld"
        default: format = repeatElement("%lld", count: count).joined(separator: " ")
    }
    let ptr = UnsafeMutablePointer<CLongLong>.allocate(capacity: count)
    let ptrArray = Array(ptr ..< ptr+count)
    let read = withVaList(ptrArray) {
        vfscanf(INPUT, format, $0)
    }
    assert(read == count)
    return ptrArray.map { Int($0.pointee) }
}
func scanInt() -> Int {
    scanInts(count: 1)[0]
}
func scanIntPair() -> (Int, Int) {
    let a: [Int] = scanInts(count: 2)
    return (a[0], a[1])
}

//=============================================================================

func gcd(_ a: Int, _ b: Int) -> Int {
    b == 0 ? abs(a) : gcd(b, a%b)
}

struct Fraction: Equatable, Hashable {
    let numerator, denominator: Int
    init (_ numerator: Int, _ denominator: Int) {
        switch (numerator, denominator) {
        case (0, 0):
            self.numerator = 0
            self.denominator = 0
        case (0, _):
            self.numerator = 0
            self.denominator = 1
        case (_, 0):
            self.numerator = 1
            self.denominator = 0
        default:
            let g = gcd(numerator, denominator)
            self.numerator = denominator.signum() * numerator / g
            self.denominator = abs(denominator) / g
        }
    }
    init (_ tuple : (Int, Int)) {
        self.init(tuple.0, tuple.1)
    }
    func perpendicular() -> Fraction {
        Fraction(-denominator, numerator)
    }
}

func pow(_ x: Int, _ n: Int, mod: Int) -> Int {
    precondition(n >= 0 && mod > 1)
    if n == 0 { return 1 }

    var u = pow(x, n/2, mod: mod)
    u = u*u % mod
    if n%2 == 1 { u = u*x % mod }
    return u
}

//=============================================================================

let MOD = Int(1e9+7)

let N = scanInt()
var cliques: [Fraction: Int] = [:]
var zeroes = 0
for _ in 1...N {
    let f = Fraction(scanIntPair())
    if f == Fraction(0, 0) {
        zeroes += 1
    } else {
        cliques[f, default: 0] += 1
    }
}

var ways = 1
while let (k1, v1) = cliques.first {
    cliques.removeValue(forKey: k1)
    let k2 = k1.perpendicular()
    let v2 = cliques.removeValue(forKey: k2)

    ways *= pow(2, v1, mod: MOD) + (v2 != nil ? pow(2, v2!, mod: MOD) - 1 : 0)
    ways %= MOD
}
ways += MOD - 1
ways += zeroes
ways %= MOD
print(ways)


func gcd(_ a: Int, _ b: Int) -> Int {
    b == 0 ? abs(a) : gcd(b, a%b)
}

struct Fraction: Equatable, Hashable {
    let numerator, denominator: Int
    init (_ numerator: Int, _ denominator: Int) {
        switch (numerator, denominator) {
        case (0, 0):
            self.numerator = 0
            self.denominator = 0
        case (0, _):
            self.numerator = 0
            self.denominator = 1
        case (_, 0):
            self.numerator = 1
            self.denominator = 0
        default:
            let g = gcd(numerator, denominator)
            self.numerator = denominator.signum() * numerator / g
            self.denominator = abs(denominator) / g
        }
    }
    func perpendicular() -> Fraction {
        Fraction(-denominator, numerator)
    }
}

func pow(_ x: Int, _ n: Int, mod: Int) -> Int {
    precondition(n >= 0 && mod > 1)
    if n == 0 { return 1 }

    var u = pow(x, n/2, mod: mod)
    u = u*u % mod
    if n%2 == 1 { u = u*x % mod }
    return u
}
///////////////////////////////////////////////////////////////////////////////

let MOD = Int(1e9+7)

let N = Int(readLine()!)!
var cliques: [Fraction: Int] = [:]
var zeroes = 0
for _ in 1...N {
    let line = readLine()!.split(separator: " ").map { Int($0)! }
    let f = Fraction(line[0], line[1])
    if f == Fraction(0, 0) {
        zeroes += 1
    } else {
        cliques[f, default: 0] += 1
    }
}

var ways = 1
while let (k1, v1) = cliques.first {
    cliques.removeValue(forKey: k1)
    let k2 = k1.perpendicular()
    let v2 = cliques.removeValue(forKey: k2)

    ways *= pow(2, v1, mod: MOD) + (v2 != nil ? pow(2, v2!, mod: MOD) - 1 : 0)
    ways %= MOD
}
ways += MOD - 1
ways += zeroes
ways %= MOD
print(ways)


func gcd(_ a: Int, _ b: Int) -> Int {
    b == 0 ? abs(a) : gcd(b, a%b)
}

struct Fraction: Equatable, Hashable {
    let numerator, denominator: Int
    init (_ numerator: Int, _ denominator: Int) {
        switch (numerator, denominator) {
        case (0, 0):
            self.numerator = 0
            self.denominator = 0
        case (0, _):
            self.numerator = 0
            self.denominator = 1
        case (_, 0):
            self.numerator = 1
            self.denominator = 0
        default:
            let g = gcd(numerator, denominator)
            self.numerator = denominator.signum() * numerator / g
            self.denominator = abs(denominator) / g
        }
    }
    func perpendicular() -> Fraction {
        Fraction(-denominator, numerator)
    }
}

func pow(_ x: Int, _ n: Int, mod: Int) -> Int {
    precondition(n >= 0 && mod > 1)
    if n == 0 { return 1 }

    var u = pow(x, n/2, mod: mod)
    u = u &* u % mod
    if n%2 == 1 { u = u &* x % mod }
    return u
}
///////////////////////////////////////////////////////////////////////////////

let MOD = Int(1e9+7)

let N = Int(readLine()!)!
var cliques: [Fraction: Int] = [:]
var zeroes = 0
for _ in 1...N {
    let line = readLine()!.split(separator: " ").map { Int($0)! }
    let f = Fraction(line[0], line[1])
    if f == Fraction(0, 0) {
        zeroes += 1
    } else {
        cliques[f, default: 0] += 1
    }
}

var ways = 1
while let (k1, v1) = cliques.first {
    cliques.removeValue(forKey: k1)
    let k2 = k1.perpendicular()
    let v2 = cliques.removeValue(forKey: k2)

    ways *= pow(2, v1, mod: MOD) + (v2 != nil ? pow(2, v2!, mod: MOD) - 1 : 0)
    ways %= MOD
}
ways += MOD - 1
ways += zeroes
ways %= MOD
print(ways)


#if os(Linux)
import Glibc
#else
import Darwin.C
#endif

let INPUT_SIZE = 67_108_864
func readLines() -> [Substring] {
    let ptr = UnsafeMutablePointer<UInt8>.allocate(capacity: INPUT_SIZE+1)
    let n = fread(ptr, 1, INPUT_SIZE, stdin)
    ptr[n] = 0
    return String(cString: ptr).split(separator: "\n")
}
var input = readLines().makeIterator()

//=============================================================================

func gcd(_ a: Int, _ b: Int) -> Int {
    b == 0 ? abs(a) : gcd(b, a%b)
}

struct Fraction: Equatable, Hashable {
    let numerator, denominator: Int
    init (_ numerator: Int, _ denominator: Int) {
        switch (numerator, denominator) {
        case (0, 0):
            self.numerator = 0
            self.denominator = 0
        case (0, _):
            self.numerator = 0
            self.denominator = 1
        case (_, 0):
            self.numerator = 1
            self.denominator = 0
        default:
            let g = gcd(numerator, denominator)
            self.numerator = denominator.signum() * numerator / g
            self.denominator = abs(denominator) / g
        }
    }
    func perpendicular() -> Fraction {
        Fraction(-denominator, numerator)
    }
}

func pow(_ x: Int, _ n: Int, mod: Int) -> Int {
    precondition(n >= 0 && mod > 1)
    if n == 0 { return 1 }

    var u = pow(x, n/2, mod: mod)
    u = u*u % mod
    if n%2 == 1 { u = u*x % mod }
    return u
}

//=============================================================================

let MOD = Int(1e9+7)

let N = Int(input.next()!)!
var cliques: [Fraction: Int] = [:]
var zeroes = 0
for _ in 1...N {
    let line = input.next()!.split(separator: " ").map { Int($0)! }
    let f = Fraction(line[0], line[1])
    if f == Fraction(0, 0) {
        zeroes += 1
    } else {
        cliques[f, default: 0] += 1
    }
}

var ways = 1
while let (k1, v1) = cliques.first {
    cliques.removeValue(forKey: k1)
    let k2 = k1.perpendicular()
    let v2 = cliques.removeValue(forKey: k2)

    ways *= pow(2, v1, mod: MOD) + (v2 != nil ? pow(2, v2!, mod: MOD) - 1 : 0)
    ways %= MOD
}
ways += MOD - 1
ways += zeroes
ways %= MOD
print(ways)


func gcd(_ a: Int, _ b: Int) -> Int {
    b == 0 ? abs(a) : gcd(b, a%b)
}

struct Fraction: Equatable, Hashable {
    let numerator, denominator: Int
    init (_ numerator: Int, _ denominator: Int) {
        switch (numerator, denominator) {
        case (0, 0):
            self.numerator = 0
            self.denominator = 0
        case (0, _):
            self.numerator = 0
            self.denominator = 1
        case (_, 0):
            self.numerator = 1
            self.denominator = 0
        default:
            let g = gcd(numerator, denominator)
            self.numerator = denominator.signum() * numerator / g
            self.denominator = abs(denominator) / g
        }
    }
    func perpendicular() -> Fraction {
        Fraction(-denominator, numerator)
    }
}

func pow(_ x: Int, _ n: Int, mod: Int) -> Int {
    precondition(n >= 0 && mod > 1)
    if n == 0 { return 1 }

    var u = pow(x, n/2, mod: mod)
    u = u*u % mod
    if n%2 == 1 { u = u*x % mod }
    return u
}
///////////////////////////////////////////////////////////////////////////////

let MOD = Int(1e9+7)

let N = Int(readLine()!)!
var cliques: [Fraction: Int] = [:]
var zeroes = 0
for _ in 1...N {
    let line = readLine()!.split(separator: " ").map { Int($0)! }
    let f = Fraction(line[0], line[1])
    if f == Fraction(0, 0) {
        zeroes += 1
    } else {
        cliques[f, default: 0] += 1
    }
}

var ways = 1
while let (k1, v1) = cliques.first {
    cliques.removeValue(forKey: k1)
    let k2 = k1.perpendicular()
    let v2 = cliques.removeValue(forKey: k2)

    ways *= pow(2, v1, mod: MOD) + (v2 != nil ? pow(2, v2!, mod: MOD) - 1 : 0)
    ways %= MOD
}
ways -= 1
ways += zeroes
ways %= MOD
print(ways)


import * as fs from 'fs';

const readString = (() => {
  const input = fs.readFileSync(0).toString();
  const words = input.split(/[ \n]/);
  let i = 0;
  return () => words[i++] || '';
})();
const readInt = () => parseInt(readString(), 10);
const readBigInt = () => BigInt(readString());

const gcd = (a: bigint, b: bigint) => {
  const _gcd = (a: bigint, b: bigint): bigint => {
    const q = a % b;
    return q === 0n ? b : _gcd(b, q);
  };
  return a > b ? _gcd(a, b) : _gcd(b, a);
};

const abs = (a: bigint) => a > 0n ? a : -a;

(function main() {
  const N = readInt();

  let zero: number = 0;
  const groups: { [k: string]: [number, number] } = {};
  Array.from({ length: N }, () => {
    let A = readBigInt();
    let B = readBigInt();
    if (A === 0n && B === 0n) {
      zero++;
    } else if (A === 0n) {
      groups['0:1'] = groups['0:1'] || [0, 0];
      groups['0:1'][0]++;
    } else if (B === 0n) {
      groups['0:1'] = groups['0:1'] || [0, 0];
      groups['0:1'][1]++;
    } else {
      const n = gcd(abs(A), abs(B));
      A /= n;
      B /= n;
      if (A < 0) {
        A *= -1n;
        B *= -1n;
      }

      if (B > 0) {
        const k = `${A}:${B}`;
        groups[k] = groups[k] || [0, 0];
        groups[k][0]++;
      } else {
        const k = `${-B}:${A}`;
        groups[k] = groups[k] || [0, 0];
        groups[k][1]++;
      }
    }
  });

  const mod = 1000000007;

  let result: bigint = BigInt(1);
  for (const k of Object.keys(groups)) {
    const [xn, yn] = groups[k];

    let tmp: number = 0;
    if (xn) {
      let xx = 1;
      for (let i = 0; i < xn; i++) {
        xx *= 2;
        if (xx > mod) xx %= mod;
      }
      xx -= 1;
      tmp += xx;
      if (tmp > mod) tmp %= mod;
    }

    if (yn) {
      let yy = 1;
      for (let i = 0; i < yn; i++) {
        yy *= 2;
        if (yy > mod) yy %= mod;
      }
      yy -= 1;
      tmp += yy;
      if (tmp > mod) tmp %= mod;
    }

    tmp++;
    if (tmp > mod) tmp %= mod;

    result *= BigInt(tmp);
    if (result > mod) result %= BigInt(mod);
  }

  result += BigInt(zero) + BigInt(mod - 1);
  if (result > mod) result %= BigInt(mod);

  console.log(result.toString());
})();


import * as fs from 'fs';

const readString = (() => {
  const input = fs.readFileSync(0).toString();
  const words = input.split(/[ \n]/);
  let i = 0;
  return () => words[i++] || '';
})();
const readInt = () => parseInt(readString(), 10);
const readBigInt = () => BigInt(readString());

const gcd = (a: bigint, b: bigint) => {
  const _gcd = (a: bigint, b: bigint): bigint => {
    const q = a % b;
    return q === 0n ? b : _gcd(b, q);
  };
  return a > b ? _gcd(a, b) : _gcd(b, a);
};

const MOD = 1000000007;
const MOD_BIGINT = BigInt(MOD);
class ModBigInt {
  constructor(public v: bigint) { }

  static of(n: number | bigint) {
    return new ModBigInt(ModBigInt.toBigInt(n));
  }

  static toBigInt(other: number | bigint | ModBigInt) {
    if (typeof other === 'bigint') return other;
    if (typeof other === 'number') return BigInt(other);
    return other.v;
  }

  clone = () => new ModBigInt(this.v);
  plus = (other: number | bigint | ModBigInt) => {
    this.v = (this.v + ModBigInt.toBigInt(other)) % MOD_BIGINT;
    return this;
  }
  minus = (other: number | bigint | ModBigInt) => {
    this.v = (this.v + MOD_BIGINT - ModBigInt.toBigInt(other)) % MOD_BIGINT;
    return this;
  }
  multiply = (other: number | bigint | ModBigInt) => {
    this.v = (this.v * ModBigInt.toBigInt(other)) % MOD_BIGINT;
    return this;
  }
  pow = (other: number) => {
    let v = mod(1);
    for (let i = 0; i < other; i++) v.multiply(this.v);
    return v;
  }
}

const mod = ModBigInt.of;

const abs = (a: bigint) => a > 0n ? a : -a;

(function main() {
  const N = readInt();

  let zero: number = 0;
  const groups: { [k: string]: [number, number] } = {};
  Array.from({ length: N }, () => {
    let A = readBigInt();
    let B = readBigInt();
    if (A === 0n && B === 0n) {
      zero++;
    } else if (A === 0n) {
      groups['0:1'] = groups['0:1'] || [0, 0];
      groups['0:1'][0]++;
    } else if (B === 0n) {
      groups['0:1'] = groups['0:1'] || [0, 0];
      groups['0:1'][1]++;
    } else {
      const n = gcd(abs(A), abs(B));
      A /= n;
      B /= n;
      if (A < 0) {
        A *= -1n;
        B *= -1n;
      }

      if (B > 0) {
        const k = `${A}:${B}`;
        groups[k] = groups[k] || [0, 0];
        groups[k][0]++;
      } else {
        const k = `${-B}:${A}`;
        groups[k] = groups[k] || [0, 0];
        groups[k][1]++;
      }
    }
  });

  let result = mod(1);
  for (const k of Object.keys(groups)) {
    const [xn, yn] = groups[k];

    let tmp = mod(0);
    if (xn) {
      tmp.plus(mod(2).pow(xn).minus(1));
    }

    if (yn) {
      tmp.plus(mod(2).pow(yn).minus(1));
    }

    tmp.plus(1);

    result.multiply(tmp);
  }

  result.plus(zero).minus(1);

  console.log(result.v.toString());
})();


import * as fs from 'fs';

const readString = (() => {
  const input = fs.readFileSync(0).toString();
  const words = input.split(/[ \n]/);
  let i = 0;
  return () => words[i++] || '';
})();
const readInt = () => parseInt(readString(), 10);
const readBigInt = () => BigInt(readString());

const gcd = (a: bigint, b: bigint) => {
  const _gcd = (a: bigint, b: bigint): bigint => {
    const q = a % b;
    return q === 0n ? b : _gcd(b, q);
  };
  return a > b ? _gcd(a, b) : _gcd(b, a);
};

const abs = (a: bigint) => a > 0n ? a : -a;

(function main() {
  const N = readInt();

  let zero: number = 0;
  const groups: { [k: string]: [number, number] } = {};
  Array.from({ length: N }, () => {
    let A = readBigInt();
    let B = readBigInt();
    if (A === 0n && B === 0n) {
      zero++;
    } else if (A === 0n) {
      groups['0:1'] = groups['0:1'] || [0, 0];
      groups['0:1'][0]++;
    } else if (B === 0n) {
      groups['0:1'] = groups['0:1'] || [0, 0];
      groups['0:1'][1]++;
    } else {
      const n = gcd(abs(A), abs(B));
      A /= n;
      B /= n;
      if (A < 0) {
        A *= -1n;
        B *= -1n;
      }

      if (B > 0) {
        const k = `${A}:${B}`;
        groups[k] = groups[k] || [0, 0];
        groups[k][0]++;
      } else {
        const k = `${-B}:${A}`;
        groups[k] = groups[k] || [0, 0];
        groups[k][1]++;
      }
    }
  });

  const mod = 1000000007;

  let result: bigint = BigInt(1);
  for (const k of Object.keys(groups)) {
    const [xn, yn] = groups[k];

    let tmp: number = 0;
    if (xn) {
      let xx = 1;
      for (let i = 0; i < xn; i++) {
        xx *= 2;
        if (xx > mod) xx %= mod;
      }
      xx -= 1;
      tmp += xx;
      if (tmp > mod) tmp %= mod;
    }

    if (yn) {
      let yy = 1;
      for (let i = 0; i < yn; i++) {
        yy *= 2;
        if (yy > mod) yy %= mod;
      }
      yy -= 1;
      tmp += yy;
      if (tmp > mod) tmp %= mod;
    }

    tmp++;
    if (tmp > mod) tmp %= mod;

    result *= BigInt(tmp);
    if (result > mod) result %= BigInt(mod);
  }

  result += BigInt(zero) + BigInt(mod - 1);
  if (result > mod) result %= BigInt(mod);

  console.log(result);
})();


import * as fs from 'fs';

const readString = (() => {
  const input = fs.readFileSync(0).toString();
  const words = input.split(/[ \n]/);
  let i = 0;
  return () => words[i++] || '';
})();
const readInt = () => parseInt(readString(), 10);
const readBigInt = () => BigInt(readString());

const gcd = (a: bigint, b: bigint) => {
  const _gcd = (a: bigint, b: bigint): bigint => {
    const q = a % b;
    return q === 0n ? b : _gcd(b, q);
  };
  return a > b ? _gcd(a, b) : _gcd(b, a);
};

const abs = (a: bigint) => a > 0n ? a : -a;

(function main() {
  const N = readInt();

  let zero: number = 0;
  const groups: { [k: string]: [number, number] } = {};
  Array.from({ length: N }, () => {
    let A = readBigInt();
    let B = readBigInt();
    if (A === 0n && B === 0n) {
      zero++;
    } else if (A === 0n) {
      groups['0:1'] = groups['0:1'] || [0, 0];
      groups['0:1'][0]++;
    } else if (B === 0n) {
      groups['0:1'] = groups['0:1'] || [0, 0];
      groups['0:1'][1]++;
    } else {
      const n = gcd(abs(A), abs(B));
      A /= n;
      B /= n;
      if (A < 0) {
        A *= -1n;
        B *= -1n;
      }

      if (B > 0) {
        const k = `${A}:${B}`;
        groups[k] = groups[k] || [0, 0];
        groups[k][0]++;
      } else {
        const k = `${-B}:${A}`;
        groups[k] = groups[k] || [0, 0];
        groups[k][1]++;
      }
    }
  });

  const mod = 1000000007;

  let result: number = 1;
  for (const k of Object.keys(groups)) {
    const [xn, yn] = groups[k];

    let tmp: number = 0;
    if (xn) {
      let xx = 1;
      for (let i = 0; i < xn; i++) {
        xx *= 2;
        if (xx > mod) xx %= mod;
      }
      xx -= 1;
      tmp += xx;
    }

    if (yn) {
      let yy = 1;
      for (let i = 0; i < yn; i++) {
        yy *= 2;
        if (yy > mod) yy %= mod;
      }
      yy -= 1;
      tmp += yy;
    }

    tmp++;
    if (tmp > mod) tmp %= mod;

    result *= tmp;
    if (result > mod) result %= mod;
  }

  result += zero - 1;
  if (result > mod) result %= mod;

  console.log(result.toString());
})();


import * as fs from 'fs';

const readString = (() => {
  const input = fs.readFileSync(0).toString();
  const words = input.split(/[ \n]/);
  let i = 0;
  return () => words[i++] || '';
})();
const readInt = () => parseInt(readString(), 10);
const readBigInt = () => BigInt(readString());

const gcd = (a: bigint, b: bigint) => {
  const _gcd = (a: bigint, b: bigint): bigint => {
    const q = a % b;
    return q === 0n ? b : _gcd(b, q);
  };
  return a > b ? _gcd(a, b) : _gcd(b, a);
};

const abs = (a: bigint) => a > 0n ? a : -a;

(function main() {
  const N = readInt();

  let zero: number = 0;
  const groups: { [a: string]: { [b: string]: number } } = {};
  Array.from({ length: N }, () => {
    let A = readBigInt();
    let B = readBigInt();
    if (A === 0n && B === 0n) {
      zero++;
    } else if (A === 0n) {
      groups['0'] = groups['0'] || {};
      groups['0']['1'] = groups['0']['1'] || 0;
      groups['0']['1']++;
    } else if (B === 0n) {
      groups['1'] = groups['1'] || {};
      groups['1']['0'] = groups['1']['0'] || 0;
      groups['1']['0']++;
    } else {
      const n = gcd(abs(A), abs(B));
      A /= n;
      B /= n;
      if (A < 0) {
        A *= -1n;
        B *= -1n;
      }

      const as = A.toString();
      const bs = B.toString();
      groups[as] = groups[as] || {};
      groups[as][bs] = groups[as][bs] || 0;
      groups[as][bs]++;
    }
  });

  console.log(JSON.stringify(groups, null, 2));
  const mod = 1000000007;

  let result: number = 1;
  for (const A of Object.keys(groups)) {
    const a = BigInt(A);
    for (const B of Object.keys(groups[A])) {
      const b = BigInt(B);
      if (b < 0 && groups[(-b).toString()] && groups[(-b).toString()][A]) continue;

      const xn = groups[A][B];
      const yn = (groups[B] && groups[B][(-a).toString()]) || 0;

      let tmp: number = 0;
      if (xn) {
        let xx = 1;
        for (let i = 0; i < xn; i++) {
          xx *= 2;
          if (xx > mod) xx %= mod;
        }
        xx -= 1;
        tmp += xx;
      }

      if (yn) {
        let yy = 1;
        for (let i = 0; i < yn; i++) {
          yy *= 2;
          if (yy > mod) yy %= mod;
        }
        yy -= 1;
        tmp += yy;
      }

      tmp++;
      if (tmp > mod) tmp %= mod;
      console.log(tmp);

      result *= tmp;
      if (result > mod) result %= mod;
    }
  }

  result += zero - 1;
  if (result > mod) result %= mod;

  console.log(result.toString());
})();


N_1, N_2, N_3, N_4 = map(int, input().split(' '))
set_1974 = {1, 9, 7, 4}
input_set = {N_1, N_2, N_3, N_4}

if len(set_1974 - input_set) == 0:
    print('YES')
else:
    print('NO')


N = list(map(int, input().split()))
N.sort()
print('YES' if N == [1, 4, 7, 9] else 'NO')


# -*- coding: utf-8 -*-

def nums():
    n = list(map(int, input().split()))
    n.sort()
    if n == [1, 4, 7, 9]:
        ans = 'YES'
    else:
        ans = 'NO'
    return ans

print(nums())


def main():
    S = input()
    ans = 'keyence'
    cnt = 0
    j = 0
    if S[0] != 'k':
        for i in range(len(S)-6):
            if S[i] == ans[j]:
                while True:
                    j += 1
                    i += 1
                    if j == 7:
                        return('YES')
                    elif S[i] != ans[j]:
                        break
        return ('NO')
    else:
        i = 0
        while True:
            i += 1
            j += 1
            if j == 7 and i == len(S):
                return('YES')
            elif S[i] != ans[j]:
                break
        #print(i,j)
        #print(S[69])
        for k in range(i,len(S)-(7-j-1),1):
            tmp = j
            #print(tmp,k,len(S)-j)
            if S[k] == ans[tmp]:
                while True:
                    #print(tmp,k)
                    tmp += 1
                    k += 1
                    if tmp == 7 and k == len(S):
                        return('YES')
                    elif tmp == 7 and k != len(S):
                        return('NO')
                    elif S[k] != ans[tmp]:
                        break
        return('No')

print(main())


N=input().split()
if N.count('1')==1 and N.count('7')==1 and
                N.count('9')==1 and N.count('4')==1:
                    print('YES')
else:
    print('NO')


N=list(input().split())
if N.count('1')==1 and N.count('7')==1 and
                N.count('9')==1 and N.count('4')==1:
                    print('YES')
else:
    print('NO')


import java.io.IOException;
import java.io.InputStream;
import java.util.NoSuchElementException;

public class Main {

	public static void main(String[] args) {
		FastScanner sc = new FastScanner();
		int N1 = sc.nextInt();
		int N2 = sc.nextInt();
		int N3 = sc.nextInt();
		int N4 = sc.nextInt();
		int[] N = new int[10];
		N[N1]++;
		N[N2]++;
		N[N3]++;
		N[N4]++;
		if (N[1] == 1 && N[9] == 1 && N[7] == 1 && N[4] == 1)
			System.out.println("YES");
		else
			System.out.println("NO");

	}

	//以下、自作ライブラリ

	/*
		int N = sc.nextInt();

		int[] a = new int[N];
		for (int i = 0; i < N; i++)
			a[i] = sc.nextInt();

		String s = sc.next();

		String[] a = new String[N];
		for (int i = 0; i < N; i++)
			a[i] = sc.next();

		int[][] a = new int[N][2];
		for (int i = 0; i < N; i++) {
			a[i][0] = sc.nextInt();
			a[i][1] = sc.nextInt();
		}
		Arrays.sort(a, (x, y) -> Integer.compare(x[0], y[0]));

		List<Integer> list = new ArrayList<>();

		Set<Integer> set = new HashSet<>();

		Map<Integer, Integer> map = new HashMap<>();
	 */

	//階乗
	public static int fact(int n) {
		if (n == 1 || n == 0)
			return 1;
		else
			return n * fact(n - 1);
	}

	//指定した値以上の要素が最初に現れる位置
	//要素の数は+1（配列のズレ)
	public static long lowerBound(int n, long[] x, long y) {
		int left = 0;
		int right = n;
		while (left < right) {
			int mid = (left + right) / 2;
			if (x[mid] < y) {
				left = mid + 1;
			} else {
				right = mid;
			}
		}
		return left;
	}

	//指定した値より大きい要素が最初に現れる位置
	//要素の数は+1（配列のズレ)
	public static long upperBound(int n, long[] x, long y) {
		int left = 0;
		int right = n;
		while (left < right) {
			int mid = (left + right) / 2;
			if (x[mid] <= y) {
				left = mid + 1;
			} else {
				right = mid;
			}
		}
		return left;
	}

	//最大公約数
	public static long gcd(long a, long b) {
		if (a < b)
			return gcd(b, a);
		long c = 0;
		c = a % b;
		if (c == 0)
			return b;
		return gcd(b, c);
	}

	//最小公倍数
	public static long lcm(long m, long n) {
		return m * n / gcd(m, n);
	}

	//素数判定
	public static boolean isPrime(int n) {
		if (n < 2)
			return false;
		if (n == 2)
			return true;
		if (n % 2 == 0)
			return false;

		for (int i = 3; i * i <= n; i += 2) {
			if (n % i == 0)
				return false;
		}
		return true;
	}
}

//以下、高速スキャナークラス
class FastScanner {
	private final InputStream in = System.in;
	private final byte[] buffer = new byte[1024];
	private int ptr = 0;
	private int buflen = 0;

	private boolean hasNextByte() {
		if (ptr < buflen) {
			return true;
		} else {
			ptr = 0;
			try {
				buflen = in.read(buffer);
			} catch (IOException e) {
				e.printStackTrace();
			}
			if (buflen <= 0) {
				return false;
			}
		}
		return true;
	}

	private int readByte() {
		if (hasNextByte())
			return buffer[ptr++];
		else
			return -1;
	}

	private static boolean isPrintableChar(int c) {
		return 33 <= c && c <= 126;
	}

	public boolean hasNext() {
		while (hasNextByte() && !isPrintableChar(buffer[ptr]))
			ptr++;
		return hasNextByte();
	}

	public String next() {
		if (!hasNext())
			throw new NoSuchElementException();
		StringBuilder sb = new StringBuilder();
		int b = readByte();
		while (isPrintableChar(b)) {
			sb.appendCodePoint(b);
			b = readByte();
		}
		return sb.toString();
	}

	public long nextLong() {
		if (!hasNext())
			throw new NoSuchElementException();
		long n = 0;
		boolean minus = false;
		int b = readByte();
		if (b == '-') {
			minus = true;
			b = readByte();
		}
		if (b < '0' || '9' < b) {
			throw new NumberFormatException();
		}
		while (true) {
			if ('0' <= b && b <= '9') {
				n *= 10;
				n += b - '0';
			} else if (b == -1 || !isPrintableChar(b)) {
				return minus ? -n : n;
			} else {
				throw new NumberFormatException();
			}
			b = readByte();
		}
	}

	public int nextInt() {
		long nl = nextLong();
		if (nl < Integer.MIN_VALUE || nl > Integer.MAX_VALUE)
			throw new NumberFormatException();
		return (int) nl;
	}

	public double nextDouble() {
		return Double.parseDouble(next());
	}
}


import java.io.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        String str = br.readLine();
        String[] data = str.split(" ");
        int[] numdata = new int[4];
        for (int i = 0; i < 4; i++) {
            numdata[i] = Integer.parseInt(data[i]);
        }

        int a = 0, b = 0, c = 0, d = 0, e = 0;
        for (int i = 0; i < 4; i++) {
            if (!(e == 0)) {
                e = 1;
                break;
            }
            switch (numdata[i]) {
            case 1:
                if (a == 0) {
                    a = 1;
                } else {
                    e = 1;
                }
                break;
            case 9:
                if (b == 0) {
                    b = 1;
                } else {
                    e = 1;
                }
                break;
            case 7:
                if (c == 0) {
                    c = 1;
                } else {
                    e = 1;
                }
                break;
            case 4:
                if (d == 0) {
                    d = 1;
                } else {
                    e = 1;
                }
                break;
            default:
                e = 1;
                break;
            }
        }

        if (e == 0)
            System.out.println("YES");
        else
            System.out.println("NO");
    }
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

class Main {
	public static void main(String args[]) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		Solver solver = new Solver();
		solver.init();
		solver.readHead(in.readLine());
		// for (; solver.hasNext();) {
		// solver.readBody(in.readLine());
		// }
		// solver.solve();
	}
}

class Solver {
	int N;
	int cnt;

	public void init() {
		N = 0;
		cnt = 0;
	}

	public void readHead(String str) {
		String[] strArr = str.split(" ");
		// N = Integer.parseInt(strArr[0]);
		// System.out.println(N);
		int N1 = Integer.parseInt(strArr[0]);
		int N2 = Integer.parseInt(strArr[1]);
		int N3 = Integer.parseInt(strArr[2]);
		int N4 = Integer.parseInt(strArr[3]);
		boolean ans = true;
		if (N1 != 1 && N2 != 1 && N3 != 1 && N4 != 1)
			ans = false;
		if (N1 != 9 && N2 != 9 && N3 != 9 && N4 != 9)
			ans = false;
		if (N1 != 7 && N2 != 7 && N3 != 7 && N4 != 7)
			ans = false;
		if (N1 != 4 && N2 != 4 && N3 != 4 && N4 != 4)
			ans = false;
		if (ans)
			System.out.println("YES");
		else
			System.out.println("NO");
	}

	public boolean hasNext() {
		return cnt < N;
	}

	public void readBody(String str) {
		// System.out.println(str);
		cnt++;
	}

	public void solve() {

	}
}


import java.util.*;

class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String s = scan.nextLine();
        String keyence = "keyence";
        for (int i = 0; i < s.length(); i++) {
            for (int j = i; j < s.length(); j++) {
                if (keyence.equals(s.substring(0,i)+s.substring(j,s.length()))) {
                    System.out.println("YES");
                    return;
                }
            }
        }
        System.out.println("NO");
    }
}


import java.util.*;

public class Main{
    public static void main(String[] args){
        Scanner  sc=new Scanner(System.in);
        Integer[] N=new Integer[4];
        int sum=0,product=1;
        for(int i=0;i<4;i++){
            N[i]=sc.nextInt();
            sum+=N[i];
            product*=N[i];
        }
        if(sum==20&&product==252){
            System.out.println("YES");
        }
        else{
            System.out.println("NO");
        }
   }
}


#include <bits/stdc++.h>
using namespace std;

class Main{
  private:

  public:
int n;
};
int main()
{
  int n;
  cin >> n;
  //cout << n <<endl;
  vector<int> list(n);
  for(int i =0 ; i < n; i++){
    cin >> list.at(i);
  }
  vector<string> str(n);
  for(int i = 0; i < n ; i++){
    stringstream ss;
    ss << bitset<100>(list.at(i));
    //cout << ss.str() << endl;
  	str.at(i) = ss.str() ;
  }
  vector<int> m(100);
  for(int j = 0; j < 100; j++){

    if(str.at(0).at(j) != str.at(1).at(j)){
      m.at(j) += 1;
    }
  }

  for(int i = 2; i < n ; i++){
    for(int j = 0; j < 100; j++){

      if(str.at(i).at(j) == '1'){
        m.at(j) += 1;
      }
    }
  }

  for(int i = 0; i < 100; i++){
    m.at(i) = m.at(i)%2;

  }
  string s;
  s = (accumulate(m.begin(), m.end(), 0) == 0)? "YES" : "NO";
  cout << s;
}


#include <iostream>
using namespace std;
int main(void){
    // Your code here!
    int a;
    int b[10]={};
    for(int i=0;i<4;i++){
    cin>>a;
    b[a]++;
    }
    if(b[1]==1 && b[4]==1 && b[7]==1 && b[9]==1) cout<<"YES"<<endl;
    else cout<<"NO"<<endl;

    return 0;
}


#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef double lf;
typedef long double Lf;
typedef pair <int,int> pii;
typedef pair <ll, ll> pll;

#define TRACE(x) cerr << #x << "  " << x << endl
#define FOR(i, a, b) for (int i = (a); i < int(b); i++)
#define REP(i, n) FOR(i, 0, n)
#define all(x) (x).begin(), (x).end()
#define _ << " " <<

#define fi first
#define sec second
#define mp make_pair

int main() {
  string s = "";
  REP(i, 4) {
    string t;
    cin >> t;
    s += t;
  }
  sort(all(s));
  if (s == "1479") puts("YES");
  else puts("NO");
  return 0;
}


#include<bits/stdc++.h>
using namespace std;
char S;
bool answer;
vector<bool> B(4, true);
int main(void){
	int i;
	for(i = 0; i < 4; i++){
		cin >> S;
		if('1' == S)B[0] = false;
		if('7' == S)B[1] = false;
		if('9' == S)B[2] = false;
		if('4' == S)B[3] = false;
	}
	for(i = 0; i < 4; i++) if(B[i]) answer = true;
  if(answer)printf("NO\n");
	else printf("YES\n");
}


n = list(map(int, input().split()))
n.sort()
if n == [1, 4, 7, 9]: print("YES")
else: print("NO")


#include <bits/stdc++.h>
using namespace std;
int main()
{
long long i,j,k,a,b,c=0,d,n,m,t=1,x;
cin>>n>>d>>x>>c;
if((n==1||n==9||n==7||n==4) && (d==1||d==9||d==7||d==4) &&(x==1||x==9||x==7||x==4)&&(c==1||c==9||c==7||c==4)) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
return 0;
}