#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <queue>
#include <numeric>
#include <iomanip>
#include <map>
#include <complex>
using namespace std;
using ll = long long;
#define MOD 1000000007
#define INF 1LL << 59

using Point = complex<double>;

istream &operator>>(istream &is, Point &p)
{
    double a, b;
    is >> a >> b;
    p = Point(a, b);
    return is;
}

ostream &operator<<(ostream &os, Point &p)
{
    os << fixed << setprecision(10) << p.real() << " " << p.imag();
}

const double EPS = 1e-10, PI = acos(-1);
inline bool eq(double a, double b) { return fabs(b - a) < EPS; }

//二つのスカラーが等しいか
#define EQ(a, b) (abs((a) - (b)) < EPS)
//二つのベクトルが等しいか
#define EQV(a, b) (EQ((a), real(), (b).real()) && EQ((a), imag(), (b).imag()))

namespace std
{
bool operator<(const Point &a, const Point &b)
{
    return a.real() != b.real() ? a.real() < b.real() : a.imag() < b.imag();
}
} // namespace std

struct Line
{
    Point a, b;
    Line() {}
    Line(Point a, Point b) : a(a), b(b) {}
    //a, bはそれぞれ座標を指す. これより一つの「line」に対して二個の点を持つことになる
    Line(double A, double B, double C) // Ax + By = C
    {
        if (eq(A, 0))
            a = Point(0, C / B), b = Point(1, C / B);
        else if (eq(B, 0))
            b = Point(C / A, 0), b = Point(C / A, 1);
        else
            a = Point(0, C / B), b = Point(C / A, 0);
    }

    friend ostream &operator<<(ostream &os, Line &p)
    {
        return os << p.a << " to " << p.b;
    }

    friend istream &operator>>(istream &is, Line &a)
    {
        return is >> a.a >> a.b;
    }
};

struct Segment : Line
{
    Segment() {}

    Segment(Point a, Point b) : Line(a, b) {}
};

struct Circle
{
    Point p;
    double r;

    Circle() {}

    Circle(Point p, double r) : p(p), r(r) {}
};

using Points = vector<Point>;
using Polygon = vector<Point>;
using Segments = vector<Segment>;
using Lines = vector<Line>;
using Circles = vector<Circle>;

double dot(const Point a, const Point b)
{
    return real(a) * real(b) + imag(a) * imag(b);
}

double cross(const Point a, const Point b)
{
    return real(a) * imag(b) - imag(a) * real(b);
}
int ccw(const Point &a, Point b, Point c)
{
    b = b - a, c = c - a;
    if (cross(b, c) > EPS)
        return +1; // "COUNTER_CLOCKWISE"
    if (cross(b, c) < -EPS)
        return -1; // "CLOCKWISE"
    if (dot(b, c) < 0)
        return +2; // "ONLINE_BACK"
    if (norm(b) < norm(c))
        return -2; // "ONLINE_FRONT"
    return 0;      // "ON_SEGMENT"
}

bool parallel(const Line &a, const Line &b)
{
    return abs(cross(a.b - a.a, b.b - b.a)) < EPS;
}

bool orthogonal(const Line &a, const Line &b)
{
    return abs(dot(a.a - a.b, b.a - b.b)) < EPS;
}

Point projection(const Line &l, const Point &p)
{
    double t = dot(p - l.a, l.a - l.b) / norm(l.a - l.b);
    return l.a + (l.a - l.b) * t;
}

Point projection(const Segment &l, const Point &p)
{
    double t = dot(p - l.a, l.a - l.b) / norm(l.a - l.b);
    return l.a + (l.a - l.b) * t;
}

Point reflection(const Line &l, const Point &p)
{
    return p + (projection(l, p) - p) * 2.0;
}

bool Intersect(const Line &l, const Point &p)
{
    return abs(ccw(l.a, l.b, p)) != 1;
}

bool intersect(const Line &l, const Line &m)
{
    return abs(cross(l.b - l.a, m.b - m.a)) > EPS || abs(cross(l.b - l.a, m.b - l.a)) < EPS;
}

bool intersect(const Segment &s, const Point &p)
{
    return ccw(s.a, s.b, p) == 0;
}

bool intersect(const Line &l, const Segment &s)
{
    return cross(l.b - l.a, s.a - l.a) * cross(l.b - l.a, s.b - l.a) < EPS;
}

bool intersect(const Segment &s, const Segment &t)
{
    return ccw(s.a, s.b, t.a) * ccw(s.a, s.b, t.b) <= 0 && ccw(t.a, t.b, s.a) * ccw(t.a, t.b, s.b) <= 0;
}

