///|
/// Exact decimal arithmetic for schema assertions only. A nonzero value is
/// sign * digits * 10^exponent, with no leading or trailing zero digits.
/// The exponent stores its value with arbitrary precision, never as an Int.
priv struct SchemaNumber {
  negative : Bool
  digits : String
  exponent : @bigint.BigInt
}

///|
/// Read a valid JSON number supplied by the JSON parser. Storage depends on
/// the written numeral, not on the size of the value or its exponent.
fn schema_number(raw : String) -> SchemaNumber {
  let chars = raw.to_array()
  let negative = chars[0] == '-'
  let mut index = if negative { 1 } else { 0 }
  let digits : Array[Char] = []
  let mut fraction = false
  let mut fraction_length = 0
  while index < chars.length() && chars[index] != 'e' && chars[index] != 'E' {
    if chars[index] == '.' {
      fraction = true
    } else {
      digits.push(chars[index])
      if fraction {
        fraction_length = fraction_length + 1
      }
    }
    index = index + 1
  }
  let mut first = 0
  while first < digits.length() && digits[first] == '0' {
    first = first + 1
  }
  if first == digits.length() {
    return { negative: false, digits: "0", exponent: 0N, }
  }
  let mut last = digits.length()
  while digits[last - 1] == '0' {
    last = last - 1
  }
  let exponent = if index == chars.length() {
    0N
  } else {
    // BigInt's decimal reader does not accept the optional plus sign.
    let start = if chars[index + 1] == '+' { index + 2 } else { index + 1 }
    @bigint.BigInt::from_string(String::from_array(chars[start:]))
  }
  {
    negative,
    digits: String::from_array(digits[first:last]),
    exponent: exponent +
    @bigint.BigInt::from_int(digits.length() - last - fraction_length),
  }
}

///|
fn SchemaNumber::is_integer(self : SchemaNumber) -> Bool {
  self.exponent >= 0N
}

///|
/// Compare normalized magnitudes, padding only during digit inspection. No
/// string of exponent-sized zeros is ever allocated, even for huge exponents.
fn SchemaNumber::compare(self : SchemaNumber, other : SchemaNumber) -> Int {
  if self.digits == "0" || other.digits == "0" {
    if self.digits == other.digits {
      return 0
    }
    if self.digits == "0" {
      return if other.negative { 1 } else { -1 }
    }
    return if self.negative { -1 } else { 1 }
  }
  if self.negative != other.negative {
    return if self.negative { -1 } else { 1 }
  }
  let magnitude = self.exponent + @bigint.BigInt::from_int(self.digits.length())
  let other_magnitude = other.exponent +
    @bigint.BigInt::from_int(other.digits.length())
  let mut order = magnitude.compare(other_magnitude)
  if order == 0 {
    let left = self.digits.to_array()
    let right = other.digits.to_array()
    for index in 0.. Int {
  schema_number(left).compare(schema_number(right))
}

///|
fn schema_number_equal(left : String, right : String) -> Bool {
  schema_number_compare(left, right) == 0
}

///|
fn schema_number_gcd(
  first : @bigint.BigInt,
  second : @bigint.BigInt,
) -> @bigint.BigInt {
  let mut a = first
  let mut b = second
  while b != 0N {
    let remainder = a % b
    a = b
    b = remainder
  }
  a
}

///|
/// The divisor has been prechecked as strictly positive. Reduce A/B, then
/// cancel the denominator using the factors of 10 provided by the exponent.
/// Loops depend on coefficient sizes, never on the exponent's numeric value.
fn schema_number_multiple_of(value_raw : String, divisor_raw : String) -> Bool {
  let value = schema_number(value_raw)
  if value.digits == "0" {
    return true
  }
  let divisor = schema_number(divisor_raw)
  let delta = value.exponent - divisor.exponent
  if delta < 0N {
    // Normalization removed every trailing zero from A. Its reduced numerator
    // A/g divides A and therefore cannot contain a factor of 10 either. A
    // negative delta would require one or more such factors, so it cannot match.
    return false
  }
  let a = @bigint.BigInt::from_string(value.digits)
  let b = @bigint.BigInt::from_string(divisor.digits)
  let mut denominator = b / schema_number_gcd(a, b)
  for factor in [2N, 5N] {
    let mut count = 0N
    while denominator % factor == 0N {
      denominator = denominator / factor
      count = count + 1N
      if count > delta {
        return false
      }
    }
  }
  denominator == 1N
}

///|
/// Convert only after exact integer and range checks. The implementation cap
/// guarantees at most ten digits and an exponent from zero through nine.
fn schema_number_count(raw : String) -> Result[Int, SchemaProblemKind] {
  let number = schema_number(raw)
  if number.negative || !number.is_integer() {
    return Err(InvalidSchema)
  }
  if number.compare(schema_number("1000000000")) > 0 {
    return Err(UnsupportedFeature)
  }
  let mut count = 0
  for digit in number.digits.to_array() {
    count = count * 10 + digit.to_int() - '0'.to_int()
  }
  // The exact comparison above proves this conversion cannot overflow.
  for _ in 0..