RawObject METH()

in runtime/float-builtins.cpp [889:1035]


RawObject METH(float, fromhex)(Thread* thread, Arguments args) {
  // Convert a hexadecimal string to a float.
  // Check the function arguments
  HandleScope scope(thread);
  Runtime* runtime = thread->runtime();

  Object type_obj(&scope, args.get(0));
  if (!runtime->isInstanceOfType(*type_obj)) {
    return thread->raiseRequiresType(type_obj, ID(type));
  }
  Type type(&scope, *type_obj);

  Object str_obj(&scope, args.get(1));
  if (!runtime->isInstanceOfStr(*str_obj)) {
    return thread->raiseRequiresType(str_obj, ID(str));
  }

  const Str str(&scope, strUnderlying(*str_obj));

  //
  // Parse the string
  //

  // leading whitespace
  word pos = nextNonWhitespace(str, 0);

  // infinities and nans
  {
    double result;
    if (parseInfOrNan(str, &pos, &result)) {
      return newFloatOrSubclass(thread, type, str, pos, result);
    }
  }

  // optional sign
  bool negate = strParseOptionalSign(str, &pos);

  // [0x]
  strAdvancePrefixCaseInsensitiveASCII(str, &pos, "0x");

  // coefficient: <integer> [. <fraction>]
  word ndigits, fdigits, coeff_end;
  floatHexParseCoefficients(str, &pos, &ndigits, &fdigits, &coeff_end);
  if (ndigits == 0) {
    return thread->raiseWithFmt(
        LayoutId::kValueError,
        "invalid hexadecimal floating-point string, no digits");
  }

  if (ndigits > Utils::minimum(kDoubleMinExp - kDoubleDigits - kMinLong / 2,
                               kMaxLong / 2 + 1 - kDoubleMaxExp) /
                    4) {
    return thread->raiseWithFmt(LayoutId::kValueError,
                                "hexadecimal string too long to convert");
  }

  // [p <exponent>]
  long exponent = floatHexParseExponent(str, &pos);

  //
  // Compute rounded value of the hex string
  //

  // Discard leading zeros, and catch extreme overflow and underflow
  while (ndigits > 0 &&
         getHexDigit(str, fdigits, coeff_end, ndigits - 1) == 0) {
    --ndigits;
  }
  if (ndigits == 0 || exponent < kMinLong / 2) {
    return newFloatOrSubclass(thread, type, str, pos, negate ? -0.0 : 0.0);
  }
  if (exponent > kMaxLong / 2) {
    return raiseOverflowErrorHexFloatTooLarge(thread);
  }

  // Adjust exponent for fractional part, 4 bits per nibble
  exponent -= 4 * static_cast<long>(fdigits);

  // top_exponent = 1 more than exponent of most sig. bit of coefficient
  long top_exponent = exponent + 4 * (static_cast<long>(ndigits) - 1);
  for (int digit = getHexDigit(str, fdigits, coeff_end, ndigits - 1);
       digit != 0; digit /= 2) {
    ++top_exponent;
  }

  // catch almost all nonextreme cases of overflow and underflow here
  if (top_exponent < kDoubleMinExp - kDoubleDigits) {
    return newFloatOrSubclass(thread, type, str, pos, negate ? -0.0 : 0.0);
  }
  if (top_exponent > kDoubleMaxExp) {
    return raiseOverflowErrorHexFloatTooLarge(thread);
  }

  // lsb = exponent of least significant bit of the *rounded* value.
  // This is top_exponent - kDoubleDigits unless result is subnormal.
  long lsb = Utils::maximum(top_exponent, static_cast<long>(kDoubleMinExp)) -
             kDoubleDigits;
  // Check if rounding required
  double result = 0.0;
  if (exponent >= lsb) {
    // no rounding required
    result = sumHexDigitsDouble(str, fdigits, coeff_end, ndigits - 1, 0);
  } else {
    // rounding required.  key_digit is the index of the hex digit
    // containing the first bit to be rounded away.
    int half_eps = 1 << static_cast<int>((lsb - exponent - 1) % 4);
    long key_digit = (lsb - exponent - 1) / 4;
    result =
        sumHexDigitsDouble(str, fdigits, coeff_end, ndigits - 1, key_digit + 1);

    // sum in the final key_digit, but subtract off 2*half_eps from it first to
    // allow for the rounding below.
    int digit = getHexDigit(str, fdigits, coeff_end, key_digit);
    result = 16.0 * result + static_cast<double>(digit & (16 - 2 * half_eps));

    // round-half-even: round up if bit lsb-1 is 1 and at least one of
    // bits lsb, lsb-2, lsb-3, lsb-4, ... is 1.
    if ((digit & half_eps) != 0) {
      bool round_up = false;
      if ((digit & (3 * half_eps - 1)) != 0 ||
          (half_eps == 8 &&
           (getHexDigit(str, fdigits, coeff_end, key_digit + 1) & 1) != 0)) {
        round_up = true;
      } else {
        for (ssize_t i = key_digit - 1; i >= 0; --i) {
          if (getHexDigit(str, fdigits, coeff_end, i) != 0) {
            round_up = true;
            break;
          }
        }
      }
      if (round_up) {
        result += 2 * half_eps;
        if (top_exponent == kDoubleMaxExp &&
            result == ldexp(static_cast<double>(2 * half_eps), kDoubleDigits)) {
          // overflow corner case: pre-rounded value < 2**kDoubleMaxExp;
          // rounded=2**kDoubleMaxExp.
          return raiseOverflowErrorHexFloatTooLarge(thread);
        }
      }
    }
    // Adjust the exponent over 4 bits for every nibble we skipped processing
    exponent += 4 * key_digit;
  }
  result = ldexp(result, static_cast<int>(exponent));
  return newFloatOrSubclass(thread, type, str, pos, negate ? -result : result);
}