Point crosspoint(const Line &l, const Line &m)
{
    double A = cross(l.b - l.a, m.b - m.a);
    double B = cross(l.b - l.a, l.b - m.a);
    if (abs(A) < EPS && abs(B) < EPS)
        return m.a;
    return m.a + (m.b - m.a) * B / A;
}

Point crosspoint(const Segment &l, const Segment &m)
{
    double A = cross(l.b - l.a, m.b - m.a);
    double B = cross(l.b - l.a, l.b - m.a);
    if (abs(A) < EPS && abs(B) < EPS)
        return m.a;
    return m.a + (m.b - m.a) * B / A;
}

double distance(const Point &a, const Point &b)
{
    return abs(a - b);
}

double distance(const Line &l, const Point &p)
{
    return abs(p - projection(l, p));
}

double distance(const Line &l, const Line &m)
{
    return intersect(l, m) ? 0 : distance(l, m.a);
}

double distance(const Segment &s, const Point &p)
{
    Point r = projection(s, p);
    if (intersect(s, r))
        return abs(r - p);
    return min(abs(s.a - p), abs(s.b - p));
}

double distance(const Segment &a, const Segment &b)
{
    if (intersect(a, b))
        return 0;
    return min({distance(a, b.a), distance(a, b.b), distance(b, a.a), distance(b, a.b)});
}

double distance(const Line &l, const Segment &s)
{
    if (intersect(l, s))
        return 0;
    return min(distance(l, s.a), distance(l, s.b));
}

double area2(const Polygon &p)
{
    double A = 0;
    for (int i = 0; i < (int)p.size(); ++i)
    {
        A += cross(p[i], p[(i + 1) % p.size()]);
    }
    return A;
}

bool isConvex(const Polygon &p)
{
    for (int i = 0; i < (int)p.size(); ++i)
    {
        if (cross(p[(i - 1 + p.size()) % p.size()] - p[i], p[i] - p[(i + 1) % p.size()]) < -EPS)
            return false;
    }
    return true;
}

enum
{
    OUT,
    ON,
    IN
};

int contains(const Polygon &Q, const Point &p)
{
    bool in = false;
    for (int i = 0; i < Q.size(); ++i)
    {
        Point a = Q[i] - p, b = Q[(i + 1) % Q.size()] - p;
        if (a.imag() > b.imag())
            swap(a, b);
        if (a.imag() <= 0 && 0 < b.imag() && cross(a, b) < 0)
            in = !in;
        if (cross(a, b) == 0 && dot(a, b) <= 0)
            return ON;
    }
    return in ? IN : OUT;
}

Polygon convex_hull(Polygon &p)
{
    int n = (int)p.size(), k = 0;
    if (n <= 2)
        return p;
    sort(p.begin(), p.end());
    vector<Point> ch(n * 2);

    for (int i = 0; i < n; ch[k++] = p[i++])
    {
        while (k >= 2 && cross(ch[k - 1] - ch[k - 2], p[i] - ch[k - 1]) < 0)
            --k;
    }

    for (int i = n - 2, t = k + 1; i >= 0; ch[k++] = p[i--])
    {
        while (k >= t && cross(ch[k - 1] - ch[k - 2], p[i] - ch[k - 1]) < 0)
            --k;
    }
    ch.resize(k - 1);
    return ch;
}

double convex_diameter(const Polygon &p)
{
    int n = (int)p.size();
    if (n == 2)
        return abs(p[0] - p[1]);

    int is = 0, js = 0;
    for (int i = 1; i < n; ++i)
    {
        if (imag(p[i]) > imag(p[is]))
            is = i;
        if (imag(p[i]) < imag(p[js]))
            js = i;
    }

    double res = abs(p[is] - p[js]);
    int i, maxi, j, maxj;
    i = maxi = is;
    j = maxj = js;
    do
    {
        if (cross(p[(i + 1) % n] - p[i], p[(j + 1) % n] - p[j]) >= 0)
            j = (j + 1) % n;
        else
            i = (i + 1) % n;
        res = max(res, abs(p[i] - p[j]));
    } while (i != is || j != js);
    return res;
}
int main()
{
    int N;
    cin >> N;
    Polygon p(N);
    for (auto &s : p)
        cin >> s;

    cout << fixed << setprecision(7) << convex_diameter(p) << endl;
    return 0;
}


Measured memory_bytes: 4136