unittest
{
	assert( [ "3", "1 2", "-1 1", "2 -1" ].solve == 5 );
	assert( [ "10", "3 2", "3 2", "-1 1", "2 -1", "-3 -9", "-8 12", "7 7", "8 1", "8 2", "8 4" ].solve == 479 );
	
	assert( [ "3", "0 2", "0 3", "5 0" ].solve == 4 );		// 追加ケース
	assert( [ "3", "0 2", "0 3", "5 1" ].solve == 7 );		// 追加ケース
	assert( [ "3", "2 0", "3 0", "5 1" ].solve == 7 );		// 追加ケース
}



import std.conv;
import std.math;
import std.numeric;
import std.range;
import std.stdio;

void main()
{
	stdin.byLineCopy.solve.writeln;
}

auto solve( Range )( Range input )
if( isInputRange!Range && is( ElementType!Range == string ) )
{
	auto n = input.front.to!( long );
	auto abs = new long[ 2 ][ n ];
	foreach( ref ab; abs )
	{
		input.popFront;
		ab = input.front.split.to!( long[] );
	}
	
	// 式変形：a[i] * a[j] + b[i] * b[j] == 0  -->  a[i] * -a[j] == b[i] * b[j]  -->  a[i] / b[i] == b[j] / -a[j]
	// つまり、a / b グループのイワシは b / -a グループのイワシと仲が悪い
	// また、仲が悪いグループ同士は1:1に対応するのでn:nの組み合わせは考慮しなくてよい
	
	// 各イワシの a / b を集計
	auto z = 0L;
	long[ long ][ long ] css;
	foreach( ab; abs )
	{
		if( ab[ 0 ] == 0 && ab[ 1 ] == 0 )
		{
			// aもbも0のイワシは単独でしか使えないので分けて数える
			z++;
		}
		else if( ab[ 0 ] == 0 )
		{
			// aが0のイワシはbが0のイワシと仲が悪い
			css[ 0 ][ 1 ]++;
		}
		else if( ab[ 1 ] == 0 )
		{
			// bが0のイワシはaが0のイワシと仲が悪い
			css[ 1 ][ 0 ]++;
		}
		else 
		{
			// a / b のイワシは b / -a のイワシと仲が悪い
			
			// 約分
			auto d = gcd( ab[ 0 ].abs, ab[ 1 ].abs );
			ab[ 0 ] /= d;
			ab[ 1 ] /= d;
			
			// aの符号揃え
			if( ab[ 0 ] < 0 )
			{
				ab[ 0 ] *= -1;
				ab[ 1 ] *= -1;
			}
			
			css[ ab[ 0 ] ][ ab[ 1 ] ]++;
		}
	}
	
	// 合計パターン数を計算
	const M = 1000000007L;
	auto ans = ModWorld!M( 1L );
	foreach( a, cs; css )
	{
		foreach( b, c; cs )
		{
			if( ( 0 < b && ( b !in css || -a !in css[ b ] ) ) || ( b <= 0 && ( -b !in css || a !in css[ -b ] ) ) )
			{
				// 仲が悪いイワシがいない
				ans *= ModWorld!M( 2L ) ^^ c;
			}
			else
			{
				// 仲が悪いイワシがいる
				
				// どちらかのグループからだけ選ぶ（1匹も選ばないパターンが二重カウントされるので1引く）
				if( 0 < b ) ans *= ModWorld!M( 2L ) ^^ c + ModWorld!M( 2L ) ^^ css[ b ][ -a ] - 1;
			}
		}
	}
	ans += z;
	ans--;		// 全体で1匹も選ばないパターンがカウントされているので1引く
	return ans;
}

// モジュラ整数型（n mod M）
struct ModWorld( long M )
{
	long n;
	
	void opUnary( string OP )() if( OP == "++" ){ this.n = ( this.n + 1 ) % M; }
	void opUnary( string OP )() if( OP == "--" ){ this.n = ( this.n + M - 1 ) % M; }
	void opOpAssign( string OP )( ModWorld r ) if( OP == "+"  ){ this.n = ( this.n + r.n ) % M; }
	void opOpAssign( string OP )( ModWorld r ) if( OP == "-"  ){ this.n = ( this.n + M - r.n ) % M; }
	void opOpAssign( string OP )( ModWorld r ) if( OP == "*"  ){ this.n = ( this.n * r.n ) % M; }
	void opOpAssign( string OP )( ModWorld r ) if( OP == "/"  ){ this *= r ^^ ModWorld( M - 2 ); }
	void opOpAssign( string OP )( ModWorld r ) if( OP == "^^" )
	{
		auto a = this.n;
		this.n = 1L;
		for( auto i = r.n; 0 < i; i >>= 1 )
		{
			if( i & 1 ) this.n = ( this.n * a ) % M;
			a = ( a * a ) % M;
		}
	}
	auto opBinary( string OP )( ModWorld r ){ auto l = this; l.opOpAssign!OP( r ); return l; }
	
	void opAssign( long r ){ this.n = r; }
	void opOpAssign( string OP )( long r ){ this.opOpAssign!OP( ModWorld( r ) ); }
	auto opBinary( string OP )( long r ){ return this.opBinary!OP( ModWorld( r ) ); }
	auto opBinaryRight( string OP )( long l ){ return ModWorld( l ).opBinary!OP( this ); }
	auto opEquals( long r ){ return this.n == r; }
	
	auto toString(){ return this.n.to!string; }
}


Measured memory_bytes: 62416