<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.lucene.geo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.SloppyMath;
import org.apache.lucene.util.TestUtil;

import com.carrotsearch.randomizedtesting.RandomizedContext;

/** static methods for testing geo */
public class GeoTestUtil {

  /** returns next pseudorandom latitude (anywhere) */
  public static double nextLatitude() {
    return nextDoubleInternal(-90, 90);
  }

  /** returns next pseudorandom longitude (anywhere) */
  public static double nextLongitude() {
    return nextDoubleInternal(-180, 180);
  }
  
  /**
   * Returns next double within range.
   * <p>
   * Don't pass huge numbers or infinity or anything like that yet. may have bugs!
   */
  // the goal is to adjust random number generation to test edges, create more duplicates, create "one-offs" in floating point space, etc.
  // we do this by first picking a good "base value" (explicitly targeting edges, zero if allowed, or "discrete values"). but it also
  // ensures we pick any double in the range and generally still produces randomish looking numbers.
  // then we sometimes perturb that by one ulp.
  private static double nextDoubleInternal(double low, double high) {
    assert low >= Integer.MIN_VALUE;
    assert high <= Integer.MAX_VALUE;
    assert Double.isFinite(low);
    assert Double.isFinite(high);
    assert high >= low : "low=" + low + " high=" + high;
    
    // if they are equal, not much we can do
    if (low == high) {
      return low;
    }

    // first pick a base value.
    final double baseValue;
    int surpriseMe = random().nextInt(17);
    if (surpriseMe == 0) {
      // random bits
      long lowBits = NumericUtils.doubleToSortableLong(low);
      long highBits = NumericUtils.doubleToSortableLong(high);
      baseValue = NumericUtils.sortableLongToDouble(TestUtil.nextLong(random(), lowBits, highBits));
    } else if (surpriseMe == 1) {
      // edge case
      baseValue = low;
    } else if (surpriseMe == 2) {
      // edge case
      baseValue = high;
    } else if (surpriseMe == 3 && low <= 0 && high >= 0) {
      // may trigger divide by 0
      baseValue = 0.0;
    } else if (surpriseMe == 4) {
      // divide up space into block of 360
      double delta = (high - low) / 360;
      int block = random().nextInt(360);
      baseValue = low + delta * block;
    } else {
      // distributed ~ evenly
      baseValue = low + (high - low) * random().nextDouble();
    }

    assert baseValue >= low;
    assert baseValue <= high;

    // either return the base value or adjust it by 1 ulp in a random direction (if possible)
    int adjustMe = random().nextInt(17);
    if (adjustMe == 0) {
      return Math.nextAfter(adjustMe, high);
    } else if (adjustMe == 1) {
      return Math.nextAfter(adjustMe, low);
    } else {
      return baseValue;
    }
  }

  /** returns next pseudorandom latitude, kinda close to {@code otherLatitude} */
  private static double nextLatitudeNear(double otherLatitude, double delta) {
    delta = Math.abs(delta);
    GeoUtils.checkLatitude(otherLatitude);
    int surpriseMe = random().nextInt(97);
    if (surpriseMe == 0) {
      // purely random
      return nextLatitude();
    } else if (surpriseMe < 49) {
      // upper half of region (the exact point or 1 ulp difference is still likely)
      return nextDoubleInternal(otherLatitude, Math.min(90, otherLatitude + delta));
    } else {
      // lower half of region (the exact point or 1 ulp difference is still likely)
      return nextDoubleInternal(Math.max(-90, otherLatitude - delta), otherLatitude);
    }
  }

  /** returns next pseudorandom longitude, kinda close to {@code otherLongitude} */
  private static double nextLongitudeNear(double otherLongitude, double delta) {
    delta = Math.abs(delta);
    GeoUtils.checkLongitude(otherLongitude);
    int surpriseMe = random().nextInt(97);
    if (surpriseMe == 0) {
      // purely random
      return nextLongitude();
    } else if (surpriseMe < 49) {
      // upper half of region (the exact point or 1 ulp difference is still likely)
      return nextDoubleInternal(otherLongitude, Math.min(180, otherLongitude + delta));
    } else {
      // lower half of region (the exact point or 1 ulp difference is still likely)
      return nextDoubleInternal(Math.max(-180, otherLongitude - delta), otherLongitude);
    }
  }

  /**
   * returns next pseudorandom latitude, kinda close to {@code minLatitude/maxLatitude}
   * <b>NOTE:</b>minLatitude/maxLatitude are merely guidelines. the returned value is sometimes
   * outside of that range! this is to facilitate edge testing of lines
   */
  private static double nextLatitudeBetween(double minLatitude, double maxLatitude) {
    assert maxLatitude >= minLatitude;
    GeoUtils.checkLatitude(minLatitude);
    GeoUtils.checkLatitude(maxLatitude);
    if (random().nextInt(47) == 0) {
      // purely random
      return nextLatitude();
    } else {
      // extend the range by 1%
      double difference = (maxLatitude - minLatitude) / 100;
      double lower = Math.max(-90, minLatitude - difference);
      double upper = Math.min(90, maxLatitude + difference);
      return nextDoubleInternal(lower, upper);
    }
  }

