diff options
Diffstat (limited to 'bps/numeric.py')
| -rw-r--r-- | bps/numeric.py | 1192 |
1 files changed, 1192 insertions, 0 deletions
diff --git a/bps/numeric.py b/bps/numeric.py new file mode 100644 index 0000000..e0f8505 --- /dev/null +++ b/bps/numeric.py @@ -0,0 +1,1192 @@ +"""bps.numeric -- numeric extensions for bps3. + +this is mainly an extension of the 'math' library. +""" +#========================================================= +#imports +#========================================================= +#core +from __builtin__ import pow as intpow +import sys +from decimal import Decimal +from itertools import izip +trunc = int #clobbered by real implementation under py26+ +from math import * #NOTE: we import everything from math so this module can act as a drop-in replacement +#pkg +from bps.meta import isnum, isseq +from bps.rng import random, srandom +#local +__all__ = [ + + #numeric format conversions + 'int_to_base', +## 'base_to_int', + + 'float_to_base', +## 'base_to_float', + + 'int_to_roman', + 'roman_to_int', + + #byte strings + 'int_to_bytes', 'bytes_to_int', + 'list_to_bytes', 'bytes_to_list', + 'xor_bytes', 'and_bytes', 'or_bytes', 'neg_bytes', 'binop_bytes', + + #numthy + 'gcd', 'lcm', 'factors', + + #prime + 'is_prime', 'get_factors', + 'next_prime', 'prev_prime', 'iter_primes', + + #misc + 'sdivmod', 'splitfrac', + 'limit', 'avgsd', 'digits', + +] + +#calc bits-per-float... (usually 53) +BPF = 0 +while (1+2**-BPF) > 1: + BPF += 1 + +EMPTY_BYTES = "" + +#========================================================= +#misc +#========================================================= +def sdivmod(x,y): + """divmod variant which preserves sign of remainder""" + d,r = divmod(x,y) + if x < 0 and r > 0: #NOTE: r < 0 if x is negative Decimal instance + d += 1 + r -= y + return d,r + +def splitfrac(v): + "split number into integer portion and fractional portion; returns ``(int_part as int, frac_part as original type)``" + #NOTE: this reason this is present instead of various other solutions: + # modf(v) - always returns (float,float); whereas it's frequently needed for int part to be integer. + # also, modf coerces Decimal to float, this preserves Decimal in the fractional portion + # divmod(v,1) or v%1 - doesn't handle negative values correctly, and int part is not integer + # sdivmod(v,1) - int part is not integer, and too more complex for common case + if isinstance(v, (int,long)): + return v, 0 + else: + ip = trunc(v) + return ip, v - ip + +def limit(value, lower, upper): + """constraints value to specified range. + + :arg value: value to clamp + :arg lower: smallest value allowed + :arg upper: largest value allowed + + :returns: + value, if it's between lower & upper. + otherwise returns the appropriate limit; + + Usage Example:: + + >>> from bps.numeric import limit + >>> limit(-1,0,1) + 0 + >>> limit(.5,0,1) + .5 + >>> limit(100,0,1) + 1 + """ + if lower > upper: + raise ValueError, "lower must be <= upper" + if value < lower: + return lower + if value > upper: + return upper + return value + +def digits(value, base=10): + """Returns minimum number of digits required to represent value under a given base. + + :arg value: integer value to check. + :arg base: base to count the digits for (defaults to base 10). + + :returns: + Returns minimum number of digits needed under specified base. + Negative numbers will be converted to positive. + ``0`` is special-cased to always return ``1``. + Thus this will always return a value >= 1. + + Usage Example:: + + >>> from bps.numeric import digits + >>> digits(99,10) + 2 + >>> digits(100,10) + 3 + >>> digits(7,2) + 3 + >>> digits(8,2) + 4 + >>> digits(255,16) + 2 + """ + if value == 0: + return 1 + if value < 0: #ignore the minus sign + value = -value + return int(ceil(log(value+1, base))) + +def avgsd(args, sample=False): + "calc avg & std deviation of a sequence of numbers" + if not hasattr(args, "__len__"): + args = list(iter(args)) + if not args: + raise IndexError, "empty list passed in" + num = len(args) + avg = sum(args) / float(num) + if sample and num > 1: + num -= 1 + sigma = sqrt(sum((x - avg)**2 for x in args) / num) + return avg, sigma + +#=================================================== +#number theory functions +#=================================================== +def gcd(a, b): + """returns the greatest common divisor of the integers *a* and *b*.""" + if b < 0: + b = -b + while b: + a, b = b, (a % b) + return a + +def lcm(a, b): + """returns least common multiple of the integers *a* and *b*""" + # lcm = abs(a*b) / gcd(a,b) + if a == 0 and b == 0: + return 0 + ab = a*b + if ab < 0: + ab = -ab + g = gcd(a, b) + assert ab % g == 0 + return ab//g + +def factors(value): + """return list of factor of integer *value* + + :arg value: + The integer to factor. + This can be negative, 0, or positive. + + :returns: + This will return a list of prime & exponent pairs. + For example, ``factors(48)`` would return ``[(2,4),(3,1)])``, + signifying that ``(2**4) * (3**1) == 48``. + + Invariants: + + * All factors will be listed in ascending order, + * All exponents will be > 0. + + Special Cases: + * If the value is prime, a single entry will be returned, + being ``[(value,1)]``. + * Negative values will be made positive first. + * The numbers 0 and 1 will return empty lists. + + Usage Example:: + + >>> from bps.numeric import factors + >>> #factors for a prime are just itself + >>> factors(5) # 5**1 + [(5, 1)] + >>> #factors for a larger number... + >>> factors(104) # 2**3 * 13**1 + [(2, 3), (13, 1)] + >>> #factors for a negative act just like the positive + >>> factors(-10) + [(2, 1), (5, 1)] + """ + #TODO: find more efficient factoring algorithm + + if value < 0: + value = -value + if value < 2: + return [] + + #check if prime (should be quick) + if is_prime(value): + return [ (value, 1) ] + + #pull off prime factors as we find them + out = [] + for prime in iter_primes(): + count = 0 + while value % prime == 0: + value //= prime + count += 1 + if count: + out.append((prime, count)) + if value == 1: + return out + +#=================================================== +#prime iteration +#=================================================== + +#the first 64 primes, for quick testing. +_small_primes = [ + 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53, + 59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131, + 137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223, + 227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311, + ] + +#--------------------------------------------------- +#primality testing +#--------------------------------------------------- +def is_mr_prime(value, rounds=None): + """tests for primality using the miller-rabin test. + + :arg value: + The value to test for primality + :param rounds: + The number of rounds to use. + The chances of a false positive are ``1/(4**rounds)``. + + By default, the number of rounds will be chosen + such that the chances of a false positive are ``1/(value**2)``, + so the larger the prime, the more certain you can be :) + + :returns: + ``False`` if the number is confirmed composite. + ``True`` if the number is probably prime (w/in bounds + set by number of rounds). + """ + #boundary cases + if value < 2: + return False + elif value == 2 or value == 3: + return True + elif value & 1 == 0: + return False + + #determine number of rounds + if rounds is None: + #pick default number of rounds based on size of value. + #add 2 rounds for every bit in number... + #since prob of false positive is 4**(-rounds), + #prob of false positive for n is 4**(-log(n,2)), + #or (n**-2). + rounds=max(256, int(log(value,2))) +## max_rounds = value//4+1 +## if rounds > max_rounds: +## rounds = max_rounds + if rounds >= value-10: + #run through all witnesses [2..value-1] + rounds = value-2 + rounds_mode = 1 + else: + #randomly pick witnesses + rounds_mode = 0 + + #find positive ints s & d s.t. + # (2**s)*d == value-1, with d % 2 == 1 + vone = value-1 + assert vone & 1 == 0 + s = 0 + d = vone + while d & 1 == 0: + d >>= 1 + s += 1 + assert d & 1 + assert s > 0 + assert (1<<s)*d == vone + r_range = xrange(1, s) + + #test some candidates + #TODO: prevent repetitions? deal w/ small modulus? + randrange = random.randrange + for i in xrange(rounds): + #generate random candidate + if rounds_mode == 0: + a = randrange(2, vone) + else: + assert rounds_mode == 1 + a = i+2 + + #check if a**d = 1 or -1 mod value + x = intpow(a, d, value) + if x == 1 or x == vone: + #satisfied condition for primality witness, + #it's not a composite witness + #try another one + continue + +## #just to save some speed, run fermat primality test first +## # if a**vone is not 1, then a**(d*2**r) will never be 1 OR -1 for any r, +## # so there's no point in doing mr loop, and intpow is cheaper +## if intpow(a, vone, value) != 1: +## return False +## #...so now we only let primes & carmichael numbers through + + #check if a**(2**r*d) for 0<=r<=s-1, but above check already done for r=0 + for r in r_range: + x = intpow(x, 2, value) + if x == 1: + #no chance it'll ever be -1, it's a composite witness + return False + if x == vone: + #satisfied condition for primality witness, + #it's not a composite witness + #try another one + break #break 'r' loop; continue 'i' loop + else: + #no chance it'll ever be -1, it's a composite witness + return False + + #probably prime, change we're wrong is 1/4**rounds + return True + +def is_prime(value): + """Test if integer is prime. + + .. note:: + Implementation wise, this does a quick check + against some smaller primes, and then falls + back to the miller-rabin test. + """ + if value < 2: + return False + #do quick check for the small ones + for prime in _small_primes: + if value % prime == 0: + return value == prime + #anything up to square of last prime in list has to be prime + if value <= prime*prime: + return True + #fallback to miller-rabin + return is_mr_prime(value) + +#--------------------------------------------------- +#prime iteration +#--------------------------------------------------- + +###don't use, not cryptographically useful +##def rand_prime(bits, is_prime=is_prime, rng=srandom): +## """generate a random prime. +## +## :param bits: +## the minimum number of bits, +## s.t. ``log(prime,2) >= bits`` +## +## """ +## rng.reseed() +## #generate odd number between 1<<(bits-1) and 1<<bits +## cur = (rng.getrandbits(bits-1)<<1) + 1 +## while True: +## if is_prime(cur): +## return cur +## cur += 2 + +def iter_primes(start=0, stop=None, count=None, rounds=None): + """generator which returns sequential primes. + + :param start: + Starting point for generating primes. + The first value generates will be the smallest + prime which is greater than or equal to *start*. + This defaults to ``0``, which means + all primes starting with ``2`` will be genereated in order. + + :param stop: + If specified, the generator will stop + after yeilding the largested prime which is + strictly less than *stop*. + + :param count: + If specified, the generator will stop + after yielding *count* prime numbers. + If a *stop* is also specified, + the generator will halt on whichever + condition is reached first. + + :param rounds: + Optionally lock the number of rounds + used by the Miller-Rabin test. + This is generally not needed. + + :returns: + A generator which will yield ascending prime numbers + according to the parameters above. + """ + if stop or count: + #wrap ourselves with a counter + idx = 0 + for prime in iter_primes(start, rounds=rounds): + if stop and prime >= stop: + return + yield prime + idx += 1 + if count and idx >= count: + return + + #yield from small primes list to get things started + global _small_primes + top = _small_primes[-1] + if start <= top: + for prime in _small_primes: + if prime < start: + continue + yield prime + cur = prime+2 + else: + cur = start|1 + #iterate one by one, using modified is_prime() to test + assert cur > top + assert cur & 1 == 1 + while True: + for prime in _small_primes: + if cur % prime == 0: + break + else: + if is_mr_prime(cur, rounds=rounds): + yield cur + cur += 2 + +def next_prime(value, rounds=None): + "return the smallest prime strictly greater than the specified value" + #pick from small primes list + top = _small_primes[-1] + if value <= top: + for prime in _small_primes: + if prime <= value: + continue + return prime + value = prime+2 + elif value & 1: + value += 2 + else: + value += 1 + #iteratoe one by one, using modified is_prime to test + assert value > top + assert value & 1 + while True: + for prime in _small_primes: + if value % prime == 0: + break + else: + if is_mr_prime(value, rounds=rounds): + return value + value += 2 + +def prev_prime(value, rounds=None): + "return the largest prime strictly less than the specified value, or ``None``" + top = _small_primes[-1] + + #pick from small primes list + if value <= top: + for prime in reversed(_small_primes): + if prime >= value: + continue + return prime + assert value <= 2 + return None + + #step value down to next candidate + if value & 1: + value -= 2 + else: + value -= 1 + assert value & 1 + assert value >= top + + #iteratoe one by one, until we reach top of preset list + while value > top: + for prime in _small_primes: + if value % prime == 0: + break + else: + if is_mr_prime(value, rounds=rounds): + return value + value -= 2 + return top + +#========================================================= +#int <-> binary encoded string +#========================================================= +def _log256_ceil(value): + "helper for mchr & mord" + #FIXME: probably a nice clever way to do this w/ integer / bitshifting + if value < 0: + return 0 + return int(ceil(log(value, 256))) + +def int_to_bytes(num, bytes=None, upper=None, order="big"): + """Returns a multi-character string corresponding to the ordinal *num*. + + Bit String Encoding: + This is the multi-character equivalent of :func:`chr`, with some additional features. + It takes in a positive integer, and returns a string representation, + packed into a specified number of bytes. + + :arg num: + The positive integer to encode. + :param bytes: + Optionally, the number of bytes to encode integer into. + If specified, this will be the length of the returned string. + If not specified, this will default to the minimum number + required to encode the number. + :param upper: + Upper bound allowed for the number. + If not specified, but *bytes* is, upper will default + to the largest number representable by that number of bytes. + If num is equal to or larger than upper, a ValueError will be raised. + :param order: + Byte ordering: "big", "little", "native". + The default is "big", since this the common network ordering, + and "native" as the default would present poor cross-platform predictability. + + :returns: + The number encoded into a string, according to the options. + + Usage Example:: + + >>> from bps.numeric import bytes_to_int, int_to_bytes + >>> int_to_bytes(1234, bytes=4) + '\\x00\\x00\\x04\\xd2' + + >>> int_to_bytes(1234, bytes=4, order="little") + '\\xd2\\x04\\x00\\x00' + + >>> bytes_to_int('\\x00\\x00\\x04\\xd2') + 1234 + """ + #TODO: would a "bits" keyword be useful? + if bytes is not None: + #encode in specified number of bytes + if bytes < 1: + raise ValueError, "bytes must be None or >= 1: %r" % (bytes,) + bupper = 256**bytes + if upper is None: + upper = bupper + elif upper > bupper: + raise ValueError, "upper bound too large for number of bytes: bytes=%r upper=%r" % (bytes, upper) + else: + if upper is None: + upper = num+1 + bytes = _log256_ceil(upper) + if upper < 0: + raise ValueError, "top must be >= 0: %r" % (upper,) + if num < 0 or num >= upper: + raise ValueError, "expected number between 0 and %d: %d" % (upper-1, num) + if order == "native": + order = sys.byteorder + if order == "big": #encode bytes-1 byte first, 0 byte last + itr = xrange(bytes*8-8, -8, -8) + else: #encode 0 byte first, bytes-1 byte last + assert order == "little" + itr = xrange(0, bytes*8, 8) + return EMPTY_BYTES.join( + chr((num >> offset) & 255) + for offset in itr + ) + +def list_to_bytes(value, bytes=None, order="big"): + """Returns a multi-character string corresponding to a list of byte values. + + This is similar to :func:`int_to_bytes`, except that this a list of integers + instead of a single encoded integer. + + :arg value: + The list of integers to encode. + It must be true that ``all(elem in range(0,256)) for elem in value``, + or a ValueError will be raised. + + :param bytes: + Optionally, the number of bytes to encode to. + If specified, this will be the length of the returned string. + + :param order: + Byte ordering: "big", "little", "native". + The default is "big", since this the common network ordering, + and "native" as the default would present poor cross-platform predictability. + + :returns: + The number encoded into a string, according to the options. + + Usage Example:: + + >>> from bps.numeric import list_to_bytes, bytes_to_list + >>> list_to_bytes([4, 210], 4) + '\\x00\\x00\\x04\\xd2' + + >>> list_to_bytes([4, 210], 4, order="little") + '\\xd2\\x04\\x00\\x00' + + >>> bytes_to_list('\\x00\\x00\\x04\\xd2') + [4, 210] + """ + #make sure all elements have valid values + if any( elem < 0 or elem > 255 for elem in value): + raise ValueError, "value must be list of integers in range(0,256): %r" % (value,) + + #validate bytes / upper + if bytes is None: + bytes = len(value) + if bytes == 0: + raise ValueError, "empty list not allowed" + else: + if bytes < 1: + raise ValueError, "bytes must be None or >= 1: %r" % (bytes,) + if len(value) > bytes: + raise ValueError, "list too large for number of bytes: bytes=%r len=%r" % (bytes, len(value)) + + #encode list in big endian mode + out = ''.join( chr(elem) for elem in value ) + pad = bytes-len(out) + + #pad/reverse as needed for endianess + if order == "native": + order = sys.byteorder + if order == "big": + if pad: + out = ('\x00' * pad) + out + else: + assert order == "little" + if pad: + out = out[::-1] + ('\x00' * pad) + else: + out = out[::-1] + return out + +def bytes_to_int(value, order="big"): + """decode a string into an integer representation of it's binary values. + + Bit String Decoding: + This returns a positive integer, as decoded from the string. + This is the inverse of the :func:`int_to_bytes` function. + + :arg value: + The string to decode. + :param order: + The byte ordering, defaults to "big". + See :func:`int_to_bytes` for more details. + + :returns: + The decoded positive integer. + """ + if not value: + return 0 + upper = len(value) #upper bound in bytes + if order == "native": + order = sys.byteorder + if order == "big": + itr = xrange(upper*8-8, -8, -8) + else: + assert order == "little" + itr = xrange(0, upper*8, 8) + return sum( + ord(value[idx]) << offset + for idx, offset in enumerate(itr) + ) + +def bytes_to_list(value, order="big"): + """decode a string into a list of numeric values representing each of it's bytes. + + This is similar to :func:`bytes_to_int`, the options and results + are effectively the same, except that this function + returns a list of numbers representing each byte in sequence, + with most significant byte listed first. + + :arg value: + The string to decode. + :param order: + The byte ordering, defaults to "big". + See :func:`int_to_bytes` for more details. + + :returns: + The decoded list of byte values. + """ + if order == "native": + order = sys.byteorder + if order == "big": + return [ ord(c) for c in value ] + else: + assert order == "little" + return [ ord(c) for c in reversed(value) ] + +def _bytes_align(left, right, order): + "helper used by xor_bytes, and_bytes, etc. to align strings" + l = len(left) + r = len(right) + if l != r: + if order is None: + raise ValueError, "strings are not same size: %r %r" % (l, r) + if order == "native": + order = sys.byteorder + if order == "big": + #right-align strings by padding left with nulls + if l < r: + left = ("\x00" * (r-l)) + left + else: + right = ("\x00" * (l-r)) + right + else: + assert order == "little" + #left-align strings by padding right with nulls + if l < r: + left += ("\x00" * (r-l)) + else: + right += ("\x00" * (l-r)) + assert len(left) == len(right) + return left, right + +def binop_bytes(left, right, op, order=None): + """perform arbitrary bit-wise logical operation on two bit strings. + + :arg left: + left bit string + :arg right: + right bit string + :arg op: + This should be a callable with the syntax ``op(left_value,right_value) -> result_value``. + It will be called for every byte in the two strings, + and will be passed each byte as an integer. + It should then return the resulting byte. + + :param order: + This sets the byte ordering of the strings, + which only really effects how the function deals + with strings of different sizes. + + =============== ===================================================== + Value Action + --------------- ----------------------------------------------------- + ``None`` No byte ordering is specified (the default). + The strings must be exactly the same size, + or a :exc:`ValueError` will be raised. + + ``"big"`` Big-endian byte ordering. + If the two strings are of unequal lengths, + the smaller one will be right-aligned, + so that the least significant digits line up. + The resulting string will be as long as the + long of the two inputs. + + ``"little"`` Little-endian byte ordering. + If the two strings are of unequal lengths, + the smaller one will be left-aligned, + so that the least significant digits line up. + The resulting string will be as long as the + long of the two inputs. + + ``"native"`` The native byte ordering (``"little"`` or ``"big"``) + will be used. + =============== ===================================================== + + :returns: + The resulting bit string + """ + left, right = _bytes_align(left, right, order) + return "".join( + chr(op(ord(a), ord(b))) + for a, b in izip(left, right) + ) + +#bytes_xor +def xor_bytes(left, right, order=None): + """XOR two bit strings together. + + This is the equivalent of ``int_to_bytes(bytes_to_int(left) ^ bytes_to_int(right))``. + + :arg left: + The first bit string to perform the operation on. + :arg right: + The second bit string to perform the operation on. + :param order: + The byte ordering to for aligning + strings of different sizes. + See :func:`binop_bytes` for details. + """ + left, right = _bytes_align(left, right, order) + return "".join( + chr(ord(a) ^ ord(b)) + for a, b in izip(left, right) + ) + +#bytes_and +def and_bytes(left, right, order=None): + """AND two bit strings together. + + This is the equivalent of ``int_to_bytes(bytes_to_int(left) & bytes_to_int(right))``. + + :arg left: + The first bit string to perform the operation on. + :arg right: + The second bit string to perform the operation on. + :param order: + The byte ordering to for aligning + strings of different sizes. + See :func:`binop_bytes` for details. + """ + left, right = _bytes_align(left, right, order) + return "".join( + chr(ord(a) & ord(b)) + for a, b in izip(left, right) + ) + +#bytes_or +def or_bytes(left, right, order=None): + """OR two bit strings together. + + This is the equivalent of ``int_to_bytes(bytes_to_int(left) | bytes_to_int(right))``. + + :arg left: + The first bit string to perform the operation on. + :arg right: + The second bit string to perform the operation on. + :param order: + The byte ordering to for aligning + strings of different sizes. + See :func:`binop_bytes` for details. + """ + left, right = _bytes_align(left, right, order) + return "".join( + chr(ord(a) | ord(b)) + for a, b in izip(left, right) + ) + +#bytes_neg +def invert_bytes(value): + """invert a bit string (1->0, 0->1) + + This is the equivalent of ``int_to_bytes(~bytes_to_int(value))``. + """ + return "".join( chr(256+~ord(a)) for a in value) + +#========================================================= +#counting systems +#========================================================= + +#set of chars used by int_to_base() +_chars = "0123456789abcdefghijklmnopqrstuvwxyz" + +def int_to_base(value, base=10, pad=0): + """convert integer to specified base. + + The builtin python function :func:`int` + has the option for converting integers from string + format using a variety of bases. + + This is the inverse, which converts an integer + into a string of the specified base. + + :arg value: + The integer to convert + :arg base: + The base to use. Must be between 2 and 36, inclusive. + :param pad: + Optionally add zeros to left of number until string is at least ``pad`` + characters long. + + It should always be true that ``int(to_base(n,b),b) == n``. + + Usage Example:: + + >>> from bps.numeric import int_to_base + >>> int_to_base(123456789,32) + '3lnj8l' + >>> int('3lnj8l',32) + 123456789 + >>> int_to_base(123456789,16) + '75bcd15' + >>> 0x75bcd15 + 123456789 + """ + if base < 2 or base > 36 or int(base) != base: + raise ValueError, "base must be between 2 and 36, inclusive: %r" % (base,) + if value == 0: + return "0" + if value < 0: + neg = True + value = -value + else: + neg = False + out = "" + while value > 0: + out = _chars[ value % base ] + out + value = int(value/base) + if pad and len(out) < pad: + out = ('0' * (pad-len(out))) + out + if neg: + out = "-" + out + return out + +base_to_int = int #convience alias for reverse of int_to_base + +def float_to_base(value, base, fsize=-1, ftrim=True): + """convert float to specified base""" + if base < 2 or base > 36 or int(base) != base: + raise ValueError, "base must be between 2 and 36, inclusive: %r" % (base,) + #split into int & frac parts + fp, ip = modf(value) + assert int(ip) == ip + #format int part + text = int_to_base(int(ip), base) + if fsize == 0: + return text + text += "." + + #determine default fsize + if fsize == -1: ##or fsize == -2: + #show digits to max system precision + bits = BPF + if ip: + bits -= 1+int(floor(log(abs(ip), 2))) #account for integer bits + if bits < 0: + fsize = 0 + else: + fsize = int(ceil(log(1<<bits, base))) #num digits under base + #TODO: under fsize==-1 + ftrim, + # could implement "pick shortest repr" algorithm + # from py3.1 + elif fsize < -1: + raise ValueError, "fsize must be >= -1: %r" % (fsize,) + + #scale fp up to fsize, and round it + r, m = modf(abs(fp) * (base**fsize)) + m = int(m) + if r >= .5: + m += 1 + + #render in reverse order + out = '' + for d in xrange(fsize): + out += _chars[m % base] + m //= base + + #trim the zeroes, reverse, and return + if ftrim: + out = out.lstrip("0") + if not out: + out = "0" + return text + out[::-1] + +#todo: base_to_float + +#========================================================= +#roman numerals +#========================================================= + +##def int_to_barred_roman(value): +## """convert integer to parsed list of roman numerals, +## suitable for rendering with overscores via post-processing. +## """ +## #return tuple of roman numerals s.t. +## #last string in list should have no overscore, +## #2nd from last should have 1 overscore, +## #3rd from last should have 2 overscores, etc. +## out = [] +## def helper(value, bars): +## if value >= 4000: +## #more than 4000, got to invoke special rules +## if value % 10000 < 4000: +## #put the thousands w/ current bar level, since <4000 +## x = value//1000 +## helper(x - (x%10), bars+1) +## value %= 10000 +## else: +## #else put the thosands w/ next bar level, since >=4000 +## helper(value//1000, bars+1) +## value %= 1000 +## if value > 0: +## temp = int_to_roman(value) +## out.append((temp, bars)) +## helper(value, 0) +## return out + +_roman_level = "IVXLCDM" +_roman_decimal = "IXCM" +_roman_values = dict(I=1, V=5, X=10, L=50, C=100, D=500, M=1000) +_roman_standard = [ + ('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), + ('C', 100), ('XC', 90), ('L', 50), ('XL', 40), + ('X', 10), ('IX', 9), ('V', 5), ('IV', 4), + ('I', 1), + ] +_roman_additive = [ + ('M', 1000), ('D', 500), ('C', 100), ('L', 50), + ('X', 10), ('V', 5), ('I', 1), + ] + +def int_to_roman(value, dialect="standard"): ##, bar="~"): + "convert integer to roman numerals" + #disable till there's a need, and a parser... +## if mode == "barred": +## return "".join( +## "".join( +## (bar * count) + c +## for c in elem +## ) +## for elem, count in int_to_barred_roman(value) +## ) + if dialect == "additive": + pairs = _roman_additive + max_mult = 4 + else: + assert dialect == "standard" + pairs = _roman_standard + max_mult = 3 + max_value = 4*pairs[0][1] + if value < 1: + raise ValueError, "value too small: %r" % (value,) + if value >= max_value: + raise ValueError, "value too large: %r >= %r" % (value, max_value) + out = '' + for char, mag in pairs: + if value >= mag: + mult = value//mag + assert 1 <= mult <= max_mult + #^ thanks to 9/5/4 pairs, we shouldn't ever have mults larger + assert mult == 1 or char in _roman_decimal + #^ thanks to 1 pairs, 9/5/4 pairs shouldn only have 0/1 mult + out += char * mult + value -= mag * mult + assert value == 0 + return out + +def roman_to_int(value, strict=False): + """convert roman numerals to integer. + + This function accepts all properly formed roman numerals (eg ``"xiv"``), + but will also attempt grammatically incorrect strings (eg ``"iiiiv"``), + but will reject ones which aren't interpretable as a valid positive integer (eg ``"vvx"``). + Such invalid values will result in a ValueError. + + :param strict: + If this is set to ``True``, the input must a a proper roman numeral. + That is to say, the subtraction only allows for + a single "I" before a "V" or "X", a single "X" before a "L" or "C", + and a single "C" before a "D" or "M", and all additive letters + must occur in decreasing value if they occur at all. + Under strict mode, any violations of this rule will cause a ValueError. + """ + orig = value + value = value.strip().upper() + if not value: + raise ValueError, "invalid literal for int_from_roman: %r" % (orig,) + if any(c not in _roman_level for c in value): + raise ValueError, "invalid literal for int_from_roman: %r" % (orig,) + if strict: + return _strict_parse_roman(orig, value) + else: + return _parse_roman(orig, value, len(value)-1, 999)[1] + +def _strict_parse_roman(orig, value): + "parser used by int_from_roman" + out = 0 + for char, mag in _roman_standard: + if char in _roman_decimal: #only IXCM are allowed to repeat + count = 0 + while value.startswith(char): + if count == 4: + #max of 4 repetitions is allowed to permit additive style, + #5 is just plain too many + raise ValueError, "invalid synatax for int_from_roman: %r" % (orig,) + out += mag + value = value[len(char):] + count += 1 + elif value.startswith(char): # VLD and the subtractive pairs can occur only once + out += mag + value = value[len(char):] + if value: + raise ValueError, "invalid syntax for int_from_roman: %r" % (orig,) + return out + +def _parse_roman(orig, value, idx, stop_level): + "parser used by int_from_roman()" + out = 0 + cur_level = -1 + while idx > -1: + char = value[idx] + level = _roman_level.find(char) + if level >= stop_level: + #if we hit higher level, return value and cursor + return idx, out + if level < cur_level: + #if dropped down from last level, this is beginning of a substraction stanza, + #which will last for all chars from to the left of idx (including idx), + #until (but excluding) the first char w/ the same level as cur_level + idx, diff = _parse_roman(orig, value, idx, cur_level) + out -= diff + if out < 1: + raise ValueError, "invalid syntax for int_from_roman: %r" % (orig,) + else: + #else we're at old level or better + out += _roman_values[char] + cur_level = level + idx -= 1 + return -1, out + +#========================================================= +#misc +#========================================================= + +##def iter_fibonacci(): +## yield 1 +## yield 1 +## last = 1 +## cur = 1 +## while True: +## last, cur = cur, last+cur +## yield cur + + +##def seqsum(*seqs): +## """generate a list that contains the element-wise sum of all the sequences passed in. +## the result will be as long as the longest sequence passed in, +## and any shorter input sequences will be implicitly right-padded with zeros. +## """ +#### if len(seqs) == 1 and isseq(seqs[0]): +#### seqs = seqs[0] +## if not seqs: +## return [] +## if isnum(seqs[0]): +## assert len(seqs) % 2 == 0 +## #assume it's a sequence of [weight1, seq1, weight2, seq2 ... ], +## size = max(len(seq) for seq in seqs[1::2]) +## return [ +## sum( +## seqs[col] * ( +## seqs[col+1][idx] if idx < len(seqs[col+1]) else 0 +## ) +## for col in xrange(0, len(seqs), 2) +## ) +## for idx in xrange(size) +## ] +## elif isseq(seqs[0]) and len(seqs[0]) == 2 and isseq(seqs[0][1]): +## #assume it's a sequence of [ (weight1, seq1), (weight2, seq2) ... ], +## size = max(len(row[1]) for row in seqs) +## return [ +## sum( +## weight * (seq[idx] if idx < len(seq) else 0) +## for weight, seq in seqs +## ) +## for idx in xrange(size) +## ] +## else: +## #assume it's a sequence of [seq1, seq2...] +## if len(seqs) == 1: +## return list(seqs[0]) +## size = max(len(seq) for seq in seqs) +## return [ +## sum((seq[idx] if idx < len(seq) else 0) for seq in seqs) +## for idx in xrange(size) +## ] + +#========================================================= +#eof +#========================================================= |
