#! /usr/bin/env python # -*- coding: utf-8 -*- # Time-stamp: "2007-10-11 00:27:04 sceptik" # Author: Alex A. Smirnov # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2, or (at # your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301 USA from math import modf import re ## FIXME: сделать внятные сообщения об ошибках # NS matches DECIMAL_MATCH = re.compile(r'^\d+$') DECIMAL2_MATCH = re.compile(r'^\w+(\.\w*)?$') ROMAN_MATCH = re.compile(r'^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$') BINARY_DECIMAL_MATCH = re.compile(r'^[0|1{0,3}|\.]+$') ALTERNATE_NUMERIC = {10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E', 15: 'F', 16: 'G', 17: 'H', 18: 'I', 19: 'J', 20: 'K', 21: 'L', 22: 'M', 23: 'N', 24: 'O', 25: 'P', 26: 'Q', 27: 'R', 28: 'S', 29: 'T', 30: 'U', 31: 'V', 32: 'W', 33: 'X', 34: 'Y', 35: 'Z'} RALTERNATE_NUMERIC = dict( zip(ALTERNATE_NUMERIC.values(), ALTERNATE_NUMERIC.keys()) ) ROMAN_FUNDAMENTAL = (('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)) DECIMAL_BINARY_EQ = {'0' : '0000', '1' : '0001', '2' : '0010', '3' : '0011', '4' : '0100', '5' : '0101', '6' : '0110', '7' : '0111', '8' : '1000', '9' : '1001', '.' : '.'} RDECIMAL_BINARY_EQ = dict( zip(DECIMAL_BINARY_EQ.values(), DECIMAL_BINARY_EQ.keys()) ) class RomanOutOfBounds(Exception): """ Raised when a numeral not a range from 0 to 4000.""" pass class NSValueError(Exception): """ Raised when a bad numeral found.""" pass def dec2roman(number): """ Dec -> Roman """ if not DECIMAL_MATCH.match( str(number) ): raise NSValueError(number) if not (0 < number < 4000): raise RomanOutOfBounds(number) result = '' for roman, value in ROMAN_FUNDAMENTAL: while number >= value: number -= value result += roman return result def roman2dec(number): """ Roman -> Dec """ if not ROMAN_MATCH.match(number): raise NSValueError(number) result = 0 index = 0 for roman, value in ROMAN_FUNDAMENTAL: while number[ index:index + len(roman) ] == roman: result += value index += len(roman) return result def dec2any(number, radix, accuracy=5): """ Dec -> Any """ if not number: return '0' result = '' fracp, intp = modf(number) number = int(intp) while number: division = number % radix number /= radix result += (ALTERNATE_NUMERIC[division] if radix > 10 and division >= 10 else str(division)) result = result[::-1] if fracp: result += ('.' if result else '0.') while fracp and accuracy: accuracy -= 1 fracp = fracp * radix fracp, intp = modf(fracp) result += (ALTERNATE_NUMERIC[int(intp)] if radix > 10 and intp >= 10 else str(int(intp))) return result def any2dec(number, radix): """ Any -> Dec """ if not DECIMAL2_MATCH.match(number): raise NSValueError, "any2dec: not a digit" power = 0 result = 0 dotpos = number.find('.') if dotpos > 0: power = -len(number[dotpos + 1:]) for position in number[::-1]: if position == '.': continue try: value = int(position) if value >= radix: raise NSValueError(number) except ValueError: if radix > 10 and position in RALTERNATE_NUMERIC.keys() \ and RALTERNATE_NUMERIC[position] < radix: value = RALTERNATE_NUMERIC[position] else: raise NSValueError(number) result += value * (radix ** power) power += 1 return result def dec2bindec(number): """ Decimal -> Binary-Decimal """ result = '' for position in str(number): result += DECIMAL_BINARY_EQ[position] return result def bindec2dec(number): """ Binary-Decimal -> Decimal """ if not number: return 0 if not BINARY_DECIMAL_MATCH.search(number): raise NSValueError(number) parts = str(number).split('.') if len(parts) > 1: parts[1] += ((4 - len(parts[1]) % 4) * '0' if len(parts[1]) % 4 else '') parts[0] = ((4 - len(parts[0]) % 4) * '0' if len(parts[0]) % 4 else '') + parts[0] results = [] for part in parts: result = '' for state in xrange(0, len(part) / 4): try: result += RDECIMAL_BINARY_EQ[part[state * 4:(state + 1) * 4]] except KeyError: raise NSValueError(number) results += [result] return '.'.join(results)