  /**
   * returns next pseudorandom longitude, kinda close to {@code minLongitude/maxLongitude}
   * <b>NOTE:</b>minLongitude/maxLongitude are merely guidelines. the returned value is sometimes
   * outside of that range! this is to facilitate edge testing of lines
   */
  private static double nextLongitudeBetween(double minLongitude, double maxLongitude) {
    assert maxLongitude >= minLongitude;
    GeoUtils.checkLongitude(minLongitude);
    GeoUtils.checkLongitude(maxLongitude);
    if (random().nextInt(47) == 0) {
      // purely random
      return nextLongitude();
    } else {
      // extend the range by 1%
      double difference = (maxLongitude - minLongitude) / 100;
      double lower = Math.max(-180, minLongitude - difference);
      double upper = Math.min(180, maxLongitude + difference);
      return nextDoubleInternal(lower, upper);
    }
  }
  
  /** Returns the next point around a line (more or less) */
  private static double[] nextPointAroundLine(double lat1, double lon1, double lat2, double lon2) {
    double x1 = lon1;
    double x2 = lon2;
    double y1 = lat1;
    double y2 = lat2;
    double minX = Math.min(x1, x2);
    double maxX = Math.max(x1, x2);
    double minY = Math.min(y1, y2);
    double maxY = Math.max(y1, y2);
    if (minX == maxX) {
      return new double[] { nextLatitudeBetween(minY, maxY), nextLongitudeNear(minX, 0.01 * (maxY - minY)) };
    } else if (minY == maxY) {
      return new double[] { nextLatitudeNear(minY, 0.01 * (maxX - minX)), nextLongitudeBetween(minX, maxX) };
    } else {
      double x = nextLongitudeBetween(minX, maxX);
      double y = (y1 - y2) / (x1 - x2) * (x-x1) + y1;
      double delta = (maxY - minY) * 0.01;
      // our formula may put the targeted Y out of bounds
      y = Math.min(90, y);
      y = Math.max(-90, y);
      return new double[] { nextLatitudeNear(y, delta), x };
    }
  }
  <commit_msg>LUCENE-7185: handle underflow<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.lucene.geo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.SloppyMath;
import org.apache.lucene.util.TestUtil;

import com.carrotsearch.randomizedtesting.RandomizedContext;

/** static methods for testing geo */
public class GeoTestUtil {

  /** returns next pseudorandom latitude (anywhere) */
  public static double nextLatitude() {
    return nextDoubleInternal(-90, 90);
  }

  /** returns next pseudorandom longitude (anywhere) */
  public static double nextLongitude() {
    return nextDoubleInternal(-180, 180);
  }
  
  /**
   * Returns next double within range.
   * <p>
   * Don't pass huge numbers or infinity or anything like that yet. may have bugs!
   */
  // the goal is to adjust random number generation to test edges, create more duplicates, create "one-offs" in floating point space, etc.
  // we do this by first picking a good "base value" (explicitly targeting edges, zero if allowed, or "discrete values"). but it also
  // ensures we pick any double in the range and generally still produces randomish looking numbers.
  // then we sometimes perturb that by one ulp.
  private static double nextDoubleInternal(double low, double high) {
    assert low >= Integer.MIN_VALUE;
    assert high <= Integer.MAX_VALUE;
    assert Double.isFinite(low);
    assert Double.isFinite(high);
    assert high >= low : "low=" + low + " high=" + high;
    
    // if they are equal, not much we can do
    if (low == high) {
      return low;
    }

    // first pick a base value.
    final double baseValue;
    int surpriseMe = random().nextInt(17);
    if (surpriseMe == 0) {
      // random bits
      long lowBits = NumericUtils.doubleToSortableLong(low);
      long highBits = NumericUtils.doubleToSortableLong(high);
      baseValue = NumericUtils.sortableLongToDouble(TestUtil.nextLong(random(), lowBits, highBits));
    } else if (surpriseMe == 1) {
      // edge case
      baseValue = low;
    } else if (surpriseMe == 2) {
      // edge case
      baseValue = high;
    } else if (surpriseMe == 3 && low <= 0 && high >= 0) {
      // may trigger divide by 0
      baseValue = 0.0;
    } else if (surpriseMe == 4) {
      // divide up space into block of 360
      double delta = (high - low) / 360;
      int block = random().nextInt(360);
      baseValue = low + delta * block;
    } else {
      // distributed ~ evenly
      baseValue = low + (high - low) * random().nextDouble();
    }

    assert baseValue >= low;
    assert baseValue <= high;

    // either return the base value or adjust it by 1 ulp in a random direction (if possible)
    int adjustMe = random().nextInt(17);
    if (adjustMe == 0) {
      return Math.nextAfter(adjustMe, high);
    } else if (adjustMe == 1) {
      return Math.nextAfter(adjustMe, low);
    } else {
      return baseValue;
    }
  }

