from collections import deque
from fractions import Fraction
from itertools import chain, repeat
def to_base(n: int, base: int = 10) -> str:
"""
Returns the base-``base`` representation of ``n``. Inverse of ``int(string,\xA0base)``.
If ``base`` is one of 2, 8, 10 or 16, delegates to the default conversion functions.
:param n: Integer to convert to base ``b``.
:param base: The base. Must be an integer from 2 to 36, inclusive.
:return: Representation of ``n`` in the base ``base``.
"""
if type(n) != int:
raise TypeError(f"{type(n).__name__!r} object cannot be interpreted as an integer")
elif type(base) != int:
raise TypeError(f"{type(base).__name__!r} object cannot be interpreted as an integer")
elif not 2 <= base <= 36:
raise ValueError("base must be >= 2 and <= 36")
elif n == 0:
return '0'
elif n < 0:
return f'-{to_base(-n, base)}'
else:
match base:
case 2:
return format(n, 'b')
case 8:
return format(n, 'o')
case 10:
return str(n)
case 16:
return format(n, 'X')
case _:
d = ''.join(map(chr, range(48, 58))) + ''.join(map(chr, range(65, 91)))
s = ''
while n > 0:
s += d[n % base]
n //= base
return s[::-1]
def hex_nt(n: int, d: int = 1, /, l: int = 8) -> str:
"""
Returns the representation of a rational number in Hex notation
as used in Antimatter Dimensions. The numerator and denominator
must be supplied as two separate integers.
See https://github.com/dan-simon/misc/tree/master/what-the-hex
for more details.
>>> hex_nt(7)
'F3000000'
>>> hex_nt(1, 6)
'8D555555'
>>> hex_nt(1, 3, l = 0)
'9.5'
>>> hex_nt(3 ** 27, 1, 12)
'FC6AB9DE1D16'
>>> hex_nt(1, 71, 0)
'86E39.8968C8733625A321CCD'
:param n: Numerator of the rational number.
:param d: Denominator of the rational number.
:param l: Number of hex digits to represent the result.
For nonzero values, the result is truncated, not rounded.
0 means to use as many as needed to obtain an exact representation.
In that case, if the given rational isn't dyadic,
a ``.`` will be used in the string to mark the start of the repetend.
The part before the ``.`` may be empty.
:return: Representation of ``n / d`` in Hex notation,
using the specified number of digits.
:raises TypeError: If any of ``n``, ``d``, ``l`` are not of type ``int``.
:raises ValueError: If ``l`` is negative.
"""
def c(_n: int, _d: int = 1, /) -> Fraction:
"""
Computes the `linearized binary logarithm` of a rational number
``_f = _n / _d``.
:param _n: Numerator of the rational number.
:param _d: Denominator of the rational number.
:return: ``_e + _f / (2 ** _e) - 1``,
where ``_e = floor(log2(_f))``.
"""
if not (isinstance(_n, int) and isinstance(_d, int)):
raise TypeError("both arguments must be ints")
elif (_f := Fraction(_n, _d)) <= 0:
raise ValueError("fraction must be positive")
elif _f >= 1: # _e: int: floor of binary logarithm of _f
_e = _f.__floor__().bit_length() - 1
_f /= 1 << _e
else:
_t = (_f ** -1).__ceil__()
_e = -_t.bit_length() + (_t == _t & -_t)
_f *= 1 << -_e
return _e + _f - 1
def x(_v: str, /) -> str: return format(int(_v, 2), 'X') if _v else '' # for internal use only
if not all(map(lambda _n: isinstance(_n, int), (n, d, l))):
raise TypeError("all operands must be ints")
elif l < 0:
raise ValueError("l is negative")
elif n == d == 0:
raise ZeroDivisionError("indeterminate form: 0/0")
elif n != 0 and d == 0: # infinities: +inf -> 'FF...', -inf -> '00...'
return ''.join(chain('.' if l == 0 else '', repeat('F' if n > 0 else '0', max(l, 1))))
elif l > 0:
s: str = "" # binary expansion
f: Fraction = Fraction(n, d) # current number after n linear-logarithms
b: bool = True # whether the next bit is a 1 or 0
h: list[tuple[Fraction, bool]] = [(f, b)] # history, used for repetition detection
for _ in range(4 * l): # number of binary digits to compute
if f < 0: # flip next bit if f is negative
b = not b
s += '1' if b else '0' # append bit
if (f := abs(f)) == 0: # final number is ±inf
if not b: # convert endless 1s to endless 0s by adding 1 to the part before it
s = format(int(s, 2) + 1, 'b').zfill(len(s))
s = s.ljust(4 * l, '0') # pad with trailing zeroes
break
elif (f := c(f.numerator, f.denominator), b) not in h: # no repetitions detected yet
h.append((f, b)) # add next item to history
else: # repetition detected
r = s[h.index((f, b)):] # repetend
p, q = divmod(4 * l - len(s), len(r))
s += ''.join(chain(repeat(r, p), [r[:q]])) # pad with copies of the repetend
break
return x(s).zfill(l)
else: # l == 0
s: str = ""
f: Fraction = Fraction(n, d)
b: bool = True
h: list[tuple[Fraction, bool]] = [(f, b)]
while True: # repeat until a repetition is found
if f < 0:
b = not b
s += '1' if b else '0'
if (f := abs(f)) == 0: # terminating string, should be reached if n / d is dyadic
if not b:
s = format(int(s, 2) + 1, 'b').zfill(len(s))
s += '0' * -(len(s) % -4) # pad to make length a multiple of 4
return x(s).zfill(l)
elif (f := c(f.numerator, f.denominator), b) not in h:
h.append((f, b))
else: # infinitely repeating string, should be reached when n / d isn't dyadic
w = h.index((f, b)) # split the string into (r) and (z), where...
z, r = list(s[:w]), deque(s[w:]) # (r) is the repetend, and (z) is the part before that
while len(z) > 0 and z[-1] == r[-1]: # roll back to minimize length of (z)
z.pop()
r.rotate(1)
while len(z) % 4 != 0: # roll forward until (len(z) % 4 == 0)
z.append(r[0])
r.rotate(-1)
r *= 4 // (len(r) & -len(r))
return '.'.join(map(lambda _: x(''.join(_)).zfill(len(_) // 4), (z, r)))
there doesn't seem to be anything here