// Original C++ solution (slower):
#include <iostream>

#include <cstdio>

#include <cmath>

#include <cstdlib>

#include <cstring>

#include <vector>

#include <queue>

#include <algorithm>

using namespace std;



struct Node

{

	int pos;

	int speed;

	double cost;

	int prev;



	bool operator<(const Node& r) const{

		return cost>r.cost;

	}

};



int main()

{

	int n,m;

	while(cin>>n>>m && n)

	{

		vector<Node> G[n];

		int s,g;

		cin>>s>>g;

		--s;

		--g;

		for(int i = 0; i < m; ++i)

		{

			int a,b,c;

			double d;

			cin>>a>>b>>d>>c;

			--a;

			--b;

			G[a].push_back({b,c,d,a});

			G[b].push_back({a,c,d,b});

		}



		priority_queue<Node> q;

		for(Node e: G[s])

		{

			int nv = 1;

			if(nv <= e.speed)

				q.push({e.pos,nv,e.cost/nv,s});

		}



		int d[40][40][40] = {};

		memset(d,0,sizeof(d));

		bool flag = 0;

		while(q.size())

		{

			Node tmp = q.top();

			q.pop();

			

			if(d[tmp.pos][tmp.speed][tmp.prev]++) continue;

			if(tmp.pos == g && tmp.speed == 1)

			{

				printf("%.5lf\n",tmp.cost);

				flag = 1;

				break;

			}



			if(tmp.speed != 0)

				for(Node e: G[tmp.pos])

					for(int nv = tmp.speed-1; nv <= tmp.speed + 1; nv++)

						if(nv > 0 && nv <= e.speed && e.pos != tmp.prev)

							q.push({e.pos,nv,tmp.cost+e.cost/nv,tmp.pos});

		}

		if(!flag)

			cout<<"unreachable"<<endl;

	}

	return 0;

}


// Optimized solution to the same problem (faster):
#include<bits/stdc++.h>

using namespace std;

using ll = long long;





struct Edge {

	int to, dist, cap;

	Edge(int t, int d, int c) {

		to = t;

		dist = d;

		cap = c;

	}

};



struct State {

	int pos, prev, v;

	double time;

	State(int p, int pr, int v_, double t) {

		pos = p;

		prev = pr;

		v = v_;

		time = t;

	}

	bool operator<(const State& right) const {

		return time > right.time;

	}

};



double min_time[32][32][32];

bool visited[32][32][32];



int main() {

	while(true) {

		int n, m;

		cin >> n >> m;

		if (n == 0) break;



		int start, goal;

		cin >> start >> goal;

		start--;

		goal--;



		vector<vector<Edge>> G(n);

		for (int i = 0; i<m; i++) {

			int x, y, d, c;

			cin >> x >> y >> d >> c;

			x--;

			y--;

			G[x].emplace_back(y, d, c);

			G[y].emplace_back(x, d, c);

		}



		memset(visited, 0, sizeof(visited));

		priority_queue<State> que;

		que.emplace(start, start, 0, 0);

		visited[start][start][0] = true;

		min_time[start][start][0] = 0;



		double ans = -1;

		while (!que.empty()) {

			auto s = que.top();

			que.pop();

			if (min_time[s.pos][s.prev][s.v] < s.time) continue;

			if (s.pos == goal && s.v == 1) {

				ans = s.time;

				break;

			}

			for (int dv = -1; dv <= 1; dv++) {

				int nv = s.v + dv;

				for (auto& e : G[s.pos]) {

					if (e.to == s.prev) continue;

					if (nv < 1 || e.cap < nv) continue;

					double nt = s.time + double(e.dist) / nv;

					if (!visited[e.to][s.pos][nv] || min_time[e.to][s.pos][nv] > nt) {

						visited[e.to][s.pos][nv] = true;

						min_time[e.to][s.pos][nv] = nt;

						que.emplace(e.to, s.pos, nv, nt);

					}

				}

			}

		}



		if (ans < 0) {

			cout << "unreachable" << endl;

		} else {

			printf("%.5f\n", ans);

		}

	}



	return 0;

}




// Fastest known solution for this problem: