Minimum and maximum random numbers

Belatedly – hah! that was a cut-paste typo.

I made it for integration into the previous demo sketch. Again, all that complexity isn’t usually necessary – but different forms for different needs.

Here is something that tests each distribution:

/**
 * RandomRanges2
 * 2020-01-09 Jeremy Douglass Processing 3.4
 * useful approaches for generating distributions of random ranges
 * in linear or circular spaces, e.g. hue colorspaces.
 *
 * In this example wrap and overflow are converted inline into the wrap
 * and split format in order to avoid ArrayIndexOutOfBoundsException.
 * In actual code you need only return the needed format.
 *
 * -  split (and wrap, when split) trend towards flat distributions.
 * -  crop (and overflow, when cropped) fall off on the left-hand side.
 * -  fitCenter and fitwidth both have curved distributions.
 * -  fitwidth returns shorter ranges on average, as it ignores minWide
 *    when center is near edge.
 * 
 * see https://discourse.processing.org/t/minimum-and-maximum-random-numbers/16888/4
 */

String[] modes;
float[][] ranges;
int[][] graphs;
int wmax = 360;
boolean reset;

void settings() {
  size(wmax, 480);
}
void setup() {
  colorMode(HSB);
  strokeWeight(3);
  // list different ways to generate a random range
  modes = new String[]{"split", "wrap", "overflow", "crop", "fitWidth", "fitCenter"};
  // each frame store a new range for each mode,
  // and add the cumulative ranges to a graph for each mode
  ranges = new float[modes.length][];
  graphs = new int[modes.length][width];
  // when this flag is set the sketch will screenshot then rerun setup
  reset = false;
}
void draw() {
  background(0);

  // loop over modes
  for (int m=0; m<modes.length; m++) {
    // get range for this mode
    ranges[m] = randomRange(0, 360, 0, 360, modes[m]);  // ! changing 4th argument to more than 2nd breaks fit modes

    // fix bad return ranges by reformatting
    if (modes[m].equals("overflow") && ranges[m][1]>wmax) ranges[m] = new float[]{ranges[m][0], min(ranges[m][1], wmax)};  // crop the overflow
    if (modes[m].equals("wrap") && ranges[m][1]<ranges[m][0]) ranges[m] = new float[]{0, ranges[m][1], ranges[m][0], wmax-1};  // split the wrap

    // add range(s) to graph
    if(ranges[m].length>6) print(ranges[m].length, " ");
    for (int r=0; r<ranges[m].length; r+=2) {
      for (int x=(int)ranges[m][r]; x<(int)ranges[m][r+1]; x++) {
        graphs[m][x]++;
        // if a graph height has filled the page, reset sketch at end of this frame
        if (graphs[m][x] > height-50) reset = true;
      }
    }
  }

  for (int m=0; m<modes.length; m++) {
    // draw range bars and graphs
    int h = m * (360-120)/modes.length;
    stroke(h, 255, 192);  // set mode line color
    int lb = (m+1)*5;
    line(ranges[m][0], lb, ranges[m][1], lb);
    if (ranges[m].length==4) line(ranges[m][2], lb, ranges[m][3], lb);
    for (int x=0; x<width; x++) {
      point(x, height-graphs[m][x]);
    }
  }
  for (int m=0; m<modes.length; m++) {
    // draw labels
    int h = m * (360-120)/modes.length;
    int tab = m * (wmax-20)/(modes.length);
    fill(0);
    textSize(16);
    text(modes[m], tab, height-graphs[m][tab]);
    fill(h, 128, 255);
    text(modes[m], tab+1, height-graphs[m][tab]+1);
  }

  // if resetting, save frame first;
  if (reset) {
    saveFrame("RandomRanges2--screenshot#####.png");
    frameCount = -1;  // rerun setup;
  }
}

float[] randomRange(float minVal, float maxVal, float minWide, float maxWide, String mode) {
  //if (maxWide > maxVal-minVal) throw new IllegalArgumentException("maxWide cannot be greater than value range.");
  // if the mode is to "fit" the random range inside the min-max values then
  // fitting can happen width-first (fit center) or center-first (fit width).
  switch(mode) {
  case "fitCenter":  // random width, fit center
    float wide = random(minWide, maxWide);
    float center = random(minVal + wide/2.0, maxVal - wide/2.0);
    return new float[]{center - wide/2.0, center + wide/2.0};
  case "fitWidth":  // random center, fit width -- ignores minWide and limits range if center near edge
    float center2 = random(minVal, maxVal);
    float wideLimit = 2 * min(center2, maxVal-center2);
    float wide2 = random(min(wideLimit, minWide), min(wideLimit, maxWide));
    return new float[]{center2 - wide2/2.0, center2 + wide2/2.0};
  }
  // otherwise, just pick a start and end, which might overflow
  float start = random(minVal, maxVal);
  float end = start + random(minWide, maxWide);
  // did end overflow? if so, choose a format to represent the result
  if (end>maxVal) {
    switch(mode) {
    case "crop": 
      // crop range, throw away invalid part
      return new float[] {start, maxVal};
    case "split": 
      // crop range, add overflow as wrapped (valid) ranges
      int wraps = (int)((end - end%maxVal)/maxVal);
      float[] result = new float[2*wraps + 2];
      for (int i=0; i<result.length; i+=2) {
        result[i] = minVal;
        result[i+1] = maxVal;
      }
      result[0] = start;
      result[result.length-1] = end%maxVal;
      return result;
      //return new float[]{minVal, end%maxVal, start, maxVal};
    case "overflow": 
      // return invalid part of the range anyway
      // using these values to iterate or interpolate may cause errors
      return new float[] {start, end};
    case "wrap": 
      // wrapping around last-first, for example: [350, 370] -> [350, 10]
      // always returns a range with 2 endpoint, e.g. [100, 3800] -> [100, 200];
      // using these values to iterate or interpolate may cause errors
      return new float[] {start, end%maxVal};
    default:
      throw new IllegalArgumentException("Invalid mode on overflow: " + mode);
    }
  }
  // no overflow: range already fits
  return new float[]{start, end};
}

Result:

RandomRanges2--screenshot