// Original C++ solution (slower):


#include <iostream>

#include <queue>

#include <cfloat>



using namespace std;



class State

{

public:

	double _time; //©©Á½Ô

	int _speed; //¬x

	int _town; //»Ýn

	int _utern; //OñÌ¬it^[Ö~j

	State(double time,int speed,int town,int utern)

		:_time(time),_speed(speed),_town(town),_utern(utern){}

	bool operator<(const State& tmp) const

	{

		return _time > tmp._time;

	}

};



class Edge

{

public:

	int _d; //£

	int _c; //§À¬x

	Edge(){}

	Edge(int d,int c):_d(d),_c(c){}

};



int main()

{

	for(;;)

	{

		//Init

		int n,m,s,g;

		cin >> n >> m;

		if(n==0 && m==0) break;



		cin	>> s >> g;

		--s;

		--g;



		Edge *Graph[30][30];

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

		{

			for(int j=0;j<30;j++)

			{

				Graph[i][j] = NULL;

			}

		}



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

		{

			int x,y,d,c;

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

			--x;

			--y;

			Graph[x][y] = new Edge(d,c);

			Graph[y][x] = new Edge(d,c);

		}



		bool Visited[30][30][30]; //»Ýn@t^[Ö~@¬x

		double Cost[30][30][30];

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

		{

			for(int j=0;j<30;j++)

			{

				for(int k=0;k<30;k++)

				{

					Visited[i][j][k] = false;

					Cost[i][j][k] = DBL_MAX;

				}

			}	

		}



		priority_queue<State> PQ;

		PQ.push(State(0.0,0,s,s));

		Cost[s][s][0] = 0;



		double min_time = -1;



		//Dijkstra

		while(!PQ.empty())

		{

			State tmp = PQ.top();

			PQ.pop();

			if(Visited[tmp._town][tmp._utern][tmp._speed] == true) continue;

			//cout << "time " << tmp._time << " town "<< tmp._town+1 << " speed " << tmp._speed << endl;

			if(tmp._town == g && tmp._speed == 1)

			{

				min_time = tmp._time;

				break;

			}

			Visited[tmp._town][tmp._utern][tmp._speed] = true;



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

			{

				if(tmp._utern == i) continue;

				if(Graph[tmp._town][i] != NULL)

				{

					for(int j=-1;j<=1;j++)

					{

						int tmp_speed = tmp._speed + j;

						if(Visited[i][tmp._town][tmp_speed] == true) continue;

						if(tmp_speed <= 0) continue;

						if(tmp_speed > Graph[tmp._town][i]->_c) continue;

						double tmp_time = Graph[tmp._town][i]->_d / static_cast<double>(tmp_speed);

						tmp_time += tmp._time;

						if(Cost[i][tmp._town][tmp_speed] < tmp_time) continue;

						Cost[i][tmp._town][tmp_speed] = tmp_time;

						PQ.push(State(tmp_time,tmp_speed,i,tmp._town));

					}

				}

			}

		}



		//Output

		if(min_time == -1)

		{

			cout << "unreachable" << endl;

		}else

		{

			cout << min_time << endl;

		}



		//Free

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

		{

			for(int j=0;j<30;j++)

			{

				if(Graph[i][j] != NULL) delete Graph[i][j];

			}

		}

	}

	return 0;

}

// Optimized solution to the same problem (faster):
#include <iostream>

#include <cstdio>

#include <vector>

#include <algorithm>

#include <complex>

#include <queue>

#include <map>

#include <set>

#include <cstring>

#include <cstdlib>

#include <string>

#include <cmath>

using namespace std;



#define REP(i,n) for(int i=0;i<(int)n;++i)

#define FOR(i,c) for(__typeof((c).begin())i=(c).begin();i!=(c).end();++i)

#define ALL(c) (c).begin(), (c).end()

const int INF = 1<<29;



typedef double Weight;

struct Edge {

  int src, dst;

  Weight weight;

  int c;                        // c ÍOtÅÍ§À¬xAqueue ÅÍ¬x

  Edge(int src, int dst, Weight weight, int c) :

    src(src), dst(dst), weight(weight), c(c){ }

};



bool operator < (const Edge &e, const Edge &f) {

  return e.weight != f.weight ? e.weight > f.weight : // !!INVERSE!!

    e.src != f.src ? e.src < f.src :

    e.dst != f.dst ? e.dst < f.dst : e.c < f.c;

}



typedef vector<Edge> Edges;

typedef vector<Edges> Graph;

typedef pair<int,int> pii;



double dist[30][30][32];           // dist[i][j][k] = Xs[h k ÌóÔÅ j ©ç i ÉÂ­ÅZ£



double dijkstra(const Graph &g, int s, int t) {

  int n = g.size();

  REP(i,n) REP(j,n) REP(k,32) dist[i][j][k] = INF;

  priority_queue<Edge> Q;

  Q.push(Edge(s,s,0,0));

  int cnt = 0;

  while(!Q.empty()) {

    Edge e = Q.top(); Q.pop();

    if (e.dst == t && e.c == 1) break;

    REP(k,3) {

      int v = e.c - 1 + k;

      if (v<=0) continue;

      if (dist[e.dst][e.src][v] < e.weight) continue;

      FOR(f, g[e.dst]) {

        if (v > f->c) continue;

        if (f->dst == e.src) continue;

        double hoge = e.weight + f->weight / v;

        if (dist[f->dst][f->src][v] > hoge) {

          dist[f->dst][f->src][v] = hoge;

          Q.push(Edge(f->src, f->dst, hoge, v));

        }

      }

    }

    if (cnt++ > 200000) break;

  }



  double res = INF;

  REP(i,n)

    res = min(res, dist[t][i][1]);

  return res;

}



int main() {

  int n,m;

  while(cin >> n>>m,n||m) {

    int s,t;

    cin >> s >> t;

    s--;t--;

    Graph g(n);

    REP(i,m) {

      int x,y,d,c;

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

      x--;y--;

      g[x].push_back(Edge(x,y,d,c));

      g[y].push_back(Edge(y,x,d,c));

    }

    double res = dijkstra(g,s,t);

    if (res == INF)

      cout << "unreachable" << endl;

    else

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

  }

}

// Fastest known solution for this problem: