docs: function finite(o) {
        var sum, xs, ws, cumsLeft, cumsRight, i, finObj;
  
        o = typeof o.f === 'function' ? popObjFromFunc(o)
                                      : {
                                         xs: o.xs.slice(0),
                                         ws: o.ws.slice(0)
                                      };
  
        xs = o.xs;
        ws = o.ws;
  
        // Check that xs are nonempty
        if (xs.length === 0) { throw new Error('finite: Need at least one x'); }
        // Check that xs, ws have same length
        if (xs.length !== ws.length) {
           throw new Error('finite: xs and ws need to have the same length');
        }
        // Check that the xs don't contain NaNs
        for (i = 0; i < xs.length; i += 1) {
           if (utils.hasNaN(xs[i], ws[i])) { return nanObject; }
        }
        // Check that xs are strictly increasing
        xs.reduce(function(acc, v) {
           if (v <= acc) {
              throw new Error('finite: xs must be strictly increasing');
           }
           return v;
        });
  
        sum = ws.reduce(function(acc, v) {
           if (!(v >= 0)) { throw new Error('finite: Cannot have negative weights'); }
           return v + acc;
        }, 0);
        if (!utils.relativelyCloseTo(sum, 1)) {
           ws = ws.map(function(v) { return v / sum; });
        }
        sum = 0;  // Reusing sum for the cumulative probabilities (cdf)
        cumsLeft = ws.map(function(v) { sum += v; return sum; });
        cumsRight = [];
        cumsRight[ws.length - 1] = 0;
        for (i = ws.length - 2; i >= 0; i -= 1) {
           cumsRight[i] = cumsRight[i + 1] + ws[i + 1];
        }
        finObj = {
           d: function(x, logp) {
              var ind, ret;
  
              logp = logp === true;
              if (utils.hasNaN(x)) { return NaN; }
              if (x < xs[0] || x > xs[xs.length - 1]) {
                 ret = 0;
              } else {
                 ind = binSearch(xs, function(y) { return y <= x; });
                 ret = xs[ind] === x ? ws[ind] : 0;
              }
              return logp ? Math.log(ret) : ret;
           },
           /* eslint-disable complexity */
           p: function(q, lowerTail, logp) {
              var ind, ret;
  
              logp = logp === true;
              lowerTail = lowerTail !== false;
  
              if (utils.hasNaN(q)) { return NaN; }
              if (q < xs[0]) {
                 ret = lowerTail ? 0 : 1;
              } else if (q >= xs[xs.length - 1]) {
                 ret = lowerTail ? 1 : 0;
              } else {
                 ind = binSearch(xs, function(y) { return y <= q; });
                 ret = lowerTail ? cumsLeft[ind] : cumsRight[ind];
              }
              ret = Math.min(ret, 1);
              return logp ? Math.log(ret) : ret;
           },
           q: function(p, lowerTail, logp) {
              logp = logp === true;
              lowerTail = lowerTail !== false;
              if (logp) { p = Math.exp(p); }
              if (utils.hasNaN(p)) { return NaN; }
              if (p >= 1) { return lowerTail ? xs[xs.length - 1] : xs[0]; }
              if (p <= 0) { return lowerTail ? xs[0] : xs[xs.length - 1]; }
              if (lowerTail) {
                 // we need the smallest x such that the area left and equal
                 // to x is >= p
                 return xs[binSearch2(cumsLeft, function(y) {
                    return utils.relativelyCloseTo(y, p, 1e-14) || y > p;
                 })];
              }
              // !lowerTail
              // we need the smallest x such that the area strictly above
              // x is <= p
              return xs[binSearch2(cumsRight, function(y) {
                 return utils.relativelyCloseTo(y, p, 1e-14) || y <= p;
              })];
           },
           /* eslint-enable complexity */
           r: function() {
              return finObj.q(rgen.random());
           }
        };
  
        return finObj;
     }