  /** returns next pseudorandom latitude, kinda close to {@code otherLatitude} */
  private static double nextLatitudeNear(double otherLatitude, double delta) {
    delta = Math.abs(delta);
    GeoUtils.checkLatitude(otherLatitude);
    int surpriseMe = random().nextInt(97);
    if (surpriseMe == 0) {
      // purely random
      return nextLatitude();
    } else if (surpriseMe < 49) {
      // upper half of region (the exact point or 1 ulp difference is still likely)
      return nextDoubleInternal(otherLatitude, Math.min(90, otherLatitude + delta));
    } else {
      // lower half of region (the exact point or 1 ulp difference is still likely)
      return nextDoubleInternal(Math.max(-90, otherLatitude - delta), otherLatitude);
    }
  }

  /** returns next pseudorandom longitude, kinda close to {@code otherLongitude} */
  private static double nextLongitudeNear(double otherLongitude, double delta) {
    delta = Math.abs(delta);
    GeoUtils.checkLongitude(otherLongitude);
    int surpriseMe = random().nextInt(97);
    if (surpriseMe == 0) {
      // purely random
      return nextLongitude();
    } else if (surpriseMe < 49) {
      // upper half of region (the exact point or 1 ulp difference is still likely)
      return nextDoubleInternal(otherLongitude, Math.min(180, otherLongitude + delta));
    } else {
      // lower half of region (the exact point or 1 ulp difference is still likely)
      return nextDoubleInternal(Math.max(-180, otherLongitude - delta), otherLongitude);
    }
  }

  /**
   * returns next pseudorandom latitude, kinda close to {@code minLatitude/maxLatitude}
   * <b>NOTE:</b>minLatitude/maxLatitude are merely guidelines. the returned value is sometimes
   * outside of that range! this is to facilitate edge testing of lines
   */
  private static double nextLatitudeBetween(double minLatitude, double maxLatitude) {
    assert maxLatitude >= minLatitude;
    GeoUtils.checkLatitude(minLatitude);
    GeoUtils.checkLatitude(maxLatitude);
    if (random().nextInt(47) == 0) {
      // purely random
      return nextLatitude();
    } else {
      // extend the range by 1%
      double difference = (maxLatitude - minLatitude) / 100;
      double lower = Math.max(-90, minLatitude - difference);
      double upper = Math.min(90, maxLatitude + difference);
      return nextDoubleInternal(lower, upper);
    }
  }

  /**
   * returns next pseudorandom longitude, kinda close to {@code minLongitude/maxLongitude}
   * <b>NOTE:</b>minLongitude/maxLongitude are merely guidelines. the returned value is sometimes
   * outside of that range! this is to facilitate edge testing of lines
   */
  private static double nextLongitudeBetween(double minLongitude, double maxLongitude) {
    assert maxLongitude >= minLongitude;
    GeoUtils.checkLongitude(minLongitude);
    GeoUtils.checkLongitude(maxLongitude);
    if (random().nextInt(47) == 0) {
      // purely random
      return nextLongitude();
    } else {
      // extend the range by 1%
      double difference = (maxLongitude - minLongitude) / 100;
      double lower = Math.max(-180, minLongitude - difference);
      double upper = Math.min(180, maxLongitude + difference);
      return nextDoubleInternal(lower, upper);
    }
  }
  
  /** Returns the next point around a line (more or less) */
  private static double[] nextPointAroundLine(double lat1, double lon1, double lat2, double lon2) {
    double x1 = lon1;
    double x2 = lon2;
    double y1 = lat1;
    double y2 = lat2;
    double minX = Math.min(x1, x2);
    double maxX = Math.max(x1, x2);
    double minY = Math.min(y1, y2);
    double maxY = Math.max(y1, y2);
    if (minX == maxX) {
      return new double[] { nextLatitudeBetween(minY, maxY), nextLongitudeNear(minX, 0.01 * (maxY - minY)) };
    } else if (minY == maxY) {
      return new double[] { nextLatitudeNear(minY, 0.01 * (maxX - minX)), nextLongitudeBetween(minX, maxX) };
    } else {
      double x = nextLongitudeBetween(minX, maxX);
      double y = (y1 - y2) / (x1 - x2) * (x-x1) + y1;
      if (Double.isFinite(y) == false) {
        // this can happen due to underflow when delta between x values is wonderfully tiny!
        y = Math.copySign(90, x1);
      }
      double delta = (maxY - minY) * 0.01;
      // our formula may put the targeted Y out of bounds
      y = Math.min(90, y);
      y = Math.max(-90, y);
      return new double[] { nextLatitudeNear(y, delta), x };
    }
  }
  
{'fraction_non_alphanumeric': 0.1037811251152782, 'fraction_numerical': 0.016046726098985553, 'mean_word_length': 3.0452623725441432, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6036477', 'n_tokens_mistral': 4943, 'n_tokens_neox': 4709, 'n_words': 1664}