#include <bits/stdc++.h>

//Common
using namespace std;using f64 = double;using i64=long long;using u64=unsigned long long;
template<typename T> using Arr=vector<T>;template<typename T> using Func=function<T>;template<typename T> using Str=basic_string<T>;
#define hfor(v, s, e) for(int v=(s);(s)<=v&&v<(e);++v)//half-opened range
#define hfori(v, s, e) for(int v=(e)-1;(s)<=v&&v<(e);--v)//inversion
#define hforo(v, s, e) int v=(s);for(;(s)<=v&&v<(e);++v)//out declaration
#define hforoi(v, s, e) int v=(e)-1; for(;(s)<=v&&v<(e);--v)
#define cfor(v, s, e) hfor(v,(s),(e)+1)//closed range
#define cfori(v, s, e) hfori(v,(s),(e)+1)
#define cforo(v, s, e) hforo(v,(s),(e)+1)
#define cforoi(v, s, e) hforoi(v,(s),(e)+1)
#define rep(v,x) hfor(v,0,(x))
#define repi(v,x) hfori(v,0,(x))
#define repo(v,x) hforo(v,0,(x))
#define all(x) (x).begin(),(x).end()
#define sz(x) ((int)(x).size())
#define pushb push_back
#define pushf push_front
#define popb pop_back
#define popf pop_front
#define empl emplace
#define emplb emplace_back
#define emplf emplace_front
#define fi first
#define se second
#define cxp constexpr
#define PQ std::priority_queue

//Math
template<typename T> cxp T inf() { return numeric_limits<T>::max() / 2; }
template<typename T> inline T sq(T x){return x*x;}
int rd(int lb, int ub){static mt19937 rng(time(0)^i64(new int)); return uniform_int_distribution<int>(lb, ub-1)(rng);}
int rd(int ub=inf<int>()){return rd(0,ub);}
const f64 pi=acosl(-1), eps=1e-10;

//IO
template<typename T>ostream& operator<<(ostream& s, const Arr<T>& a) {for(auto i:a) cout<<i<<' '; return s;}
int getint(){int x;cin>>x;return x;}
char getchr(){char x;cin>>x;return x;}
#ifndef DEBUG
	//#pragma GCC optimize ("Ofast") //somtimes it become more slower
	auto __PRE_RUN__=(ios::sync_with_stdio(0), cin.tie(0), cout.tie(0),(cout<<fixed<<setprecision(11)), 0);
#endif

//Misc
auto split(auto s, auto p){
	Arr<decltype(s)> ret;
	auto it1=s.begin();
	for(auto it2=it1; (it2 = find(it1,s.end(),p)) != s.end(); it1=it2+1)
		ret.pushb({it1, it2});
	ret.pushb({it1,s.end()});
	return ret;
}
int pow2ceil(int x){assert(x);return 1<<(32-__builtin_clz(x-1));}
template<typename T> T rev(const T& a){return {a.rbegin(),a.rend()};}
Arr<int> range(int n){ Arr<int> ret(n); rep(i,n)ret[i]=i; return ret; }
struct defer{ defer(auto f):f(f){} ~defer(){f();} function<void()> f; };
#define defer(x) auto _##__COUNTER__ = defer([&](){x;});
#define lam(expr, ...) [&](__VA_ARGS__){return expr;}
#define reduce accumulate

template<typename T>
T fastpow(const T& a, int p) {
	if (p==0)
		return T(1);
	T tmp=fastpow(a, p/2);
	if (p%2)
		return tmp*tmp*a;
	return tmp*tmp;
}

/*
Adjust Formaula
xgcd(a,b) solves ax+by=gcd(a,b)=g
ax+by=c satisfied iff c=g*k
so, do x*=k and y*=k.

if (x,y) is one of root, (x+t*b/g, y-t*a/g) is also root.(t is arbitary integer)
if x and y have bounds, solve that inequalities.
For example, I have one lbx(lower bound x) and one lby(lower bound y).
I will find the root which is the closest to lbx.
solve x+t*b/g<lbx and x+t*b/g>=lbx consecutively.
then, all of big x go to small, and next all small thing go to big|equal to lbx
now, we got proper t for lbx, and check if y-t*a/g>=lby.(cuz (x,y)->(x+t*b/g, y-t*a/g))
if it is not satisfied, solve inequality for y similarly.
if adjusted x for satisfying y is not satisfied, this formula has no solution.

Maybe simpler solution exist. see	(jung2381187, koosaga)'s code at boj_11661
linearity?
*/

