#![allow(unused_imports, unused_macros)]

use kyoproio::*;
use std::{
    collections::*,
    io::{self, prelude::*},
    iter,
    mem::{replace, swap},
};

fn main() -> io::Result<()> {
    std::thread::Builder::new()
        .stack_size(64 * 1024 * 1024)
        .spawn(solve)?
        .join()
        .unwrap();
    Ok(())
}

fn solve() {
    let stdin = io::stdin();
    let mut kin = KInput::new(stdin.lock());
    let stdout = io::stdout();
    let mut out = io::BufWriter::new(stdout.lock());
    macro_rules! output { ($($args:expr),+) => { write!(&mut out, $($args),+).unwrap(); }; }
    macro_rules! outputln {
        ($($args:expr),+) => { output!($($args),+); outputln!(); };
        () => { output!("\n"); if cfg!(debug_assertions) { out.flush().unwrap(); } }
    }

    const MOD: i64 = 1e9 as i64 + 7;
    let n: usize = kin.input();
    let mut map = BTreeMap::new();
    let mut zero = 0;
    for (a, b) in kin.iter::<(i64, i64)>().take(n) {
        if a == 0 && b == 0 {
            zero += 1;
        } else {
            let r = if b == 0 {
                Rational::new(-1, 0)
            } else {
                Rational::new(a, b)
            };
            *map.entry(r).or_insert(0) += 1;
        }
    }

    let mut ans = 1;
    let mut set = BTreeSet::new();
    for (&r, &v) in map.iter() {
        if set.contains(&r) {
            continue;
        }
        let ir = -r.inv();
        let iv = map.get(&ir).cloned().unwrap_or(0);
        ans = ans * (mod_pow(2, v, MOD) + mod_pow(2, iv, MOD) - 1) % MOD;
        set.insert(ir);
    }

    ans = (ans + zero - 1) % MOD;
    outputln!("{}", ans);
}

pub fn mod_pow(mut a: i64, mut b: i64, m: i64) -> i64 {
    let mut y = 1;
    while b > 0 {
        if b & 1 == 1 {
            y = y * a % m;
        }
        a = a * a % m;
        b >>= 1;
    }
    y
}

#[repr(C)]
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
pub struct Rational(i64, i64);
impl Rational {
    pub fn new(nom: i64, den: i64) -> Self {
        Self(nom, den).reduced()
    }
    pub fn inv(self) -> Self {
        if self.0 >= 0 {
            Self(self.1, self.0)
        } else {
            Self(-self.1, -self.0)
        }
    }
    fn reduced(self) -> Self {
        let Self(nom, den) = self;
        let d = gcd(nom.abs(), den.abs());
        if den >= 0 {
            Self(nom / d, den / d)
        } else {
            Self(-nom / d, -den / d)
        }
        .checked()
    }
    fn checked(self) -> Self {
        assert_ne!(self, Self(0, 0));
        self
    }
}
use std::{cmp::Ordering, ops::*};
impl PartialOrd for Rational {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}
impl Ord for Rational {
    fn cmp(&self, other: &Self) -> Ordering {
        if self.1 == 0 && other.1 == 0 {
            self.0.cmp(&other.0)
        } else {
            (self.0 as i128 * other.1 as i128).cmp(&(self.1 as i128 * other.0 as i128))
        }
    }
}
impl Neg for Rational {
    type Output = Self;
    fn neg(self) -> Self {
        Self(-self.0, self.1)
    }
}
impl Add for Rational {
    type Output = Self;
    fn add(self, other: Self) -> Self {
        let g = gcd(self.1, other.1);
        let d = self.1 / g;
        Self(self.0 * (other.1 / g) + other.0 * d, d * other.1)
    }
}
impl Sub for Rational {
    type Output = Self;
    fn sub(self, other: Self) -> Self {
        self + -other
    }
}
impl Mul for Rational {
    type Output = Self;
    fn mul(self, other: Self) -> Self {
        let g1 = gcd(self.0, other.1);
        let g2 = gcd(self.1, other.0);
        Self(self.0 / g1 * other.0, self.1 / g2 * other.1).checked()
    }
}
impl Div for Rational {
    type Output = Self;
    fn div(self, other: Self) -> Self {
        self * other.inv()
    }
}
impl From<i64> for Rational {
    fn from(x: i64) -> Self {
        Self(x, 1)
    }
}
impl From<Rational> for f64 {
    fn from(r: Rational) -> f64 {
        r.0 as f64 / r.1 as f64
    }
}
fn gcd(x: i64, y: i64) -> i64 {
    if y == 0 {
        x
    } else {
        gcd(y, x % y)
    }
}

