#include <bits/stdc++.h>
#define EPS 1e-10
#define PI 3.141592653589793238
#define equals(a,b) (fabs((a)-(b)) < EPS)
#define rep(i,n) for(int i=0;i<n;++i)
typedef long long ll;
using namespace std;

static const int COUNTER_CLOCKWISE = 1;
static const int CLOCKWISE = -1;
static const int ONLINE_BACK = 2;
static const int ONLINE_FRONT = -2;
static const int ON_SEGMENT = 0;

// 点
struct Point {
    double x,y;
    Point(){}
    Point(double x, double y) : x(x),y(y){}
    Point operator+(Point p) {return Point(x+p.x, y+p.y);}
    Point operator-(Point p) {return Point(x-p.x, y-p.y);}
    Point operator*(double k){return Point(x*k,y*k);}
    Point operator/(double k){return Point(x/k,y/k);}
    double norm(){return x*x+y*y;}
    double abs(){sqrt(norm());}
    bool operator == (const Point &p) const {return equals(x,p.x)&&equals(y,p.y);}
    bool operator < (const Point &p) const {
        return x!=p.x?x<p.x:y<p.y;
        //grid-point only
        //return !equals(x,p.x)?x<p.x:!equals(y,p.y)?y<p.y:0;
    }

};

typedef Point P;
typedef vector<Point> Polygon;

double norm(P a){return a.x*a.x+a.y*a.y;}
double abs(P a){return sqrt(norm(a));}

// 線分
struct Segment {
    Point p1,p2;
    Segment(){}
    Segment(Point p1, Point p2):p1(p1),p2(p2){}
};

typedef Segment Line;

struct Circle {
    Point c;
    double r;
    Circle(){}
    Circle(Point c, double r):c(c),r(r){}
};

// 法線ベクトル
Point normal(Point p){return Point(-p.y,p.x);}

// 内積
double dot(Point a, Point b) {return a.x*b.x + a.y*b.y;}

// 外積
double cross(Point a, Point b) {return a.x*b.y-a.y*b.x;}
// 2直線の直行判定
bool is_orthogonal(Point a1, Point a2, Point b1, Point b2) {
    return equals(dot(a1-a2, b1-b2), 0.0);
}
// 2直線の平行判定
bool is_parallel(Point a1, Point a2, Point b1, Point b2) {
    return equals(cross(a1-a2, b1-b2), 0.0);
}
// 点cが直線ab上にあるかないか
bool is_point_on_INF_line(Point a, Point b, Point c) {
    return equals(cross(b-a,c-a), 0.0);
}
// 点cが線分ab上にあるかないか
bool is_point_on_LIMITED_line(Point a, Point b, Point c) {
    return (Point(a-c).abs()+Point(c-b).abs() < Point(a-b).abs() + EPS);
}
// 直線と点の距離
double distance_l_p(Point a, Point b, Point c) {return abs(cross(b-a, c-a)) / (b-a).abs();}

// 点a,bを端点とする線分と点cとの距離
double distance_ls_p(Point a, Point b, Point c) {
    if (dot(b-a, c-a) < EPS) return (c-a).abs();
    if (dot(a-b, c-b) < EPS) return (c-b).abs();
    return abs(cross(b-a, c-a)) / (b-a).abs();
}

// 点が線分のどちら側にあるかを計算
int ccw(Point p0,Point p1,Point p2) {
    P a = p1-p0;
    P b = p2-p0;
    if(cross(a,b) > EPS) return COUNTER_CLOCKWISE;
    if(cross(a,b) < -EPS) return CLOCKWISE;
    if(dot(a,b) < -EPS) return ONLINE_BACK;
    if(a.norm()<b.norm()) return ONLINE_FRONT;
    return ON_SEGMENT;
}

// a1,a2を端点とする線分とb1,b2を端点とする線分の交差判定
bool intersectSS(Point p1, Point p2, Point p3, Point p4) {
    return (ccw(p1,p2,p3)*ccw(p1,p2,p4) <= 0&&ccw(p3,p4,p1)*ccw(p3,p4,p2) <= 0 );
}

// a1,a2を端点とする線分とb1,b2を端点とする線分の交差判定
bool intersectSS(Segment s1, Segment s2) {
    return intersectSS(s1.p1,s1.p2,s2.p1,s2.p2);
}

// 直線と点の距離
double getDistanceLP(Line l, Point p) {
    return abs(cross(l.p2-l.p1,p-l.p1)/abs(l.p2-l.p1));
}