//Multivariable Linear Deophantine:
//ax+by+cz=k
//=> gcd(a,b)w+cz=k && ax+by=gcd(a,b)w
//(extend it recursively)

//recommand to use python
struct XGCD{i64 g; i64 x; i64 y;};
XGCD xgcd(i64 a, i64 b) {
		if (b==0)
				return {a,1,0};
		auto res = xgcd(b, a%b);
		return {res.g, res.y, res.x-(a/b)*res.y};
}
XGCD XGCDtolb(XGCD a, i64 lbx, i64 lby){
	if(0)
		throw "no root";
	return {};
	//return minimum a which satisfies lb<a
}
XGCD XGCDtoub(XGCD a, i64 ubx, i64 uby){
	if(0)
		throw "no root";
	return {};
	//return maximum a which satisfies a<ub
}

bool is_prime(i64 n){
	if(n<2)
		return false;
	for(i64 i=2;i*i<=n;i++){
		if(!(n%i))
			return false;
	}
	return true;
}

bool miller_rabin(i64 n){
	return true;
}

Arr<i64> factorization(i64 n){
	i64 cur=n;
	Arr<i64> ret;
	for(i64 i=2; i*i<=n; i++)
		while (!(cur%i)){
			cur/=i;
			ret.pushb(i);
		}
	if (cur>1)
		ret.pushb(cur);
	return ret;
}

Arr<i64> divisors(i64 n){
	Arr<i64> ret, tmp;
	for(i64 i=1; i*i<=n; i++){
		if (n%i==0){
			ret.pushb(i);
			if (i!=n/i)
				tmp.pushb(n/i);
		}
	}
	reverse(all(tmp));
	ret.insert(ret.end(), all(tmp));
	return ret;
}

//nloglog(n)
Arr<bool> sieve_prime(int ub){
	Arr<bool> ret(ub, true);
	ret[0]=ret[1]=false;
	for (i64 i=2; i*i<ub; i++) {
		if (!ret[i])
			continue;
		for (i64 j=i*i; j<ub; j+=i)
			ret[j]=false;
	}
	return ret;
}

//nlogn (harmony series)
//can apply to calculate (low constant factor)sieve_numdiv, sieve_sumdiv, etc.
Arr<Arr<int>> sieve_divs(int ub){
	Arr<Arr<int>> ret(ub);
	for (i64 i=1; i<ub; i++) {
		for (i64 j=i; j<ub; j+=i)
			ret[j].pushb(i);
	}
	return ret;
}

int eulerphi(int n){return 0;}

//floor(log(n))
int lgf(i64 n, int base=2){
	int ret=0;
	while (n)
		n/=base, ret++;
	return ret-1;
}

//ceil(log(n))
int lgc(i64 n, int base=2) {
	int ret=0;
	int rem=0;
	while (n)
		rem+=n%base, n/=base, ret++;
	return ret-(rem<=1);
}

int sqrt_f(i64 n){
	i64 i=1;
	while(i*i<=n)
		i++;
	return i-1;
}

int sqrt_c(i64 n){
	i64 i=1;
	while(i*i<n)
		i++;
	return i;
}

template<typename T>
Arr<T> factorials(T n){
	Arr<T> ret(int(n)+1);
	ret[0]=1;
	cfor(i, 1, n)
		ret[i]=ret[i-1]*i;
	return ret;
}

//recommanded T=ModNum. i64 can easily overflow.
template<typename T>
T binom(T n, T k){
	if(k>n/2)
		return binom(n, n-k);
	if(k==0)
		return 1;
	auto f=factorials(n);
	return f[int(n)]/(f[int(k)]*f[int(n-k)]);
}

template<typename T>
Arr<Arr<T>> binom_dp(T n, T k){
	Arr<Arr<T>> ret(n, Arr<T>(k));

	return ret;
}

Arr<int> to_digits(i64 n){
	Arr<int> ret;
	while(n)
		ret.pushb(n%10), n/=10;
	return ret;
}

//chinese remainder theorem
void crt(){
}

//baby step giant step
void discrete_log(){
}

class NoModInv {};

template<int m>
struct Mod {
	Mod(i64 n=0) :n((n%m+m)%m){}
	explicit operator int()const{return n;}
	explicit operator bool()const{return !!n;}
	i64 val()const { return n; }

