use proconio::{input, fastout};
use std::collections::HashMap;
use std::collections::HashSet;
// use num::Integer;

#[derive(Clone, Copy)]
struct ModInt(u64);

const MOD: u64 = 1_000_000_007;

impl std::ops::Add for ModInt {
  type Output = Self;
  fn add(self, rhs: Self) -> Self {
    let mut d = self.0 + rhs.0;
    if d >= MOD {
      d -= MOD;
    }
    Self(d)
  }
}

impl std::ops::AddAssign for ModInt {
  fn add_assign(&mut self, rhs: Self) {
    *self = *self + rhs;
  }
}

impl std::ops::Sub for ModInt {
  type Output = Self;
  fn sub(self, rhs: Self) -> Self {
    let mut d = self.0 + MOD - rhs.0;
    if d >= MOD {
      d -= MOD;
    }
    Self(d)
  }
}

impl std::ops::SubAssign for ModInt {
  fn sub_assign(&mut self, rhs: Self) {
    *self = *self - rhs;
  }
}

impl std::ops::Mul for ModInt {
  type Output = Self;
  fn mul(self, rhs: Self) -> Self {
    ModInt(self.0 * rhs.0 % MOD)
  }
}

impl std::ops::MulAssign for ModInt {
    fn mul_assign(&mut self, rhs: Self) {
        *self = *self * rhs;
    }
}

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

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

impl std::str::FromStr for ModInt {
    type Err = std::num::ParseIntError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let val = s.parse::<u64>()?;
        Ok(ModInt::new(val))
    }
}

impl From<usize> for ModInt {
    fn from(val: usize) -> ModInt {
        ModInt((val % MOD as usize) as u64)
    }
}

#[allow(dead_code)]
impl ModInt {
    pub fn new(n: u64) -> ModInt {
        ModInt(n % MOD)
    }
    pub fn zero() -> ModInt {
        ModInt(0)
    }
    pub fn one() -> ModInt {
        ModInt(1)
    }
    pub fn pow(self, mut n: u64) -> ModInt {
        let mut t = ModInt::one();
        let mut s = self;
        while n > 0 {
            if n & 1 == 1 {
                t *= s;
            }
            s *= s;
            n >>= 1;
        }
        t
    }
    pub fn inv(self) -> ModInt {
        assert!(self.0 > 0);
        // only if MOD is prime
        self.pow(MOD - 2)
        // if self and MOD are relatively prime
        // use extened equlidean algorithm
    }
}

fn norm(x: i64, y: i64) -> (i64, i64) {
  let (ax, ay) = (x.abs(), y.abs());
  let g = num::Integer::gcd(&ax, &ay);
  (x / g, y / g)
}

#[fastout]
fn main() {
  input!{
    n: usize,
    ab: [(i64, i64); n]
  };
  let mut h : HashMap<(i64, i64), u64> = HashMap::new();
  let mut zerozero = 0;
  for (a, b) in &ab {
    let (a, b) = if *b < 0 {
      (*a * -1, *b * -1)
    } else {
      (*a, *b)
    };
    if a == 0 && b == 0 {
      zerozero += 1;
    } else if a == 0 {
      *h.entry((0, 1)).or_insert(0) += 1;
    } else if b == 0 {
      *h.entry((1, 0)).or_insert(0) += 1;
    } else {
      *h.entry(norm(a, b)).or_insert(0) += 1;
    }
  }

  let mut ans = ModInt::new(1);
  let mut s : HashSet<(i64, i64)> = HashSet::new();
  for (r, v) in &h {
    let r = *r;
    let v = *v;
    if !s.contains(&r) {
      let r2 = if r.0 <= 0 {
        (r.1, r.0 * -1)
      } else {
        (r.1 * -1, r.0)
      };
      let v2  = match h.get(&r2) {
        Some(vv2) => *vv2,
        None => 0
      };
      let pow = ModInt::new(2).pow(v) + ModInt::new(2).pow(v2) - ModInt::one();

      s.insert(r);
      s.insert(r2);
      ans = ans * pow;
    }
  }
  ans -= ModInt::one();
  ans += ModInt::new(zerozero);
  println!("{}", ans);
}


Measured memory_bytes: 36416