// -----------------------------------------------------------------------------
pub mod kyoproio {
    use std::io::prelude::*;
    pub trait Input {
        fn str(&mut self) -> &str;
        fn input<T: InputParse>(&mut self) -> T {
            T::input(self)
        }
        fn iter<T: InputParse>(&mut self) -> Iter<T, Self> {
            Iter(self, std::marker::PhantomData)
        }
        fn seq<T: InputParse, B: std::iter::FromIterator<T>>(&mut self, n: usize) -> B {
            self.iter().take(n).collect()
        }
    }
    pub struct KInput<R> {
        src: R,
        buf: String,
        pos: usize,
    }
    impl<R: BufRead> KInput<R> {
        pub fn new(src: R) -> Self {
            Self {
                src,
                buf: String::with_capacity(1024),
                pos: 0,
            }
        }
        pub fn src(&mut self) -> &mut R {
            &mut self.src
        }
    }
    impl<R: BufRead> Input for KInput<R> {
        fn str(&mut self) -> &str {
            loop {
                if self.pos >= self.buf.len() {
                    self.pos = 0;
                    self.buf.clear();
                    if self.src.read_line(&mut self.buf).expect("io error") == 0 {
                        return &self.buf;
                    }
                }
                let range = self.pos
                    ..self.buf[self.pos..]
                        .find(|c: char| c.is_ascii_whitespace())
                        .map(|i| i + self.pos)
                        .unwrap_or_else(|| self.buf.len());
                self.pos = range.end + 1;
                if range.end > range.start {
                    return &self.buf[range];
                }
            }
        }
    }
    pub struct Iter<'a, T, I: ?Sized>(&'a mut I, std::marker::PhantomData<*const T>);
    impl<'a, T: InputParse, I: Input + ?Sized> Iterator for Iter<'a, T, I> {
        type Item = T;
        fn next(&mut self) -> Option<T> {
            Some(self.0.input())
        }
    }
    pub trait InputParse: Sized {
        fn input<I: Input + ?Sized>(src: &mut I) -> Self;
    }
    impl InputParse for Vec<u8> {
        fn input<I: Input + ?Sized>(src: &mut I) -> Self {
            src.str().as_bytes().to_owned()
        }
    }
    macro_rules! from_str_impl {
        { $($T:ty)* } => {
            $(impl InputParse for $T {
                fn input<I: Input + ?Sized>(src: &mut I) -> Self {
                    src.str().parse::<$T>().expect("parse error")
                }
            })*
        }
    }
    from_str_impl! {
        String char bool f32 f64 isize i8 i16 i32 i64 i128 usize u8 u16 u32 u64 u128
    }
    macro_rules! tuple_impl {
        ($H:ident $($T:ident)*) => {
            impl<$H: InputParse, $($T: InputParse),*> InputParse for ($H, $($T),*) {
                fn input<I: Input + ?Sized>(src: &mut I) -> Self {
                    ($H::input(src), $($T::input(src)),*)
                }
            }
            tuple_impl!($($T)*);
        };
        () => {}
    }
    tuple_impl!(A B C D E F G);
    #[macro_export]
    macro_rules! kdbg {
        ($($v:expr),*) => {
            if cfg!(debug_assertions) {
                dbg!($($v),*)
            } else {
                ($($v),*)
            }
        }
    }
}
