Last active
January 10, 2022 09:47
-
-
Save hezicyan/3f84a70edf01e2dd3bbd3718ad21b70c to your computer and use it in GitHub Desktop.
A base converter supporting fractions, negative number and even negative base, and it has commands and graphical user interface.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3.8 | |
# -*- coding: utf-8 -*- | |
__all__ = ['mod', 'to_decimal', 'to_base', 'convert', 'DEFAULT_TABLE'] | |
from math import ceil, floor | |
from string import ascii_lowercase, ascii_uppercase, digits | |
from typing import Tuple, Union | |
DEFAULT_TABLE = digits + ascii_uppercase + ascii_lowercase | |
def mod(dividend: int, divisor: int) -> Tuple[int, int]: | |
quotient, remainder = dividend // divisor, dividend % divisor | |
if remainder < 0: | |
quotient += 1 | |
remainder -= divisor | |
return quotient, remainder | |
def to_decimal(number: str, base: Union[int, str], table: str = DEFAULT_TABLE) -> Union[int, float]: | |
base = int(base) | |
position = number.find('.') | |
if number[0] == '-': | |
negative = True | |
number = number[1:] | |
else: | |
negative = False | |
if position == -1: | |
result, index = 0, 1 | |
for digit in number[::-1]: | |
result += index * table.find(digit) | |
index *= base | |
else: | |
integer, fraction = number.split('.') | |
result, index = to_decimal(integer, base, table), 1.0 | |
for digit in fraction: | |
index /= base | |
result += index * table.find(digit) | |
return -result if negative else result | |
def to_base(number: Union[int, float], base: Union[int, str], table: str = DEFAULT_TABLE) -> str: | |
try: | |
base = int(base) | |
except ValueError: | |
pass | |
if number == 0: | |
return table[0] if isinstance(number, int) else '{}.{}'.format(table[0], table[0]) | |
result_numbers = [] | |
if number < 0 < base: | |
negative = True | |
number = abs(number) | |
else: | |
negative = False | |
if isinstance(number, int): | |
while number != 0: | |
number, remainder = mod(number, base) | |
result_numbers.insert(0, table[remainder]) | |
else: | |
integer = ceil(number) if base < 0 else floor(number) | |
fraction = number - integer | |
result_numbers.append(to_base(integer, base, table) + '.') | |
times = 0 | |
while fraction != 0 and times < 100: | |
fraction *= base | |
integer = ceil(fraction) if base < 0 else floor(fraction) | |
fraction -= integer | |
result_numbers.append(table[integer]) | |
times += 1 | |
if times == 0: | |
result_numbers.append(table[0]) | |
if negative: | |
result_numbers.insert(0, '-') | |
return ''.join(result_numbers) | |
def convert(number: str, original_base: Union[int, str] = 0, result_base: Union[int, str] = 10, | |
table: str = DEFAULT_TABLE) -> str: | |
original_base, result_base = int(original_base), int(result_base) | |
if original_base == result_base: | |
return number | |
return to_base(to_decimal(number, original_base, table), result_base, table) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
from PyQt5.QtWidgets import QMainWindow, QApplication | |
from PyQt5.QtCore import pyqtSignal | |
from MainWindow import Ui_MainWindow | |
import sys | |
from converter import convert, DEFAULT_TABLE | |
class MainWindow(QMainWindow, Ui_MainWindow): | |
number_changed = pyqtSignal(str, name='numberChanged') | |
result_changed = pyqtSignal(str, name='resultChanged') | |
def __init__(self, number: str = None, original_base: int = 10, result_base: int = 10, table: str = None): | |
super().__init__(None) | |
self.setupUi(self) | |
self.computing = False | |
self.number_changed['QString'].connect(self.resultNumberLineEdit.setText) | |
self.result_changed['QString'].connect(self.originalNumberLineEdit.setText) | |
self.originalNumberLineEdit.textEdited.connect(lambda: self.compute(True)) | |
self.resultNumberLineEdit.textEdited.connect(lambda: self.compute(False)) | |
self.originalBaseLineEdit.setText(str(original_base)) | |
self.resultBaseLineEdit.setText(str(result_base)) | |
if table and table != DEFAULT_TABLE: | |
self.usingTableCheckBox.setChecked(True) | |
self.tableLabel.setEnabled(True) | |
self.tableLineEdit.setEnabled(True) | |
self.tableLineEdit.setText(table) | |
if number: | |
self.originalNumberLineEdit.setText(number) | |
self.compute(True) | |
def compute(self, from_number: bool): | |
if from_number: | |
number = self.originalNumberLineEdit.text() | |
original_base = self.originalBaseLineEdit.text() | |
result_base = self.resultBaseLineEdit.text() | |
else: | |
number = self.resultNumberLineEdit.text() | |
original_base = self.resultBaseLineEdit.text() | |
result_base = self.originalBaseLineEdit.text() | |
table = self.tableLineEdit.text() if self.usingTableCheckBox.isChecked() else DEFAULT_TABLE | |
try: | |
result = convert(number, original_base, result_base, table) | |
except (ValueError, IndexError): | |
result = '' | |
if from_number: | |
self.number_changed[str].emit(result) | |
else: | |
self.result_changed[str].emit(result) | |
def start(number: str = None, original_base: int = 10, result_base: int = 10, table: str = None): | |
application = QApplication([]) | |
main_window = MainWindow(number, original_base, result_base, table) | |
main_window.show() | |
sys.exit(application.exec()) | |
if __name__ == '__main__': | |
start() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3.8 | |
# -*- coding: utf-8 -*- | |
from typing import Optional | |
import click | |
from converter import DEFAULT_TABLE, convert | |
from gui import start as gui_start | |
@click.command() | |
@click.option('-g', '--gui', is_flag=True, help='Open Graphical User Interface.') | |
@click.option('-n', '--number', type=click.STRING, help='Set the number.') | |
@click.option('-o', '--original-base', type=click.INT, help='Set original base.') | |
@click.option('-r', '--result-base', type=click.INT, help='Set result base.') | |
@click.option('-t', '--table', type=click.STRING, help='Set the table.') | |
@click.argument('others', nargs=-1) | |
def process(gui: bool, | |
number: Optional[str], original_base: Optional[int], result_base: Optional[int], table: Optional[str], | |
others: tuple): | |
for arg in others: | |
if number is None: | |
number = arg | |
elif original_base is None: | |
original_base = arg | |
elif result_base is None: | |
result_base = arg | |
elif table is None: | |
table = arg | |
original_base = original_base or 10 | |
result_base = result_base or 10 | |
table = table or DEFAULT_TABLE | |
if not gui: | |
click.echo(convert(number, original_base, result_base, table)) | |
else: | |
gui_start(number, original_base, result_base, table) | |
if __name__ == '__main__': | |
process() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# Form implementation generated from reading ui file '.\MainWindow.ui' | |
# | |
# Created by: PyQt5 UI code generator 5.14.1 | |
# | |
# WARNING! All changes made in this file will be lost! | |
from PyQt5 import QtCore, QtGui, QtWidgets | |
class Ui_MainWindow(object): | |
def setupUi(self, MainWindow): | |
MainWindow.setObjectName("MainWindow") | |
MainWindow.resize(294, 222) | |
self.centralwidget = QtWidgets.QWidget(MainWindow) | |
self.centralwidget.setObjectName("centralwidget") | |
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) | |
self.gridLayout.setObjectName("gridLayout") | |
self.formLayout = QtWidgets.QFormLayout() | |
self.formLayout.setObjectName("formLayout") | |
self.originalNumberLabel = QtWidgets.QLabel(self.centralwidget) | |
self.originalNumberLabel.setObjectName("originalNumberLabel") | |
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.originalNumberLabel) | |
self.originalNumberLineEdit = QtWidgets.QLineEdit(self.centralwidget) | |
self.originalNumberLineEdit.setObjectName("originalNumberLineEdit") | |
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.originalNumberLineEdit) | |
self.originalBaseLabel = QtWidgets.QLabel(self.centralwidget) | |
self.originalBaseLabel.setObjectName("originalBaseLabel") | |
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.originalBaseLabel) | |
self.originalBaseLineEdit = QtWidgets.QLineEdit(self.centralwidget) | |
self.originalBaseLineEdit.setObjectName("originalBaseLineEdit") | |
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.originalBaseLineEdit) | |
self.resultBaseLabel = QtWidgets.QLabel(self.centralwidget) | |
self.resultBaseLabel.setObjectName("resultBaseLabel") | |
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.resultBaseLabel) | |
self.resultBaseLineEdit = QtWidgets.QLineEdit(self.centralwidget) | |
self.resultBaseLineEdit.setObjectName("resultBaseLineEdit") | |
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.resultBaseLineEdit) | |
self.resultNumberLabel = QtWidgets.QLabel(self.centralwidget) | |
self.resultNumberLabel.setObjectName("resultNumberLabel") | |
self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.resultNumberLabel) | |
self.resultNumberLineEdit = QtWidgets.QLineEdit(self.centralwidget) | |
self.resultNumberLineEdit.setObjectName("resultNumberLineEdit") | |
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.resultNumberLineEdit) | |
self.usingTableLabel = QtWidgets.QLabel(self.centralwidget) | |
self.usingTableLabel.setObjectName("usingTableLabel") | |
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.usingTableLabel) | |
self.usingTableCheckBox = QtWidgets.QCheckBox(self.centralwidget) | |
self.usingTableCheckBox.setObjectName("usingTableCheckBox") | |
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.usingTableCheckBox) | |
self.tableLabel = QtWidgets.QLabel(self.centralwidget) | |
self.tableLabel.setEnabled(False) | |
self.tableLabel.setObjectName("tableLabel") | |
self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.tableLabel) | |
self.tableLineEdit = QtWidgets.QLineEdit(self.centralwidget) | |
self.tableLineEdit.setEnabled(False) | |
self.tableLineEdit.setObjectName("tableLineEdit") | |
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.tableLineEdit) | |
self.gridLayout.addLayout(self.formLayout, 0, 0, 1, 1) | |
MainWindow.setCentralWidget(self.centralwidget) | |
self.menubar = QtWidgets.QMenuBar(MainWindow) | |
self.menubar.setGeometry(QtCore.QRect(0, 0, 294, 26)) | |
self.menubar.setObjectName("menubar") | |
MainWindow.setMenuBar(self.menubar) | |
self.statusbar = QtWidgets.QStatusBar(MainWindow) | |
self.statusbar.setObjectName("statusbar") | |
MainWindow.setStatusBar(self.statusbar) | |
self.retranslateUi(MainWindow) | |
self.usingTableCheckBox.clicked['bool'].connect(self.tableLabel.setEnabled) | |
self.originalBaseLineEdit.textChanged['QString'].connect(self.originalNumberLineEdit.clear) | |
self.originalBaseLineEdit.textChanged['QString'].connect(self.resultNumberLineEdit.clear) | |
self.resultBaseLineEdit.textChanged['QString'].connect(self.originalNumberLineEdit.clear) | |
self.resultBaseLineEdit.textChanged['QString'].connect(self.resultNumberLineEdit.clear) | |
self.usingTableCheckBox.clicked['bool'].connect(self.tableLineEdit.setEnabled) | |
self.tableLineEdit.textChanged['QString'].connect(self.originalNumberLineEdit.clear) | |
self.tableLineEdit.textChanged['QString'].connect(self.resultNumberLineEdit.clear) | |
self.usingTableCheckBox.toggled['bool'].connect(self.originalNumberLineEdit.clear) | |
self.usingTableCheckBox.toggled['bool'].connect(self.resultNumberLineEdit.clear) | |
QtCore.QMetaObject.connectSlotsByName(MainWindow) | |
def retranslateUi(self, MainWindow): | |
_translate = QtCore.QCoreApplication.translate | |
MainWindow.setWindowTitle(_translate("MainWindow", "Base Converter")) | |
self.originalNumberLabel.setText(_translate("MainWindow", "Original Number")) | |
self.originalBaseLabel.setText(_translate("MainWindow", "Original Base")) | |
self.resultBaseLabel.setText(_translate("MainWindow", "Result Base")) | |
self.resultNumberLabel.setText(_translate("MainWindow", "Result Number")) | |
self.usingTableLabel.setText(_translate("MainWindow", "Using Table")) | |
self.tableLabel.setText(_translate("MainWindow", "Table")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<ui version="4.0"> | |
<class>MainWindow</class> | |
<widget class="QMainWindow" name="MainWindow"> | |
<property name="geometry"> | |
<rect> | |
<x>0</x> | |
<y>0</y> | |
<width>294</width> | |
<height>222</height> | |
</rect> | |
</property> | |
<property name="windowTitle"> | |
<string>Base Converter</string> | |
</property> | |
<widget class="QWidget" name="centralwidget"> | |
<layout class="QGridLayout" name="gridLayout"> | |
<item row="0" column="0"> | |
<layout class="QFormLayout" name="formLayout"> | |
<item row="0" column="0"> | |
<widget class="QLabel" name="originalNumberLabel"> | |
<property name="text"> | |
<string>Original Number</string> | |
</property> | |
</widget> | |
</item> | |
<item row="0" column="1"> | |
<widget class="QLineEdit" name="originalNumberLineEdit"/> | |
</item> | |
<item row="1" column="0"> | |
<widget class="QLabel" name="originalBaseLabel"> | |
<property name="text"> | |
<string>Original Base</string> | |
</property> | |
</widget> | |
</item> | |
<item row="1" column="1"> | |
<widget class="QLineEdit" name="originalBaseLineEdit"/> | |
</item> | |
<item row="2" column="0"> | |
<widget class="QLabel" name="resultBaseLabel"> | |
<property name="text"> | |
<string>Result Base</string> | |
</property> | |
</widget> | |
</item> | |
<item row="2" column="1"> | |
<widget class="QLineEdit" name="resultBaseLineEdit"/> | |
</item> | |
<item row="3" column="0"> | |
<widget class="QLabel" name="resultNumberLabel"> | |
<property name="text"> | |
<string>Result Number</string> | |
</property> | |
</widget> | |
</item> | |
<item row="3" column="1"> | |
<widget class="QLineEdit" name="resultNumberLineEdit"/> | |
</item> | |
<item row="4" column="0"> | |
<widget class="QLabel" name="usingTableLabel"> | |
<property name="text"> | |
<string>Using Table</string> | |
</property> | |
</widget> | |
</item> | |
<item row="4" column="1"> | |
<widget class="QCheckBox" name="usingTableCheckBox"/> | |
</item> | |
<item row="5" column="0"> | |
<widget class="QLabel" name="tableLabel"> | |
<property name="enabled"> | |
<bool>false</bool> | |
</property> | |
<property name="text"> | |
<string>Table</string> | |
</property> | |
</widget> | |
</item> | |
<item row="5" column="1"> | |
<widget class="QLineEdit" name="tableLineEdit"> | |
<property name="enabled"> | |
<bool>false</bool> | |
</property> | |
</widget> | |
</item> | |
</layout> | |
</item> | |
</layout> | |
</widget> | |
<widget class="QMenuBar" name="menubar"> | |
<property name="geometry"> | |
<rect> | |
<x>0</x> | |
<y>0</y> | |
<width>294</width> | |
<height>26</height> | |
</rect> | |
</property> | |
</widget> | |
<widget class="QStatusBar" name="statusbar"/> | |
</widget> | |
<resources/> | |
<connections> | |
<connection> | |
<sender>usingTableCheckBox</sender> | |
<signal>clicked(bool)</signal> | |
<receiver>tableLabel</receiver> | |
<slot>setEnabled(bool)</slot> | |
<hints> | |
<hint type="sourcelabel"> | |
<x>143</x> | |
<y>155</y> | |
</hint> | |
<hint type="destinationlabel"> | |
<x>40</x> | |
<y>177</y> | |
</hint> | |
</hints> | |
</connection> | |
<connection> | |
<sender>originalBaseLineEdit</sender> | |
<signal>textChanged(QString)</signal> | |
<receiver>originalNumberLineEdit</receiver> | |
<slot>clear()</slot> | |
<hints> | |
<hint type="sourcelabel"> | |
<x>143</x> | |
<y>75</y> | |
</hint> | |
<hint type="destinationlabel"> | |
<x>144</x> | |
<y>44</y> | |
</hint> | |
</hints> | |
</connection> | |
<connection> | |
<sender>originalBaseLineEdit</sender> | |
<signal>textChanged(QString)</signal> | |
<receiver>resultNumberLineEdit</receiver> | |
<slot>clear()</slot> | |
<hints> | |
<hint type="sourcelabel"> | |
<x>276</x> | |
<y>76</y> | |
</hint> | |
<hint type="destinationlabel"> | |
<x>275</x> | |
<y>129</y> | |
</hint> | |
</hints> | |
</connection> | |
<connection> | |
<sender>resultBaseLineEdit</sender> | |
<signal>textChanged(QString)</signal> | |
<receiver>originalNumberLineEdit</receiver> | |
<slot>clear()</slot> | |
<hints> | |
<hint type="sourcelabel"> | |
<x>168</x> | |
<y>97</y> | |
</hint> | |
<hint type="destinationlabel"> | |
<x>166</x> | |
<y>42</y> | |
</hint> | |
</hints> | |
</connection> | |
<connection> | |
<sender>resultBaseLineEdit</sender> | |
<signal>textChanged(QString)</signal> | |
<receiver>resultNumberLineEdit</receiver> | |
<slot>clear()</slot> | |
<hints> | |
<hint type="sourcelabel"> | |
<x>249</x> | |
<y>101</y> | |
</hint> | |
<hint type="destinationlabel"> | |
<x>250</x> | |
<y>128</y> | |
</hint> | |
</hints> | |
</connection> | |
<connection> | |
<sender>usingTableCheckBox</sender> | |
<signal>clicked(bool)</signal> | |
<receiver>tableLineEdit</receiver> | |
<slot>setEnabled(bool)</slot> | |
<hints> | |
<hint type="sourcelabel"> | |
<x>273</x> | |
<y>151</y> | |
</hint> | |
<hint type="destinationlabel"> | |
<x>274</x> | |
<y>171</y> | |
</hint> | |
</hints> | |
</connection> | |
<connection> | |
<sender>tableLineEdit</sender> | |
<signal>textChanged(QString)</signal> | |
<receiver>originalNumberLineEdit</receiver> | |
<slot>clear()</slot> | |
<hints> | |
<hint type="sourcelabel"> | |
<x>184</x> | |
<y>170</y> | |
</hint> | |
<hint type="destinationlabel"> | |
<x>181</x> | |
<y>46</y> | |
</hint> | |
</hints> | |
</connection> | |
<connection> | |
<sender>tableLineEdit</sender> | |
<signal>textChanged(QString)</signal> | |
<receiver>resultNumberLineEdit</receiver> | |
<slot>clear()</slot> | |
<hints> | |
<hint type="sourcelabel"> | |
<x>230</x> | |
<y>168</y> | |
</hint> | |
<hint type="destinationlabel"> | |
<x>231</x> | |
<y>121</y> | |
</hint> | |
</hints> | |
</connection> | |
<connection> | |
<sender>usingTableCheckBox</sender> | |
<signal>toggled(bool)</signal> | |
<receiver>originalNumberLineEdit</receiver> | |
<slot>clear()</slot> | |
<hints> | |
<hint type="sourcelabel"> | |
<x>194</x> | |
<y>148</y> | |
</hint> | |
<hint type="destinationlabel"> | |
<x>193</x> | |
<y>51</y> | |
</hint> | |
</hints> | |
</connection> | |
<connection> | |
<sender>usingTableCheckBox</sender> | |
<signal>toggled(bool)</signal> | |
<receiver>resultNumberLineEdit</receiver> | |
<slot>clear()</slot> | |
<hints> | |
<hint type="sourcelabel"> | |
<x>214</x> | |
<y>146</y> | |
</hint> | |
<hint type="destinationlabel"> | |
<x>214</x> | |
<y>123</y> | |
</hint> | |
</hints> | |
</connection> | |
</connections> | |
</ui> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment