#include using namespace std; using ll = long long; #define rep(i,n) for(ll i = 0; i < n; i++) #define all(a) a.begin(),a.end() using Real = long double; using Point = complex; const Real EPS = 1e-8, PI = acos(-1); istream &operator>>(istream &is, Point& p) { Real a, b; is >> a >> b; p = Point(a, b); return is; } ostream &operator<<(ostream &os, Point p) { return os << fixed << p.real() << " " << p.imag(); } Point operator*(Point p, Real d) { return Point(real(p) * d, imag(p) * d); } Point operator/(Point p, Real d) { return Point(real(p) / d, imag(p) / d); } inline bool equal(Real a, Real b){ return fabs(a - b) < EPS; } Point unit(Point a) { return a / abs(a); } Point normal(Point a) { return a * Point(0, 1); } Point normalUnit(Point a) { return unit(normal(a)); } Real dot(Point a, Point b){ return a.real() * b.real() + a.imag() * b.imag(); } Real cross(Point a, Point b){ return (a.real() * b.imag() - a.imag() * b.real()); } Point rotate(Point a, double theta){ return Point(cos(theta) * a.real() - sin(theta) * a.imag(), sin(theta) * a.real() + cos(theta) * a.imag()); } Real radianToDegree(Real r) { return r * 180 / PI; } Real degreeToRadian(Real d) { return d * PI / 180; } Real getAngle(Point a, Point b, Point c){ const Point ab(b - a), bc(c - b); Real s = atan2(ab.imag(), ab.real()), t = atan2(bc.imag(), bc.real()); if(s > t) swap(s,t); Real theta = t - s; return min(theta, 2 * PI - theta); } struct Line{ Point a,b; Line() = default; Line(Point A, Point B) : a(A), b(B) {} // ax + by = c Line(Real A, Real B, Real C){ if(equal(A, 0)) a = Point(0, C / B), b = Point(1, C / B); else if(equal(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; } }; using Segment = Line; // 点pから線lに垂線を下ろした時の交点 Point projection(Line l, Point p){ Real t = dot(p - l.a, l.b - l.a) / norm(l.b - l.a); return l.a + (l.b - l.a) * t; } int ccw(Point a, Point b, Point c){ b -= a; c -= a; // 反時計回り if(cross(b, c) > EPS) return 1; // 時計回り if(cross(b, c) < -EPS) return -1; // c-a-bの順番で点がある if(dot(b, c) < 0) return -2; // a-b-cの順番で点がある if(norm(b) < norm(c)) return 2; // a-b の間にcがある return 0; } bool isParallel(Line a, Line b){ return equal(cross(a.b - a.a, b.b - b.a), 0); } bool isOrthogonal(Line a, Line b){ return equal(dot(a.b - a.a, b.b - b.a), 0); } bool isIntersect(Segment s, 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(Line s, Line t){ Real d1 = cross(s.b - s.a, t.b - t.a); Real d2 = cross(s.b - s.a, s.b - t.a); if(equal(d1, 0) && equal(d2, 0)){ if( ccw(t.a, t.b, s.a) == 0) return s.a; if( ccw(t.a, t.b, s.b) == 0) return s.b; if( ccw(s.a, s.b, t.a) == 0) return t.a; if( ccw(s.a, s.b, t.b) == 0) return t.b; } return t.a + (t.b - t.a) * (d2 / d1); } Real distance(Segment l, Point p){ if(dot(l.b - l.a, p - l.a) < EPS) return abs(p - l.a); if(dot(l.a - l.b, p - l.b) < EPS) return abs(p - l.b); return abs(cross(l.b - l.a, p - l.a)) / abs(l.b - l.a); } Real ldistance(Line l, Point p){ return abs(cross(l.b - l.a, p - l.a)) / abs(l.b - l.a); } Real distance(Segment s, Segment t){ if(isIntersect(s,t)) return 0; Real res = distance(s,t.a); res = min(res, distance(s, t.b)); res = min(res, distance(t, s.a)); res = min(res, distance(t, s.b)); return res; } Real area(vector& p){ Real res = 0; int n = p.size(); rep(i,n){ res += cross(p[i], p[(i + 1) % n]); } return res / 2; } bool isConvex(vector& p){ bool res = true; int n = p.size(); rep(i,n){ if(cross(p[(i+1)%n]-p[i], p[(i+2)%n] - p[(i+1)%n]) < -EPS){ res = false; } } return res; } int isContain(vector& g, Point& p){ bool in = false; int n = g.size(); rep(i,n){ Point a = g[i] - p, b = g[(i+1)%n] - p; if(imag(a) > imag(b)){ swap(a, b); } if(imag(a) <= EPS && EPS < imag(b) && cross(a, b) < - EPS){ in = !in; } if( equal(cross(a, b), 0) && dot(a, b) <= 0){ return 1; } } return in ? 2 : 0; } // 直線を含めない場合は <=-EPS を convexHull(vector& p){ int n = (int)p.size(), k = 0; sort(all(p), [](const Point &a, const Point &b) { return (real(a) != real(b) ? real(a) < real(b) : imag(a) < imag(b)); }); vector ch(2 * n); 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]) <= -EPS) --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]) <= -EPS) --k; } ch.resize(k - 1); return ch; } Real convexDiameter(vector& p){ int n = (int)p.size(); 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; } Real mx = abs(p[js] - p[is]); int i = is, j = 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; } if(abs(p[i] - p[j]) > mx){ mx = abs(p[i] - p[j]); } }while(i != is || j != js); return mx; } vector convexCut(vector& p, Line l){ vector res; int n = (int)p.size(); for(int i = 0; i < n; i++){ if(ccw(l.a, l.b, p[i]) != -1) res.emplace_back(p[i]); if(ccw(l.a, l.b, p[i]) * ccw(l.a, l.b, p[(i + 1) % n]) < 0){ res.emplace_back(crossPoint(Line(p[i], p[(i + 1) % n]), l)); } } return res; } struct Circle{ Point p; Real r; Circle() = default; Circle(Point p, Real r) : p(p), r(r) {} }; int isIntersect(Circle a, Circle b){ double d = abs(a.p - b.p); // 2つの円が離れている場合 if(d > a.r + b.r + EPS) { return 4; } // 外接している場合 if(equal(d, a.r + b.r)) { return 3; } // 内接している場合 if(equal(d, abs(a.r - b.r))) { return 1; } // 内包している場合 if(d < abs(a.r - b.r) - EPS) { return 0; } return 2; } Circle inCircle(Point a, Point b, Point c){ Real A = abs(b - c), B = abs(a - c), C = abs(a - b); Point p(A * real(a) + B * real(b) + C * real(c), A * imag(a) + B * imag(b) + C * imag(c)); p /= (A + B + C); Real r = distance(Line(a, b), p); return Circle(p, r); } Circle outCircle(Point a, Point b, Point c){ Point f = (a + b) / 2, s = (b + c) / 2; Line A(f, normal(b - f) + f), B(s, normal(c - s) + s); Point m = crossPoint(A,B); Real r = abs(a - m); r = max(r, abs(b - m)); r = max(r, abs(c - m)); return Circle(m, r); } vector crossPoint(Circle c, Line l) { vector res; Real d = ldistance(l, c.p); // 交点を持たない if(d > c.r + EPS) { return res; } // 接する Point h = projection(l, c.p); if(equal(d, c.r)) { res.emplace_back(h); return res; } Point e = unit(l.b - l.a); Real ph = sqrt(c.r * c.r - d * d); res.emplace_back(h - e * ph); res.emplace_back(h + e * ph); return res; } vector crossPoint(Circle c1, Circle c2) { vector res; int mode = isIntersect(c1, c2); Real d = abs(c1.p - c2.p); if(mode == 4) { return res; } if(mode == 0) { return res; } if(mode == 3) { Real t = c1.r / (c1.r + c2.r); res.emplace_back(c1.p + (c2.p - c1.p) * t); return res; } if(mode == 1) { if(c2.r < c1.r - EPS) { res.emplace_back(c1.p + (c2.p - c1.p) * (c1.r / d)); } else { res.emplace_back(c2.p + (c1.p - c2.p) * (c2.r / d)); } return res; } Real rc1 = (c1.r * c1.r + d * d - c2.r * c2.r) / (2 * d); Real rs1 = sqrt(c1.r * c1.r - rc1 * rc1); if(c1.r - abs(rc1) < EPS) { rs1 = 0; } Point e12 = (c2.p - c1.p) / abs(c2.p - c1.p); res.emplace_back(c1.p + rc1 * e12 + rs1 * e12 * Point(0, 1)); res.emplace_back(c1.p + rc1 * e12 + rs1 * e12 * Point(0, -1)); return res; } vector tangentToCircle(Point p, Circle c) { return crossPoint(c, Circle(p, sqrt(norm(c.p - p) - c.r * c.r))); } vector tangent(Circle a, Circle b) { vector ret; // 2円の中心間の距離 Real g = abs(a.p - b.p); // 円が内包されている場合 if(equal(g, 0)) { return ret; } Point u = unit(b.p - a.p); Point v = rotate(u, PI / 2); for(int s : {-1, 1}) { double h = (a.r + b.r * s) / g; if(equal(h * h, 1)) { ret.emplace_back(a.p + (h > 0 ? u : -u) * a.r, a.p + (h > 0 ? u : -u) * a.r + v); } else if(1 - h * h > 0) { Point U = u * h, V = v * sqrt(1 - h * h); ret.emplace_back(a.p + (U + V) * a.r, b.p - (U + V) * (b.r * s)); ret.emplace_back(a.p + (U - V) * a.r, b.p - (U - V) * (b.r * s)); } } return ret; } void solve() { Point p1, p2; Real r1, r2; cin >> p1 >> r1 >> p2 >> r2; Circle c1(p1, r1), c2(p2, r2); vector ret = tangent(c1, c2); Real ans = 0; rep(i,ret.size()) { Real a, b, c; // ax + by + c = 0 という形にする a = ret[i].b.imag() - ret[i].a.imag(); b = ret[i].a.real() - ret[i].b.real(); c = - (a * ret[i].a.real() + b * ret[i].a.imag()); Real mx = max({abs(a), abs(b), abs(c)}); a /= mx; b /= mx; c /= mx; ans += abs(a + b + c); } cout << ans << endl; } int main() { cout << fixed << setprecision(20); int t; cin >> t; while(t--) solve(); }