use std::f64::consts::PI;

const EPS: f64 = 1e-8;

fn main() {
    let mut sc = Scanner::new();

    let c1p: Pt = sc.next();
    let c1r: f64 = sc.next();

    let c2p: Pt = sc.next();
    let c2r: f64 = sc.next();

    let ans = match circle_relationship(&Circle::new(c1p, c1r), &Circle::new(c2p, c2r)) {
        CircleRelationship::NotCross => 4,
        CircleRelationship::Circumscribe => 3,
        CircleRelationship::Cross => 2,
        CircleRelationship::Inscribe => 1,
        CircleRelationship::Inclusion => 0,
    };

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

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

pub type Pt = complex::Complex<f64>;

pub fn cross(a: Pt, b: Pt) -> f64 {
    (a.conj() * b).im
}

pub fn dot(a: Pt, b: Pt) -> f64 {
    (a.conj() * b).re
}

pub fn normalize(p: Pt) -> Pt {
    p / p.norm()
}

/// Positional relationship of 3-points
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum CCW {
    /// a->b->c is counter clockwise
    CounterClockwise = 1,
    /// a->b->c is clockwise
    Clockwise = -1,
    /// c--a--b
    OnLineBack = 2,
    /// a--b--c
    OnLineFront = -2,
    /// a--c--b
    OnSegment = 0,
}

/// Positional relationship of 3-points
pub fn ccw(a: Pt, b: Pt, c: Pt) -> CCW {
    let b = b - a;
    let c = c - a;
    if cross(b, c) > 0.0 {
        CCW::CounterClockwise
    } else if cross(b, c) < 0.0 {
        CCW::Clockwise
    } else if dot(b, c) < 0.0 {
        CCW::OnLineBack
    } else if b.norm() < c.norm() {
        CCW::OnLineFront
    } else {
        CCW::OnSegment
    }
}

/// Signed area of triangle
pub fn triangle_area_signed(a: Pt, b: Pt, c: Pt) -> f64 {
    cross(b - a, c - a) / 2.0
}

/// Area of polygon
pub fn polygon_area_signed(p: &[Pt]) -> f64 {
    let mut ret = 0.0;
    for r in p[1..].windows(2) {
        ret += triangle_area_signed(p[0], r[0], r[1]);
    }
    ret
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PolygonContains {
    On,
    In,
    Out,
}

pub fn polygon_contains(poly: &[Pt], p: Pt) -> PolygonContains {
    let n = poly.len();
    let mut is_in = false;

    for i in 0..poly.len() {
        let a = poly[i] - p;
        let b = poly[(i + 1) % n] - p;
        let (a, b) = if a.im > b.im { (b, a) } else { (a, b) };

        if a.im <= 0.0 && 0.0 < b.im && cross(a, b) < 0.0 {
            is_in = !is_in
        };
        if cross(a, b) == 0.0 && dot(a, b) <= 0.0 {
            return PolygonContains::On;
        }
    }

    if is_in {
        PolygonContains::In
    } else {
        PolygonContains::Out
    }
}

pub fn is_convex(p: &[Pt]) -> bool {
    let n = p.len();
    for i in 0..n {
        if ccw(p[i], p[(i + 1) % n], p[(i + 2) % n]) == CCW::Clockwise {
            return false;
        }
    }
    true
}

pub fn rem_euclid(a: f64, b: f64) -> f64 {
    let r = a % b;
    if r < 0.0 {
        r + b.abs()
    } else {
        r
    }
}

/// Andrew's monotone chain convex hull algorithm
pub fn convex_hull(p: Vec<Pt>) -> Vec<Pt> {
    let n = p.len();
    assert!(n >= 3);

    let mut p = p;
    p.sort_by(|x, y| (x.re, x.im).partial_cmp(&(y.re, y.im)).unwrap());

    let mut ret = vec![];

    macro_rules! half_hull {
        ($r:expr) => {
            for i in $r {
                while {
                    let k = ret.len();
                    k >= 2 && ccw(ret[k - 2], ret[k - 1], p[i]) == CCW::Clockwise
                } {
                    ret.pop();
                }
                ret.push(p[i]);
            }
        };
    }

    // lower-hull
    half_hull!(0..n);
    // upper-hull
    half_hull!((0..n - 1).rev());

    ret.pop();
    ret
}

pub struct Line(Pt, Pt);
pub struct LineSegment(Pt, Pt);

pub fn projection(l: &Line, p: Pt) -> Pt {
    let t = dot(p - l.0, l.0 - l.1) / (l.0 - l.1).norm_sqr();
    l.0 + (l.0 - l.1) * t
}

pub fn intersect_lines(a: &Line, b: &Line) -> bool {
    cross(a.1 - a.0, b.1 - b.0) > EPS || cross(a.1 - a.0, b.0 - b.1) < EPS
}

pub fn intersect_segment_pt(a: &LineSegment, p: Pt) -> bool {
    (a.0 - p).norm() + (a.1 - p).norm() - (a.1 - a.0).norm() < EPS
}

pub fn intersect_segments(a: &LineSegment, b: &LineSegment) -> bool {
    ccw(a.0, a.1, b.0) as i32 * ccw(a.0, a.1, b.1) as i32 <= 0
        && ccw(b.0, b.1, a.0) as i32 * ccw(b.0, b.1, a.1) as i32 <= 0
}

pub fn distance_segment_pt(a: &LineSegment, p: Pt) -> f64 {
    let r = projection(&Line(a.0, a.1), p);
    if intersect_segment_pt(a, r) {
        (r - p).norm()
    } else {
        (a.0 - p).norm().min((a.1 - p).norm())
    }
}

pub fn distance_segments(a: &LineSegment, b: &LineSegment) -> f64 {
    if intersect_segments(a, b) {
        return 0.0;
    }
    distance_segment_pt(a, b.0)
        .min(distance_segment_pt(a, b.1))
        .min(distance_segment_pt(b, a.0))
        .min(distance_segment_pt(b, a.1))
}

#[derive(Debug)]
pub enum CrossLineSegment {
    None,
    Many,
    One(Pt),
}

impl CrossLineSegment {
    pub fn ok(&self) -> Option<Pt> {
        match self {
            &CrossLineSegment::One(p) => Some(p.clone()),
            _ => None,
        }
    }
}

pub fn cross_line_segment(a: &LineSegment, b: &LineSegment) -> CrossLineSegment {
    use CrossLineSegment::*;

    let ccw_a_b0 = ccw(a.0, a.1, b.0);
    let ccw_a_b1 = ccw(a.0, a.1, b.1);

    let ccw_b_a0 = ccw(b.0, b.1, a.0);
    let ccw_b_a1 = ccw(b.0, b.1, a.1);

    if ccw_a_b0 as i32 * ccw_a_b1 as i32 == 0 {
        if ccw_a_b1 == CCW::CounterClockwise || ccw_a_b1 == CCW::Clockwise {
            return One(b.0);
        } else if ccw_a_b0 == CCW::CounterClockwise || ccw_a_b0 == CCW::Clockwise {
            return One(b.1);
        } else {
            return Many;
        }
    }

    if ccw_b_a0 as i32 * ccw_b_a1 as i32 == 0 {
        if ccw_b_a1 == CCW::CounterClockwise || ccw_b_a1 == CCW::Clockwise {
            return One(a.0);
        } else if ccw_b_a0 == CCW::CounterClockwise || ccw_b_a0 == CCW::Clockwise {
            return One(a.1);
        } else {
            return Many;
        }
    }

    if ccw_a_b0 as i32 * ccw_a_b1 as i32 == -4 || ccw_b_a0 as i32 * ccw_b_a1 as i32 == -4 {
        return Many;
    }
    if ccw_a_b0 as i32 * ccw_a_b1 as i32 > 0 || ccw_b_a0 as i32 * ccw_b_a1 as i32 > 0 {
        return None;
    }

    assert!(ccw_a_b0 as i32 * ccw_a_b1 as i32 == -1);
    assert!(ccw_b_a0 as i32 * ccw_b_a1 as i32 == -1);

    // normalize a to [(0, 0), (0, 1)]
    let b0 = b.0 - a.0;
    let b1 = b.1 - a.0;
    let va = a.1 - a.0;

    let b0 = b0 / va;
    let b1 = b1 / va;
    let vb = b1 - b0;

    let t = -b0.im / vb.im;
    assert!(t >= 0.0 && t <= 1.0);

    One(b.0 + (b.1 - b.0) * t)
}

pub struct Circle {
    pub center: Pt,
    pub radius: f64,
}

impl Circle {
    fn new(center: Pt, radius: f64) -> Self {
        Self { center, radius }
    }
}

pub enum CircleRelationship {
    NotCross,
    Circumscribe,
    Cross,
    Inscribe,
    Inclusion,
}

pub fn circle_relationship(a: &Circle, b: &Circle) -> CircleRelationship {
    let d = (a.center - b.center).norm();

    if d > a.radius + b.radius {
        CircleRelationship::NotCross
    } else if d == a.radius + b.radius {
        CircleRelationship::Circumscribe
    } else if d > (a.radius - b.radius).abs() {
        CircleRelationship::Cross
    } else if d == (a.radius - b.radius).abs() {
        CircleRelationship::Inscribe
    } else {
        CircleRelationship::Inclusion
    }
}

/// Cross point of 2 circles
pub fn cross_circle(a: &Circle, b: &Circle) -> Option<(Pt, Pt)> {
    let c = (a.center - b.center).norm();

    if c > a.radius + b.radius || c + b.radius < a.radius || c + a.radius < b.radius {
        return None;
    }

    let x = (a.radius.powi(2) - b.radius.powi(2) + c.powi(2)) / (2.0 * c);
    let h = (a.radius.powi(2) - x.powi(2)).sqrt();

    let v = (b.center - a.center) / c;
    let w = v * Pt::from_polar(&1.0, &(std::f64::consts::PI / 2.0));
    let c1 = a.center + x * v + h * w;
    let c2 = a.center + x * v - h * w;

    Some((c1, c2))
}

pub fn cross_circle_line(a: &Circle, l: &Line) -> Option<(Pt, Pt)> {
    let p = projection(l, a.center);
    let d = (p - a.center).norm();
    if d > a.radius {
        return None;
    }
    let e = (a.radius.powi(2) - d.powi(2)).sqrt();
    let v = normalize(l.0 - l.1);
    Some((p + e * v, p - e * v))
}

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

mod complex {
    use std::fmt::Debug;
    use std::ops::{Add, Div, Mul, Sub};

    #[derive(PartialEq, Eq, Copy, Clone, Hash, Debug, Default)]
    #[repr(C)]
    pub struct Complex<T> {
        /// Real portion of the complex number
        pub re: T,
        /// Imaginary portion of the complex number
        pub im: T,
    }

    impl<T> Complex<T> {
        #[inline]
        pub fn new(re: T, im: T) -> Self {
            Complex { re, im }
        }
    }

    impl Complex<f64> {
        #[inline]
        pub fn conj(&self) -> Self {
            Self::new(self.re.clone(), -self.im.clone())
        }

        #[inline]
        pub fn norm(&self) -> f64 {
            self.re.hypot(self.im)
        }

        #[inline]
        pub fn norm_sqr(&self) -> f64 {
            self.re.clone() * self.re.clone() + self.im.clone() * self.im.clone()
        }

        #[inline]
        pub fn arg(&self) -> f64 {
            self.im.atan2(self.re)
        }

        #[inline]
        pub fn from_polar(r: &f64, theta: &f64) -> Complex<f64> {
            Complex::new(*r * theta.cos(), *r * theta.sin())
        }
    }

    impl<T: Add<Output = T>> Add for Complex<T> {
        type Output = Self;
        fn add(self, rhs: Self) -> Self::Output {
            Self::new(self.re + rhs.re, self.im + rhs.im)
        }
    }

    impl<T: Sub<Output = T>> Sub for Complex<T> {
        type Output = Self;
        fn sub(self, rhs: Self) -> Self::Output {
            Self::new(self.re - rhs.re, self.im - rhs.im)
        }
    }

    impl<T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T>> Mul for Complex<T> {
        type Output = Self;
        fn mul(self, rhs: Self) -> Self::Output {
            Self::new(
                self.re * rhs.re - self.im * rhs.im,
                self.re * rhs.im + self.im * rhs.re,
            )
        }
    }

    impl<T: Copy + Mul<Output = T>> Mul<T> for Complex<T> {
        type Output = Self;
        fn mul(self, rhs: T) -> Self::Output {
            Self::new(self.re * rhs, self.im * rhs)
        }
    }

    // impl<T: Copy + Mul<Output = T>> Mul<Complex<T>> for T {
    //     type Output = Complex<T>;
    //     fn mul(self, rhs: Complex<T>) -> Self::Output {
    //         Self::new(self * rhs.re, self * rhs.im)
    //     }
    // }

    impl Mul<Complex<f64>> for f64 {
        type Output = Complex<f64>;
        fn mul(self, rhs: Complex<f64>) -> Self::Output {
            Complex::new(self * rhs.re, self * rhs.im)
        }
    }

    impl<T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T>> Div
        for Complex<T>
    {
        type Output = Self;
        fn div(self, rhs: Self) -> Self::Output {
            let d = rhs.re * rhs.re + rhs.im * rhs.im;
            Self::new(
                (self.re * rhs.re + self.im * rhs.im) / d,
                (self.im * rhs.re - self.re * rhs.im) / d,
            )
        }
    }

    impl<T: Copy + Div<Output = T>> Div<T> for Complex<T> {
        type Output = Self;
        fn div(self, rhs: T) -> Self::Output {
            Self::new(self.re / rhs, self.im / rhs)
        }
    }
}

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

pub struct Scanner {
    buf: Vec<char>,
    cur: usize,
}

impl Scanner {
    pub fn new() -> Scanner {
        Scanner {
            buf: vec![],
            cur: 0,
        }
    }

    fn fill(&mut self) {
        let mut s = String::new();
        let len = std::io::stdin().read_line(&mut s).unwrap();
        if len == 0 {
            panic!("unexpected EOF");
        }
        for c in s.chars() {
            self.buf.push(c);
        }
    }

    pub fn next_char(&mut self) -> char {
        'outer: loop {
            if self.cur >= self.buf.len() {
                self.fill();
            }
            while self.cur < self.buf.len() {
                if !self.buf[self.cur].is_whitespace() {
                    break 'outer;
                }
                self.cur += 1;
            }
        }
        let ret = self.buf[self.cur];
        self.cur += 1;
        ret
    }

    pub fn next_word(&mut self) -> String {
        'outer: loop {
            if self.cur >= self.buf.len() {
                self.fill();
            }
            while self.cur < self.buf.len() {
                if !self.buf[self.cur].is_whitespace() {
                    break 'outer;
                }
                self.cur += 1;
            }
        }

        let mut s = String::new();
        while self.cur < self.buf.len() && !self.buf[self.cur].is_whitespace() {
            s.push(self.buf[self.cur]);
            self.cur += 1;
        }
        s
    }

    pub fn next<T: Reader>(&mut self) -> T {
        T::read(self)
    }

    pub fn next_vec_len<T: Reader>(&mut self) -> Vec<T> {
        let n: usize = self.next();
        self.next_vec(n)
    }

    pub fn next_vec<T: Reader>(&mut self, n: usize) -> Vec<T> {
        (0..n).map(|_| self.next()).collect()
    }
}

pub trait Reader {
    fn read(sc: &mut Scanner) -> Self;
}

impl<T: Reader> Reader for complex::Complex<T> {
    fn read(sc: &mut Scanner) -> Self {
        Self::new(sc.next(), sc.next())
    }
}

impl Reader for char {
    fn read(sc: &mut Scanner) -> Self {
        sc.next_char()
    }
}

macro_rules! impl_for_from_str {
    ($typ:ty) => {
        impl Reader for $typ {
            fn read(sc: &mut Scanner) -> Self {
                sc.next_word().parse().unwrap()
            }
        }
    };
}

impl_for_from_str!(i8);
impl_for_from_str!(u8);
impl_for_from_str!(i16);
impl_for_from_str!(u16);
impl_for_from_str!(i32);
impl_for_from_str!(u32);
impl_for_from_str!(i64);
impl_for_from_str!(u64);
// impl_for_from_str!(i128);
// impl_for_from_str!(u128);
impl_for_from_str!(isize);
impl_for_from_str!(usize);
impl_for_from_str!(f32);
impl_for_from_str!(f64);
impl_for_from_str!(String);



Measured memory_bytes: 5132