#include <bits/stdc++.h>
#include<cmath>
using ll = long long;
using namespace std;
#define rep(i,n) for(int i=0, i##_len=(int)(n); i<i##_len; i++)
#define reps(i,n) for(int i=1 , i##_len=(int)(n);i<=i##_len;i++)
#define rrep(i,n) for(int i=((int)(n)-1);i>=0;i--)
#define rreps(i,n) for(int i=((int)(n));i>0;i--)
#define all(x) (x).begin(), (x).end()
#define F first
#define S second
#define mp make_pair
#define pb push_back
#define solve(a) ((a)?"Yes":"No")
typedef vector<long long> V;
typedef vector<V> VV;
typedef pair<long long , long long> P;
typedef vector<P> VP;
template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1;} return 0;}
template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1;} return 0;}
const long long INFLL = 1LL<<62;
const int INF = 1<<30;
const double PI=acos(-1);
//include UnionFind
//union by size + path having
class UnionFind{
	public:
	vector<long long> par; // 各元の親を表す配列
	vector<long long> siz; // 素集合のサイズを表す配列(1 で初期化)


	// Constructor
	UnionFind(long long sz_) : par(sz_), siz(sz_,1LL){
		for (long long i = 0; i < sz_; ++i)
		par[i] = i; // 初期では親は自分自身
	}
	void init(long long sz_){
		par.resize(sz_);
		siz.assign(sz_,1LL); // resize だとなぜか初期化されなかった
		for (long long i = 0; i < sz_; ++i)
			par[i] = i; // 初期では親は自分自身
	}
	// Member Function


	// Find
	long long root(long long x){ // 根の検索
		while (par[x] != x){
			x = par[x] = par[par[x]]; // x の親の親を x の親とする
		}
		return x;
	}
	// 結合(Unite, Merge)
	bool merge(long long x, long long y){
		x = root(x);
		y = root(y);
		if (x == y){
			return false;
		}
		// merge technique（データ構造をマージするテク．小を大にくっつける）
		if (siz[x] < siz[y]){
			swap(x, y);
		}
		siz[x] += siz[y];
		par[y] = x;
		return true;
	}


	// 連結判定(つながっているときはture,つながっていなければfalse)
	bool issame(long long x, long long y){ 
		return root(x) == root(y);
	}


	// 素集合のサイズ
	long long size(long long x){ 
		return siz[root(x)];
	}
};

//クラスカル法(隣接リストから最小全域木を隣接リストで返す)
//計算量 (辺)log(頂点)
vector<vector<pair<int, double>>> Kruskal(vector<vector<pair<int, double>>> pathlist)
{
	vector<pair<double, pair<int, int>>> paths;
	int edge=pathlist.size();
	UnionFind conect(edge);
	vector<vector<pair<int, double>>> res(edge);
	rep(i, edge){
		for (auto itr = pathlist[i].begin(); itr != pathlist[i].end(); itr++){
			paths.emplace_back(itr->S, mp(i, itr->F));
		}
	}
	sort(all(paths));
	for (auto at = paths.begin(); at != paths.end(); at++){
		if (conect.merge(at->S.F, at->S.S)){
			res[at->S.F].emplace_back(at->S.S, at->F);
			res[at->S.S].emplace_back(at->S.F, at->F);
			if (conect.size(0) == edge){
				break;
			}
		}
	}
	return res;
}
int main(){
	vector<vector<pair<int, double>>> res;
	int n = 1;
	double ans=0.0;
	vector<vector<pair<int, double>>> path ;
	vector<tuple<double,double,double,double>>data;
	while (1)
	{
		cin >> n;
		if (n == 0)
		{
			break;
		}
		path.clear();
		path.resize(n);
		//data.resize(n);
		data.clear();
		res.clear();
		ans=0.0;
		double x,y,z,r;
		rep(i,n){
			cin>>x>>y>>z>>r;
			data.push_back(make_tuple(x,y,z,r));
		}
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				x = get<0>(data[i]) - get<0>(data[j]);
				y = get<1>(data[i]) - get<1>(data[j]);
				z = get<2>(data[i]) - get<2>(data[j]);
				r = get<3>(data[i]) + get<3>(data[j]);
				path[i].emplace_back(j, max(0.0, sqrt(x*x+y*y+z*z) -r));
			//	clog<<i<<" "<<j<<endl;
			//	clog<<x<<" "<<y<<" "<<z<<" "<<r<<endl;
			//	clog<<path[i].back().S<<endl;
			}
		}
		res=Kruskal(path);
		for(int i=0;i<n;i++){
			for(auto itr=res[i].begin();itr!=res[i].end();itr++){
				ans+=itr->S;
			}
		}
		printf("%.3lf\n",ans/2.0);
	}
}


Measured memory_bytes: 3860