// 線分と点の距離
double getDistanceSP(Segment s,Point p){
    if(dot(s.p2-s.p1,p-s.p1) < 0.0 ) return abs(p-s.p1);
    if(dot(s.p1-s.p2,p-s.p2) < 0.0 ) return abs(p-s.p2);
    return getDistanceLP(s,p);
}

// 線分と線分の距離
double getDistanceSS(Segment s1,Segment s2){
    if(intersectSS(s1,s2)) return 0.0;
    return min(min(getDistanceSP(s1,s2.p1),getDistanceSP(s1,s2.p2)),
    min(getDistanceSP(s2,s1.p1),getDistanceSP(s2,s1.p2)));
}

// a1,a2を端点とする線分とb1,b2を端点とする線分の交点計算
// 前提として交差していることが必要
P getCrossPointSS(P a1, P a2, P b1, P b2) {
    P b = b2-b1;
    double d1 = abs(cross(b, a1-b1));
    double d2 = abs(cross(b, a2-b1));
    double t = d1/(d1+d2);
    return a1+(a2-a1)*t;
}

// a1,a2を通る直線とb1,b2を通る直線の交点計算
// 前提として平行でないことが必要
P intersection_l(P a1, P a2, P b1, P b2) {
    P a = a2-a1; P b = b2-b1;
    return a1+a*cross(b, b1-a1)/cross(b, a);
}

// 射影
Point project(Segment s, Point p) {
    Point base = s.p2-s.p1;
    double r = dot(p-s.p1,base)/norm(base);
    return s.p1+base*r;
}

double area(Polygon s){
    double res=0;
    for(int i=0;i<(int)s.size();i++){
        res+=cross(s[i],s[(i+1)%s.size()])/2.0;
    }
    return res;
}

bool isConvex(Polygon p){
    bool f=1;
    int n=p.size();
    for(int i=0;i<n;i++){
        int t=ccw(p[(i+n-1)%n],p[i],p[(i+1)%n]);
        f&=t!=CLOCKWISE;
    }
    return f;
}

// IN:2 ON:1 OUT:0
int contains(Polygon g,Point p){
    int n=g.size();
    bool x=false;
    for(int i=0;i<n;i++){
        Point a=g[i]-p,b=g[(i+1)%n]-p;
        if(fabs(cross(a,b)) < EPS && dot(a,b) < EPS) return 1;
        if(a.y>b.y) swap(a,b);
        if(a.y < EPS && EPS < b.y && cross(a,b) > EPS ) x = !x;
    }
    return (x?2:0);
}

bool sort_x(Point a,Point b){
    return a.x!=b.x?a.x<b.x:a.y<b.y;
}

bool sort_y(Point a,Point b){
    return a.y!=b.y?a.y<b.y:a.x<b.x;
}

Polygon convex_hull(Polygon ps){
    int n=ps.size();
    sort(ps.begin(),ps.end(),sort_y);
    int k=0;
    Polygon qs(n*2);
    for(int i=0;i<n;i++){
        while(k>1&&cross(qs[k-1]-qs[k-2],ps[i]-qs[k-1])<0) k--;
        qs[k++]=ps[i];
    }
    for(int i=n-2,t=k;i>=0;i--){
        while(k>t&&cross(qs[k-1]-qs[k-2],ps[i]-qs[k-1])<0) k--;
        qs[k++]=ps[i];
    }
    qs.resize(k-1);
    return qs;
}

double diameter(Polygon s){
    Polygon p=s;
    int n=p.size();
    if(n==2) return abs(p[0]-p[1]);
    int i=0,j=0;
    for(int k=0;k<n;k++){
        if(p[i]<p[k]) i=k;
        if(!(p[j]<p[k])) j=k;
    }
    double res=0;
    int si=i,sj=j;
    while(i!=sj||j!=si){
        res = max(res,abs(p[i]-p[j]));
        if (cross(p[(i+1)%n]-p[i],p[(j+1)%n]-p[j])<0.0){
            i=(i+1)%n;
        }
        else {
            j=(j+1)%n;
        }
    }
    return res;
}

int main(int argc, char const *argv[]) {
    int n;cin>>n;
    Polygon pl;
    rep(i,n) {
        double x,y;cin>>x>>y;
        pl.push_back(Point(x,y));
    }
    cout << fixed << setprecision(13);
    cout << diameter(pl) << endl;
    return 0;
}


Measured memory_bytes: 6792