	Mod operator - () const { return {-n}; }
	Mod operator + (const Mod& b)const { return n + b.val(); }
	Mod operator - (const Mod& b)const { return n - b.val() + m; }
	Mod operator * (const Mod& b)const { return n * b.val(); }
	Mod operator / (const Mod& b)const { return *this * inv(b); }
	Mod operator+= (const Mod& b) { return *this = *this + b; }
	Mod operator-= (const Mod& b) { return *this = *this - b; }
	Mod operator*= (const Mod& b) { return *this = *this * b; }
	Mod operator/= (const Mod& b) { return *this = *this / b; }

	Mod& operator++ () { *this += 1ll; return *this; }
	Mod& operator-- () { *this -= 1ll; return *this; }
	Mod operator++(int) { auto ret = *this; ++*this; return ret; }
	Mod operator--(int) { auto ret = *this; --*this; return ret; }

	bool operator==(const Mod& r) const { return n == r.n; }
	bool operator!=(const Mod& r) const { return !(*this == r); }
	bool operator<(const Mod& r) const { return n < r.n; }
private:
	i64 n;

	Mod inv(const Mod &b)const {
		auto res = xgcd(b.val(), -m);
		if (1 % res.g)
			throw NoModInv();
		res.x *= 1 / res.g;
		while (res.x < 0)
			res.x += -m / res.g;
		return res.x;
	}
};

template<int m>
ostream& operator<<(ostream& s, const Mod<m>& n) { return s << n.val(); }

struct Frac{
	i64 a,b;
	Frac(i64 a=0, i64 b=1):a(a),b(b){normalize();}
	bool operator<(const Frac& r)const{
		//NaN < -inf < ... < inf (for using set conveniently)
		//!(a>b) && !(a<b) <=> a==b
		if(r.is_nan())
			return false;
		if(is_nan())
			return true;
		return a*r.b<r.a*b;
	}
	bool operator>(const Frac& r)const{return r<*this;}
	bool operator==(const Frac& r)const{return !(*this<r)&&!(*this>r);}
	bool operator!=(const Frac& r)const{return !(*this==r);}
	bool operator<=(const Frac& r)const{return *this<r||*this==r;}
	bool operator>=(const Frac& r)const{return *this>r||*this==r;}
	Frac operator+(const Frac& r)const{return {a*r.b+r.a*b, b*r.b};}
	Frac operator*(const Frac& r)const{return {a*r.a, b*r.b};}
	Frac operator/(const Frac& r)const{return (*this)*Frac(r.b,r.a);}
	bool is_singular()const{return !b;}
	bool is_infpos()const{return a>0&&!b;}
	bool is_infneg()const{return a<0&&!b;}
	bool is_nan()const{return !a&&!b;}
	bool is_int()const{return b&&!(a%b);}
	i64 to_integer()const{return a/b;}
	f64 to_f64(){return (f64)a/b;}
	i64 ceil()const{return a/b+(a*b>0)*!!(a%b);}
	i64 floor()const{return a/b-(a*b<0)*!!(a%b);}
	i64 smaller_int()const{return is_int()?to_integer()-1:floor();}
	i64 larger_int()const{return is_int()?to_integer()+1:ceil();}
	void normalize(){
		if(is_singular()){
			if(a)
				a/=abs(a);
			return;
		}

		i64 g = gcd(a,b);
		a/=g; b/=g;
		if(b<0)
			a*=-1, b*=-1;
	}
};

template<>
Frac inf(){return {1,0};}

template<typename T>
ostream& operator<<(ostream& s, const Frac& n){return s<<n.a<<'/'<<n.b;}

#define endl '\n'//not interactive?
#define int i64//MLE?
using pint = pair<int,int>;
using tint = tuple<int,int,int>;

const int m=1e9+7;

Mod<m> fp(Mod<m> x, int p){
	if(!p) return 1;
	auto z=fp(x,p/2);
	return z*z*(p%2?x:1);
}

signed main(){
	int n;cin>>n;
	map<Frac,int> z;
	int zc=0;
	rep(i,n){
		int a,b; cin>>a>>b;
		if(a or b)
			z[{a,b}]++;
		else
			zc++;
	}
	Mod<m> ans=1;
	while(sz(z)){
		auto c=z.begin()->fi;
		ans*=fp(Mod<m>(2),z[c])+fp(Mod<m>(2),z[{-c.b,c.a}])-1;
		z.erase(c);
		z.erase({-c.b,c.a});
	}
	cout<<ans-1+zc<<endl;
	
	return 0;
}

