IsDecimalMagnitudeBelow🔗

Type: Transpilable verification function
Check that the magnitude of the decimal text is below limit.

The text is expected to match the pattern of a finite xs:double or xs:float, i.e. , a decimal with an optional sign, an optional point and an optional exponent. The limit is expected to be a positive integer without leading zeros.

We compare the numbers as strings, since they do not necessarily fit into any native number, and parsing them to floating-point numbers would round them.

text🔗
Decimal to be checked
limit🔗
Exclusive upper bound on the magnitude
Return🔗
True if the magnitude of text is below limit

Code

unsigned = text.lstrip("+-")

exponent_start = unsigned.find("e")
if exponent_start == -1:
    exponent_start = unsigned.find("E")

mantissa = unsigned
exponent_text = ""
if exponent_start != -1:
    mantissa = unsigned[:exponent_start]
    exponent_text = unsigned[exponent_start + 1 :]

point = mantissa.find(".")

integer_part = mantissa
fraction_part = ""
if point != -1:
    integer_part = mantissa[:point]
    fraction_part = mantissa[point + 1 :]

# NOTE (mristin):
# We represent the decimal as the significant digits ``0.d1 d2 ...`` times
# ten to the power of the magnitude. The significant digits are given by
# the ``head`` followed by the ``tail``.
head = integer_part.lstrip("0")
tail = fraction_part
shift: int = len(head)

if head == "":
    tail = fraction_part.lstrip("0")
    if tail == "":
        # The decimal is zero.
        return True

    shift = len(tail) - len(fraction_part)

exponent_digits = exponent_text.lstrip("+-").lstrip("0")

# NOTE (mristin):
# An exponent with more than 15 digits does not safely fit into an integer.
# Its magnitude then dwarfs the number of the digits in any text, so its sign
# alone decides.
if len(exponent_digits) > 15:
    return exponent_text[:1] == "-"

exponent: int = 0
if len(exponent_digits) > 0:
    exponent = int(exponent_digits)

    if exponent_text[:1] == "-":
        exponent = -exponent

magnitude = exponent + shift

if magnitude != len(limit):
    return magnitude < len(limit)

for i in range(0, len(limit)):
    digit = _digit_at(head, tail, i)
    limit_digit = int(limit[i : i + 1])

    if digit != limit_digit:
        return digit < limit_digit

# NOTE (mristin):
# The decimal starts with all the digits of the limit, so it is at least as
# large as the limit.
return False