rangeOfMatchingFloatingPointNumbers function

Tuple2<double, double> rangeOfMatchingFloatingPointNumbers (double value, int maxNumbersBetween)

Determines the range of floating point numbers that will match the specified value with the given tolerance.

Implementation

Tuple2<double, double> rangeOfMatchingFloatingPointNumbers(
    double value, int maxNumbersBetween) {
  // Make sure ulpDifference is non-negative
  if (maxNumbersBetween < 1) {
    throw ArgumentError.value(
        maxNumbersBetween, 'maxNumbersBetween', messages.argumentPositive);
  }

  // If the value is infinity (positive or negative) just
  // return the same infinity for the range.
  if (value.isInfinite) {
    return Tuple2<double, double>(value, value);
  }

  // If the value is a NaN then the range is a NaN too.
  if (value.isNaN) {
    return Tuple2<double, double>(double.nan, double.nan);
  }

  // Translate the bit pattern of the double to an integer.
  // Note that this leads to:
  // double > 0 --> long > 0, growing as the double value grows
  // double < 0 --> long < 0, increasing in absolute magnitude as the double
  //                          gets closer to zero!
  //                          i.e. 0 - double.epsilon will give the largest long value!
  var bytes = ByteData(8);
  bytes.setFloat64(0, value);
  int intValue = bytes.getInt64(0);

  // We need to protect against over- and under-flow of the intValue when
  // we start to add the ulpsDifference.
  if (intValue < 0) {
    // Note that long.MinValue has the same bit pattern as
    // -0.0. Therefore we're working in opposite direction (i.e. add if we want to
    // go more negative and subtract if we want to go less negative)
    var topRangeEnd = (int64MinValue - intValue).abs() < maxNumbersBetween
        // Got underflow, which can be fixed by splitting the calculation into two bits
        // first get the remainder of the intValue after subtracting it from the long.MinValue
        // and add that to the ulpsDifference. That way we'll turn positive without underflow
        ? int64BitsToDouble(maxNumbersBetween + (int64MinValue - intValue))
        // No problems here, move along.
        : int64BitsToDouble(intValue - maxNumbersBetween);

    var bottomRangeEnd = intValue.abs() < maxNumbersBetween
        // Underflow, which means we'd have to go further than a long would allow us.
        // Also we couldn't translate it back to a double, so we'll return -Double.MaxValue
        ? -double.maxFinite
        // intValue is negative. Adding the positive ulpsDifference means that it gets less negative.
        // However due to the conversion way this means that the actual double value gets more negative :-S
        : int64BitsToDouble(intValue + maxNumbersBetween);

    return Tuple2<double, double>(bottomRangeEnd, topRangeEnd);
  } else {
    // IntValue is positive
    var topRangeEnd = int64MaxValue - intValue < maxNumbersBetween
        // Overflow, which means we'd have to go further than a long would allow us.
        // Also we couldn't translate it back to a double, so we'll return Double.MaxValue
        ? double.maxFinite
        // No troubles here
        : int64BitsToDouble(intValue + maxNumbersBetween);

    // Check the bottom range end for underflows
    var bottomRangeEnd = intValue > maxNumbersBetween
        // No problems here. IntValue is larger than ulpsDifference so we'll end up with a
        // positive number.
        ? int64BitsToDouble(intValue - maxNumbersBetween)
        // Int value is bigger than zero but smaller than the ulpsDifference. So we'll need to deal with
        // the reversal at the negative end
        : int64BitsToDouble(int64MinValue + (maxNumbersBetween - intValue));

    return Tuple2<double, double>(bottomRangeEnd, topRangeEnd);
  }
}