#include <bits/stdc++.h>
using namespace std;

#define INF_LL (int64)1e18
#define INF (int32)1e9
#define REP(i, n) for(int64 i = 0;i < (n);i++)
#define FOR(i, a, b) for(int64 i = (a);i < (b);i++)
#define all(x) x.begin(),x.end()
#define fs first
#define sc second

using int32 = int_fast32_t;
using uint32 = uint_fast32_t;
using int64 = int_fast64_t;
using uint64 = uint_fast64_t;
using PII = pair<int32, int32>;
using PLL = pair<int64, int64>;

const double eps = 1e-10;

template<typename A, typename B>inline void chmin(A &a, B b){if(a > b) a = b;}
template<typename A, typename B>inline void chmax(A &a, B b){if(a < b) a = b;}

template<typename T>
vector<T> make_v(size_t a){return vector<T>(a);}

template<typename T,typename... Ts>
auto make_v(size_t a,Ts... ts){
  return vector<decltype(make_v<T>(ts...))>(a,make_v<T>(ts...));
}

template<typename T,typename U,typename... V>
typename enable_if<is_same<T, U>::value!=0>::type
fill_v(U &u,const V... v){u=U(v...);}

template<typename T,typename U,typename... V>
typename enable_if<is_same<T, U>::value==0>::type
fill_v(U &u,const V... v){
  for(auto &e:u) fill_v<T>(e,v...);
}

template<class EdgeInfo>
class Graph {
protected:
	using size_type = ::std::size_t;
	struct Edge {
		size_type to;
		EdgeInfo info;
		Edge() : to(-1) {}
		Edge(size_type to_, EdgeInfo info_) : to(to_), info(info_) {}
	};
	using edge_type = Edge;

	::std::vector<::std::vector<edge_type>> edges;

public:
	Graph() {}
	Graph(size_type n) : edges(n) {}
	Graph(const Graph& g) : edges(g.edges) {}
	Graph(Graph&& g) { ::std::swap(edges, g.edges); }
	Graph& operator=(const Graph& g) { edges = g.edges; }
	Graph& operator=(Graph&& g) { ::std::swap(edges, g.edges); }

	const size_type size() { return edges.size(); }
	virtual void add_edge (size_type from, size_type to, EdgeInfo info) = 0;

	const ::std::vector<edge_type>& operator[](size_type k) { return edges[k]; }

};

template<class EdgeInfo>
class DirectedGraph : public Graph<EdgeInfo> {
protected:
	using size_type = ::std::size_t;
public:
	using edge_type = typename Graph<EdgeInfo>::edge_type;
	DirectedGraph() {}
	DirectedGraph(size_type n) : Graph<EdgeInfo>(n) {}
	DirectedGraph(const DirectedGraph& g) : Graph<EdgeInfo>(g) {}
	DirectedGraph(DirectedGraph&& g) : Graph<EdgeInfo>(g) {}
	DirectedGraph& operator=(const DirectedGraph& g) { this->edges = g.edges; return *this;}
	DirectedGraph& operator=(DirectedGraph&& g) noexcept { ::std::swap(this->edges, g.edges); return *this; }

	void add_edge (size_type from, size_type to, EdgeInfo info = EdgeInfo()) {
		this->edges[from].push_back(edge_type(to, info));
	}
};

template<class DistanceType>
class Dijkstra {
private:
	using size_type = ::std::size_t;
	using distance_type = DistanceType;
	using Graph = DirectedGraph<distance_type>;

	Graph graph;
public:
	Dijkstra() {}
	Dijkstra(size_type n) : graph(n) {}
	Dijkstra(const Graph& g) : graph(g) {}

	void add_edge (size_type from, size_type to, distance_type d) {
		graph.add_edge(from, to, d);
	}

	::std::vector<distance_type> build(size_type start) {
		::std::vector<distance_type> dis(graph.size(), invalid_value());

		using P = ::std::pair<distance_type, size_type>;
		::std::priority_queue<P, ::std::vector<P>, ::std::greater<P>> pq;
		dis[start] = 0;
		pq.push(P(dis[start], start));
		while (pq.size()) {
			P p = pq.top(); pq.pop();
			if (p.first > dis[p.second]) continue;
			for (auto e : graph[p.second]) {
				if (dis[e.to] > dis[p.second] + e.info) {
					dis[e.to] = dis[p.second] + e.info;
					pq.push(P(dis[e.to], e.to));
				}
			}
		}

		return dis;
	}

	static const distance_type invalid_value () {
		static const distance_type inf = ::std::numeric_limits<distance_type>::max() / 2 - 1;
		return inf;
	}
};

int64 n, m;
int64 to_idx(int64 v, int64 s, int64 from, int64 is_to) {
  return (is_to ? n*n*40 : 0) + n*40*from + n*s + v;
}

int main(void) {
  while (cin >> n >> m && n+m) {
    Dijkstra<double> shp(2*n*n*(40));
    int64 s, g;
    cin >> s >> g; s--; g--;
    REP(i, n) {
      REP(j, 35) {
        REP(k, n) {
          shp.add_edge(to_idx(i, j, k, 0), to_idx(i, j, k, 1), 0);
          shp.add_edge(to_idx(i, j, k, 0), to_idx(i, j+1, k, 1), 0);
          if (j > 0)
            shp.add_edge(to_idx(i, j, k, 0), to_idx(i, j-1, k, 1), 0);
        }
      }
    }
    REP(i, m) {
      int64 x, y, d, c;
      cin >> x >> y >> d >> c; x--; y--;
      REP(j, c) {
        REP(k, n) {
          if (y != k)
            shp.add_edge(to_idx(x, j, k, 1), to_idx(y, j, x, 0), (double)d/(double)(j+1));
          if (x != k)
            shp.add_edge(to_idx(y, j, k, 1), to_idx(x, j, y, 0), (double)d/(double)(j+1));
        }
      }
    }
    auto ret = shp.build(to_idx(s, 0, s, 1));
    double res = shp.invalid_value();
    REP(i, n) {
      chmin(res, ret[to_idx(g, 0, i, 0)]);
    }
    if (res == shp.invalid_value()) {
      cout << "unreachable" << endl;
    } else {
      cout << fixed << setprecision(10) << res << endl;
    }
  }
}


Measured memory_bytes: 22724