コード例 #1
0
    def add(self, data_tuple):
        """
        Adds a data-row to table
        :param data_tuple: Tuple[Optional[int, float, Const, Data]] = data for each column of table
        :return: void
        """

        type_tuple = type(data_tuple)

        if type_tuple == list:
            data_tuple = tuple(data_tuple)
        elif type_tuple != tuple:
            raise ValueError(f"Expected a tuple, get {type_tuple} instead")

        if len(data_tuple) == self.columns:
            for index, data in enumerate(data_tuple):
                if isinstance(data, (Data, Const)):
                    if data.unit != self.units[index] and data.unit != Unit(
                            ""):
                        raise Exception(
                            f"Can´t fill column of {self.units[index]} with {data.unit}"
                        )
                    elif data.unit == Unit(""):
                        data_tuple[index].unit = self.units[index]
                    else:
                        data_tuple[index].unit = Unit()
            self.datas.append(data_tuple)
        else:
            raise ValueError(
                f"Expected an tuple of {self.columns}, get tuple of {len(data_tuple)} instead"
            )
コード例 #2
0
    def __init__(self, column_names=[], columns=2, signs=[]):
        """
        Initialize a Table.
        :param column_names: List[str] = names of the columns
        :param columns: int = number of columns
        :param signs: List[Union[Unit, str]] = sign or unit of each column
        """

        self.datas = []
        self.columns = columns
        self.column_names = []
        self.units = []

        # create units
        if not signs:
            self.units = [Unit("") for i in range(self.columns)]
        else:
            for sign in signs:
                if isinstance(sign, str):
                    sign = sign.split("/")
                    if len(sign) > 1:
                        sign = ["" if s == "1" else s for s in sign]
                        unit = Unit(sign[0], sign[1])
                    else:
                        unit = Unit(sign[0])
                else:
                    unit = sign
                self.units.append(unit)

        # create column names
        if not column_names:
            self.column_names = [f"Column {i}" for i in range(self.columns)]
        else:
            self.column_names = column_names
コード例 #3
0
    def calc_unit(self, value_dict):
        """
        Determine the unit of the result will have.
        :param value_dict: Dict[str: Union[int, float, Data, Const]] = Dict of the variables for the formula
        :return: Unit = Unit of the result of the formula
        """

        type_dict = {key: type(value_dict[key]) for key in value_dict}
        dummy = {
            "unit": self.__create_formula(type_dict),
            "cur_unit": 1,
            "Symbol": Symbol,
            "sympy": sympy
        }
        exec("from sympy.functions import *", dummy)

        for key in value_dict:
            if not (type_dict[key] in [int, float]):
                numerator = value_dict[key].unit.numerator
                denominator = value_dict[key].unit.denominator

                for unit in numerator.keys():
                    exec(f"{unit} = Symbol('{unit}')", dummy)
                    exec(f"cur_unit *= {unit} ** {numerator[unit]}", dummy)

                for unit in denominator.keys():
                    exec(f"{unit} = Symbol('{unit}')", dummy)
                    exec(f"cur_unit /= ({unit} ** {denominator[unit]})", dummy)

            exec(f"{key} = Symbol('{key}')", dummy)
            exec(f"unit = unit.subs({key},cur_unit)", dummy)
            dummy["cur_unit"] = 1

        units = self.__get_units(sympy.nsimplify(dummy["unit"]))

        if not units:
            return Unit("")

        numerator = {}
        denominator = {}
        for unit, power in units:
            if power > 0:
                numerator[unit] = power
            else:
                denominator[unit] = -1 * power

        unit = Unit()
        unit.denominator = denominator
        unit.numerator = numerator
        return unit
コード例 #4
0
    def modus(self):
        """
        calculate modus of each column.
        modus: most common value in column
        :return: List[Optional[float, Const]]
        """

        modes = []
        for i in range(self.columns):
            column_data = [
                data[i].value if isinstance(data[i],
                                            (Data, Const)) else data[i]
                for data in self.datas
            ]
            mode = column_data[0]
            counter = column_data.count(mode)
            for i2 in range(counter):
                column_data.remove(mode)

            while len(column_data) > 0:
                data = column_data[0]
                new_counter = column_data.count(data)
                if new_counter > counter:
                    mode = data
                    counter = new_counter
                for i2 in range(new_counter):
                    column_data.remove(data)

            if self.units[i] != Unit():
                mode = Const(value=mode, sign=self.units[i])
            modes.append(mode)
        return modes
コード例 #5
0
ファイル: data.py プロジェクト: YanickT/Pyrror
    def __add__(self, other):
        """
        Addition with other Const.
        :param other: Union[Const, Data, int, float] = other object to add with
        :return: Union[Const, Data, int, float] = result of the subtraction
        """

        if isinstance(other, Const):
            if self.unit == other.unit:
                return Const(self.value + other.value, sign=self.unit)
            else:
                raise ArithmeticError(
                    "Addition of Data with different units is not possible")

        elif isinstance(other, Data):
            if self.unit == other.unit:
                return Data(str(self.value + other.value),
                            str(other.error),
                            n=other.n,
                            sign=self.unit)
            else:
                raise ArithmeticError(
                    "Addition of Data and Const with different units is not possible"
                )

        elif isinstance(other, (int, float)):
            if self.unit == Unit():
                return self.value + other
            else:
                raise ArithmeticError(
                    "Addition of values with different units is not possible")

        else:
            raise TypeError(
                f"unsupported operand '+' for Const and {type(other)}")
コード例 #6
0
ファイル: unit_test.py プロジェクト: YanickT/Pyrror
 def test_truediv(self):
     self.assertEqual(str(a / b), "m/s")
     self.assertEqual(str(a / a), "")
     self.assertEqual(str(c / a), "m")
     self.assertEqual(str(f / f), "")
     self.assertEqual(str(c / a), "m")
     self.assertEqual(str(d / a), "s")
     self.assertEqual(e / f, Unit("kg;s^2", "N"))
コード例 #7
0
ファイル: data.py プロジェクト: YanickT/Pyrror
    def __number_sub(self, other):
        """
        Helper function for subtraction of a Data and an Union[int, float].
        :param other: Union[int, float] = value to subtract
        :return: Data = result of the subtraction
        """

        if self.unit == Unit(""):
            data = Data(str(self.value - other), str(self.error), n=self.n)
            data.power = self.power
            return data
コード例 #8
0
ファイル: data.py プロジェクト: YanickT/Pyrror
    def __init__(self,
                 value: str,
                 error: str,
                 sign: Union[str, Unit] = "",
                 power: int = 0,
                 n: int = 0):
        """
        Initiate new value with uncertainty
        :param value: str = value
        :param error: str = uncertainty
        :param sign: Union[str, Unit] = unit of the value
            **sign-EBNF:**
            S := '"' units '"' | '"' units '/' units '"'
            units := unit | unit ';' units
            unit := string | string '^' integer
        :param power: int = power of the value (for dimensions like mV => power = -3 and unit = 'V')
        :param n: int = significant digits of the error (if 0  (Default): get digits from error: str)
        """

        type_check((value, str), (error, str))

        if n == 0:
            self.n = digits(error)
        else:
            self.n = n

        if isinstance(sign, str):
            sign = sign.split("/")
            if len(sign) > 1:
                sign = ["" if s == "1" else s for s in sign]
                self.unit = Unit(sign[0], sign[1])
            else:
                self.unit = Unit(sign[0])
        else:
            self.unit = sign
        self.power = power

        self.error = float(error) * 10**self.power
        self.value = float(value) * 10**self.power

        round_data(self)
コード例 #9
0
ファイル: data.py プロジェクト: YanickT/Pyrror
    def __init__(self, value, sign):
        """
        Initalize a constant with a unit.
        :param value: Union[int, float] = constant value
        :param sign: Union[str, Unit] = String carrying the unit.
        **sign-EBNF:**
            S := '"' units '"' | '"' units '/' units '"'
            units := unit | unit ';' units
            unit := string | string '^' integer
        """

        if isinstance(sign, str):
            sign = sign.split("/")
            if len(sign) > 1:
                sign = ["" if s == "1" else s for s in sign]
                self.unit = Unit(sign[0], sign[1])
            else:
                self.unit = Unit(sign[0])
        else:
            self.unit = sign

        self.value = float(value)
コード例 #10
0
    def insert(self, line, column, value):
        """
        insert a value at line, column in table
        :param line: int; line in the table
        :param column: int; column in the table
        :param value: Optional[float, int, Data, Const]; new value for slot
        :return: void
        """

        if isinstance(value, Data):
            value = Data(value=str(value.value),
                         error=str(value.error),
                         sign=Unit(""),
                         n=value.n,
                         power=value.power)
        elif isinstance(value, Const):
            value = Const(value=value.value, sign=Unit(""))
        data_l = self.datas[line][:]
        data_l = list(data_l)
        data_l[column] = value
        data_l = tuple(data_l)
        self.datas[line] = data_l
        return True
コード例 #11
0
    def add_column(self, name="", sign=""):
        """
        Adds a column to table.
        :param name: str = name of the new table
        :param unit: Union[str, Unit] = unit of the new column
        :return: void
        """

        if name == "":
            self.column_names.append(f"Column {self.columns}")
        else:
            self.column_names.append(name)
        self.columns += 1

        if isinstance(sign, str):
            sign = sign.split("/")
            if len(sign) > 1:
                sign = ["" if s == "1" else s for s in sign]
                unit = Unit(sign[0], sign[1])
            else:
                unit = Unit(sign[0])
            self.units.append(unit)

        elif isinstance(sign, Unit):
            self.units.append(sign)
        else:
            raise TypeError(
                f"Expected unit or string got {type(sign)} instead")

        for index in range(len(self.datas)):
            element = self.datas[index][:]
            element = list(element)
            element += [""]
            element = tuple(element)
            self.datas[index] = element
        return True
コード例 #12
0
    def drop(self, line_index):
        """
        Return and delete data-row at line_index
        :param line_index: int = index for row to delete
        :return: Tuple[Optional[int, float, Const, Data]] = row to drop
        """

        results = self.datas.pop(line_index)
        new_results = []
        for index, result in enumerate(results):
            if self.units[index] != Unit():
                if isinstance(result, (Data, Const)):
                    result.unit = self.units[index]
                    new_results.append(result)
                else:
                    new_result = Const(value=result, sign=self.units[index])
                    new_results.append(new_result)
            else:
                new_results.append(result)
        return new_results
コード例 #13
0
    def export(self, path, name, replace_dot=False):
        """
        Exports data in .csv format
        :param path: path to .csv file (.csv in name needed)
        :param replace_dot: difference between english and german floats "3.11", "3,11"
        :return: void
        """

        # check if any value in a column has an error (is a Data)
        data_cols = [
            any([isinstance(row[i], Data) for row in self.datas])
            for i in range(self.columns)
        ]

        with open(path + "/" + name, "w", encoding="UTF-8") as doc:
            # write table headers
            doc.write("".join([
                f"{h_str};;" if data_col else f"{h_str};"
                for data_col, h_str in zip(data_cols, [
                    f"{column_name} [{unit}]"
                    if unit != Unit("") else column_name
                    for column_name, unit in zip(self.column_names, self.units)
                ])
            ]) + "\n")

            doc.write("".join([
                "Bestwert; Fehler;" if data_col else "Bestwert;"
                for data_col in data_cols
            ]) + "\n")

            # write data
            for row in self.datas:
                data_string = "".join([
                    f"{data.value};{data.error};" if data_col else
                    f"{data.value};" if type(data) == Const else f"{data};"
                    for data, data_col in zip(row, data_cols)
                ]) + "\n"
                if replace_dot:
                    data_string = data_string.replace(".", ",")

                doc.write(data_string)
コード例 #14
0
ファイル: unit_test.py プロジェクト: YanickT/Pyrror
import unittest
from pyrror.unit import Unit

a = Unit("m")
b = Unit("s")
c = Unit("m^2")
d = Unit("m;s")
e = Unit("s;m")
f = Unit("N;m", "kg;s")
g = Unit()


class MyTestCase(unittest.TestCase):
    def test_truediv(self):
        self.assertEqual(str(a / b), "m/s")
        self.assertEqual(str(a / a), "")
        self.assertEqual(str(c / a), "m")
        self.assertEqual(str(f / f), "")
        self.assertEqual(str(c / a), "m")
        self.assertEqual(str(d / a), "s")
        self.assertEqual(e / f, Unit("kg;s^2", "N"))

    def test_mul(self):
        self.assertEqual((d * a == e * a), True)
        self.assertEqual((d * a == e), False)

    def test_eq(self):
        self.assertEqual((d == e), True)
        self.assertEqual((a == a), True)
        self.assertEqual((a == b), False)
        self.assertEqual((c == a * a), True)
コード例 #15
0
ファイル: data.py プロジェクト: YanickT/Pyrror
class Const:
    """
    Class for constants and values with units if they carry no uncertainty (or a neglected one).
    """
    def __init__(self, value, sign):
        """
        Initalize a constant with a unit.
        :param value: Union[int, float] = constant value
        :param sign: Union[str, Unit] = String carrying the unit.
        **sign-EBNF:**
            S := '"' units '"' | '"' units '/' units '"'
            units := unit | unit ';' units
            unit := string | string '^' integer
        """

        if isinstance(sign, str):
            sign = sign.split("/")
            if len(sign) > 1:
                sign = ["" if s == "1" else s for s in sign]
                self.unit = Unit(sign[0], sign[1])
            else:
                self.unit = Unit(sign[0])
        else:
            self.unit = sign

        self.value = float(value)

    @instancemethod
    def __str__(self):
        """
        Creates a string representation of the Const.
        :return: str = representation of the Const
        """

        string = str(self.value)
        unit_string = str(self.unit)
        if unit_string != "":
            string += " " + unit_string
        return string

    @instancemethod
    def __repr__(self):
        return self.__str__()

    @instancemethod
    def __mul__(self, other):
        """
        Multiplication with other.
        :param other: Union[Data, Const, int, float] = other object to multiply with
        :return: Union[Data, Const] = Result type depends on the other object
        """

        if isinstance(other, (int, float)):
            value = self.value * other
            unit = self.unit
            return Const(value, sign=unit)

        elif isinstance(other, Const):
            value = self.value * other.value
            unit = self.unit * other.unit
            return Const(value, sign=unit)

        elif isinstance(other, Data):
            value = self.value * other.value
            unit = self.unit * other.unit
            n = other.n
            error = self.value * other.error
            return Data(str(value), str(error), n=n, sign=unit)

        else:
            raise TypeError(
                f"unsupported operand '*' for Const and {type(other)}")

    @instancemethod
    def __rmul__(self, other):
        """
        Multiplication with other.
        :param other: Union[Data, Const, int, float] = other object to multiply with
        :return: Union[Data, Const] = result type depends on the other object
        """

        return self.__mul__(other)

    @instancemethod
    def __add__(self, other):
        """
        Addition with other Const.
        :param other: Union[Const, Data, int, float] = other object to add with
        :return: Union[Const, Data, int, float] = result of the subtraction
        """

        if isinstance(other, Const):
            if self.unit == other.unit:
                return Const(self.value + other.value, sign=self.unit)
            else:
                raise ArithmeticError(
                    "Addition of Data with different units is not possible")

        elif isinstance(other, Data):
            if self.unit == other.unit:
                return Data(str(self.value + other.value),
                            str(other.error),
                            n=other.n,
                            sign=self.unit)
            else:
                raise ArithmeticError(
                    "Addition of Data and Const with different units is not possible"
                )

        elif isinstance(other, (int, float)):
            if self.unit == Unit():
                return self.value + other
            else:
                raise ArithmeticError(
                    "Addition of values with different units is not possible")

        else:
            raise TypeError(
                f"unsupported operand '+' for Const and {type(other)}")

    @instancemethod
    def __radd__(self, other):
        return self.__add__(other)

    @instancemethod
    def __sub__(self, other):
        """
        Subtraction with other Const.
        :param other: Union[Const, Data, int, float] = other object to add with
        :return: Union[Const, Data] = result of the subtraction
        """

        return self.__add__(-1 * other)

    @instancemethod
    def __rsub__(self, other):
        neg_self = -1 * self
        return neg_self.__add__(other)

    @instancemethod
    def __truediv__(self, other):
        """
        Division with other object.
        :param other: Union[Data, Const, int, float] = other object to add with
        :return: Union[Data, Const] = Result type depends on the other object
        """

        if isinstance(other, (int, float)):
            value = self.value / other
            unit = self.unit
            return Const(value, sign=unit)

        elif isinstance(other, Const):
            value = self.value / other.value
            unit = self.unit / other.unit
            return Const(value, sign=unit)

        elif isinstance(other, Data):
            value = self.value / other.value
            unit = self.unit / other.unit
            n = other.n
            error = self.value / other.error
            return Data(str(value), str(error), n=n, sign=unit)

        else:
            raise TypeError(
                f"unsupported operand '/' for Const and {type(other)}")

    @instancemethod
    def __rtruediv__(self, other):
        """
        Division with other object.
        :param other: Union[int, float] = other object to add with
        :return: Union[Data, Const] = Result type depends on the other object
        """

        if isinstance(other, (int, float)):
            result = other / self.value
            unit = self.unit.flip()
            return Const(result, unit)
        else:
            raise ValueError(
                f"Unsupported operation '/' for Const and {type(other)}")

    @instancemethod
    def __pow__(self, other):
        """
        Power a Const object
        :param other: Union[int, float] = object to power with
        :return: Const = result of the calculation
        """

        typ_other = type(other)
        if typ_other == int or typ_other == float:
            result = self.value**other
            unit = self.unit**other
            return Const(result, unit)
        elif typ_other == Data:
            raise ArithmeticError("Try to use a Formula instead!")
        else:
            raise TypeError(
                f"Unsupported operation '/' for Const and {typ_other}")

    @unit_control
    def __lt__(self, other):
        """
        Compare Const with other objects.
        :param other: Const = object to compare with
        :return: bool = result of comparison
        """

        if isinstance(other, Const):
            return self.value < other.value
        raise TypeError(
            f"unsupported operation '<' for Data and {type(other)}")

    @unit_control
    def __le__(self, other):
        """
        Compare Const with other objects.
        :param other: Const = object to compare with
        :return: bool = result of comparison
        """

        if isinstance(other, Const):
            return self.value <= other.value
        raise TypeError(
            f"unsupported operation '<=' for Data and {type(other)}")

    @unit_control
    def __eq__(self, other):
        """
        Compare Const with other objects.
        :param other: Const = object to compare with
        :return: bool = result of comparison
        """

        if isinstance(other, Const):
            return self.value == other.value
        raise TypeError(
            f"unsupported operation '==' for Data and {type(other)}")

    @unit_control
    def __ne__(self, other):
        """
        Compare Const with other objects.
        :param other: Const = object to compare with
        :return: bool = result of comparison
        """

        if isinstance(other, Const):
            return self.value != other.value
        raise TypeError(
            f"unsupported operation '!=' for Data and {type(other)}")

    @unit_control
    def __ge__(self, other):
        """
        Compare Const with other objects.
        :param other: Const = object to compare with
        :return: bool = result of comparison
        """

        if isinstance(other, Const):
            return self.value >= other.value
        raise TypeError(
            f"unsupported operation '>=' for Data and {type(other)}")

    @unit_control
    def __gt__(self, other):
        """
        Compare Const with other objects.
        :param other: Const = object to compare with
        :return: bool = result of comparison
        """

        if isinstance(other, Const):
            return self.value > other.value
        raise TypeError(
            f"unsupported operation '>' for Data and {type(other)}")
コード例 #16
0
ファイル: unit_test.py プロジェクト: YanickT/Pyrror
 def test_str(self):
     self.assertEqual(str(Unit("m^2", "m")), "m")
     self.assertEqual(str(a), "m")
     self.assertEqual(str(c), "m^2")
コード例 #17
0
    def calc(self, formula, para_dict, column_name="", sign=True):
        """
        Method for calculating new columns with dependencies to others given by a formula.
        The new column is added to the current data.
        :param formula: Formula-Object = Connection between parameters, and table columns
        :param para_dict: Dict[str, Optional[int, str, Any]] = dictionary specifying which objects to use in formula
            key (str): name of variable or parameter in formula-object
            value (int): index of column in table for the parameter at key
            value (str): fixed parameter for equation. Should be str(<float>) (equal for all columns)
        :param column_name: str = Name of the arising column
        :param sign: Optional[Unit, str] = sign/unit of the arising column
        :return: void
        """

        type_check((formula, Formula))

        if not self.datas:
            warnings.warn(f"No data found in table.")
            return

        # add new column name
        if column_name == "":
            column_name = f"Column {self.columns}"
        self.column_names.append(column_name)

        # update number of columns
        self.columns += 1

        # add unit to column
        if sign is True:
            value_dict = {}
            for key in para_dict:
                if isinstance(para_dict[key], int):
                    if self.units[para_dict[key]] != Unit():
                        value_dict[key] = Const(1, self.units[para_dict[key]])
                    else:
                        value_dict[key] = 1
                elif isinstance(para_dict[key], str):
                    value_dict[key] = float(para_dict[key])
                else:
                    value_dict[key] = para_dict[key]

            unit = formula.calc_unit(value_dict)
            self.units.append(unit)
        elif sign is False:
            self.units.append(Unit(""))
        else:
            self.units.append(sign)

        # calculate data and insert them into the column
        datas = []
        for row in self.datas:
            value_dict = {
                key: row[para_dict[key]]
                if isinstance(para_dict[key], int) else float(para_dict[key])
                if isinstance(para_dict[key], str) else para_dict[key]
                for key in para_dict
            }
            value = formula.calc(value_dict, sign=False)
            if isinstance(value, (Data, Const)):
                value.unit = Unit()
            datas.append(list(row) + [value])
        self.datas = datas
コード例 #18
0
ファイル: data.py プロジェクト: YanickT/Pyrror
class Data:
    """
    Main-Class of the project.
    Represents a value with uncertainty
    """
    def __init__(self,
                 value: str,
                 error: str,
                 sign: Union[str, Unit] = "",
                 power: int = 0,
                 n: int = 0):
        """
        Initiate new value with uncertainty
        :param value: str = value
        :param error: str = uncertainty
        :param sign: Union[str, Unit] = unit of the value
            **sign-EBNF:**
            S := '"' units '"' | '"' units '/' units '"'
            units := unit | unit ';' units
            unit := string | string '^' integer
        :param power: int = power of the value (for dimensions like mV => power = -3 and unit = 'V')
        :param n: int = significant digits of the error (if 0  (Default): get digits from error: str)
        """

        type_check((value, str), (error, str))

        if n == 0:
            self.n = digits(error)
        else:
            self.n = n

        if isinstance(sign, str):
            sign = sign.split("/")
            if len(sign) > 1:
                sign = ["" if s == "1" else s for s in sign]
                self.unit = Unit(sign[0], sign[1])
            else:
                self.unit = Unit(sign[0])
        else:
            self.unit = sign
        self.power = power

        self.error = float(error) * 10**self.power
        self.value = float(value) * 10**self.power

        round_data(self)

    def __str__(self):
        """
        Return a string representation of the Data.
        :return: str = string representation of the Data
        """

        # insert values
        if self.n > 1:
            string = f"({self.value * 10 ** (-self.power):.{(self.n - 1)}f}±{self.error * (10 ** -self.power):.{(self.n - 1)}f})"
        elif self.n == 1:
            string = f"({self.value * 10 ** (-self.power):.0f}±{self.error * (10 ** -self.power):.0f})"
        else:
            raise ValueError("n could not be smaller than 1")

        # add power
        if self.power != 0:
            string += f"*10^{self.power}"

        # add unit
        unit = str(self.unit)
        if unit != "":
            string += f" {unit}"

        return string

    @instancemethod
    def __repr__(self):
        return self.__str__()

    def latex(self):
        # insert values
        if self.n > 1:
            string = f"({self.value * 10 ** (-self.power):.{(self.n - 1)}f}±{self.error * (10 ** -self.power):.{(self.n - 1)}f})"
        elif self.n == 1:
            string = f"({self.value * 10 ** (-self.power):.0f} \\pm {self.error * (10 ** -self.power):.0f})"
        else:
            raise ValueError("n could not be smaller than 1")

        # add power
        if self.power != 0:
            string += f"\\cdot 10^{{{self.power}}}"

        # add unit
        unit = str(self.unit)
        if unit != "":
            string += f" {unit}"
        return string

    # Calculations using simplified gauss
    def __number_mul(self, other):
        """
        Helper function of multiplication of Data with float.
        :param other: Union[int, float] = value to multiplicative Data with
        :return: Data = result of the multiplication
        """

        return Data(str(self.value * other),
                    str(self.error * other),
                    sign=self.unit,
                    n=self.n)

    def __const_mul(self, other):
        """
        Helper function of multiplication of Data with Const.
        :param other: Const = Const to multiplicative Data with
        :return: Data = result of the multiplication
        """

        return Data(str(self.value * other.value),
                    str(self.error * other.value),
                    sign=self.unit * other.unit,
                    n=self.n)

    def __data_mul(self, other):
        """
        Helper function of multiplication of two Data.
        :param other: Data = The other Data to multiplicative Data with
        :return: Data = result of the multiplication
        """

        result = self.value * other.value
        error = str(result * ((self.error / self.value)**2 +
                              (other.error / other.value)**2)**0.5)
        significant_digits = min(self.n, other.n)
        unit = self.unit * other.unit
        result = str(result)
        return Data(result, error, sign=unit, n=significant_digits)

    @instancemethod
    def __mul__(self, other):
        """
        Multiplication of Data object with other. Which will happen depend on the type of other.
        :param other: Union[Data, Const, int, float] = Object to multiply with
        :return: Data = result of the multiplication
        """

        type_other = type(other)
        functions = {
            int: self.__number_mul,
            float: self.__number_mul,
            Const: self.__const_mul,
            Data: self.__data_mul
        }

        if type_other not in functions:
            raise ValueError(
                f"Unsupported operation '*' for Data and {type(other)}")

        return functions[type_other](other)

    @instancemethod
    def __rmul__(self, other):
        """
        Multiplication of Data object with other. Which will happen depend on the type of other.
        :param other: Union[Data, Const, int, float] = Object to multiply with
        :return: Data = result of the multiplication
        """
        return self.__mul__(other)

    @unit_control
    def __data_add(self, other):
        """
        Helper function for addition of two Data.
        :param other: Data = other Data to add with Data
        :return: Data = result of the addition
        """

        result = self.value + other.value
        significant_digits = min(self.n, other.n)
        error = str((self.error**2 + other.error**2)**0.5)
        unit = self.unit
        result = str(result)
        return Data(result, error, n=significant_digits, sign=unit)

    def __number_add(self, other):
        """
        Helper function for addition of a Data and an Union[int, float].
        :param other: Union[int, float] = value to add
        :return: Data = result of the addition
        """

        if self.unit == Unit(""):
            data = Data(str(self.value + other), str(self.error), n=self.n)
            data.power = self.power
            return data

    @unit_control
    def __const_add(self, other):
        """
        Helper function for addition of a Data and an Const.
        :param other: Const = value to add
        :return: Data = result of the addition
        """

        result = self.value + other.value
        significant_digits = min(self.n, other.n)
        unit = self.unit
        result = str(result)
        return Data(result, self.error, n=self.n, sign=unit)

    @instancemethod
    def __add__(self, other):
        """
        Addition of a Data and other.
        :param other: Union[Data, Const, int, float] = Object to add with
        :return: Data = result of the addition
        """

        type_other = type(other)
        functions = {
            int: self.__number_add,
            float: self.__number_add,
            Const: self.__const_add,
            Data: self.__data_add
        }

        if type_other not in functions:
            raise ValueError(
                "Unsupported operation '+' for Data and {type(other)}")

        return functions[type_other](other)

    @instancemethod
    def __radd__(self, other):
        """
        Addition of a Data and other.
        :param other: Union[Data, Const, int, float] = Object to add with
        :return: Data = result of the addition
        """

        return self.__number_add(other)

    @unit_control
    def __data_sub(self, other):
        """
        Helper function for subtraction of two Data.
        :param other: Data = other Data to subtract with Data
        :return: Data = result of the subtraction
        """

        result = self.value - other.value
        significant_digits = min(self.n, other.n)
        error = str((self.error**2 + other.error**2)**0.5)
        unit = self.unit
        result = str(result)
        return Data(result, error, sign=unit, n=significant_digits)

    def __number_sub(self, other):
        """
        Helper function for subtraction of a Data and an Union[int, float].
        :param other: Union[int, float] = value to subtract
        :return: Data = result of the subtraction
        """

        if self.unit == Unit(""):
            data = Data(str(self.value - other), str(self.error), n=self.n)
            data.power = self.power
            return data

    @unit_control
    def __const_sub(self, other):
        """
        Helper function for subtraction of a Data and an Const.
        :param other: Const = value to subtract
        :return: Data = result of the subtraction
        """

        return Data(str(self.value - other.value),
                    str(self.error),
                    n=self.n,
                    sign=self.unit)

    @instancemethod
    def __sub__(self, other):
        """
        Subtraction of two Data objects.
        :param other: Data = other Data to subtract with Data
        :return: Data = result of the subtraction
        """

        type_other = type(other)
        functions = {
            int: self.__number_sub,
            float: self.__number_sub,
            Const: self.__const_sub,
            Data: self.__data_sub
        }

        if type_other not in functions:
            raise ValueError(
                "Unsupported operation '-' for Data and {type(other)}")

        return functions[type_other](other)

    @instancemethod
    def __rsub__(self, other):
        """
        Subtraction of a Data and other.
        :param other: Union[Data, Const, int, float] = Object to subtract with
        :return: Data = result of the substraction
        """

        return -1 * self.__number_sub(other)

    def __number_div(self, other):
        """
        Helper function of division of Data with float.
        :param other: Union[int, float] = value to divide Data with
        :return: Data = result of division
        """

        return Data(str(self.value / other),
                    str(self.error / other),
                    sign=self.unit,
                    n=self.n)

    def __const_div(self, other):
        """
        Helper function of division of Data with Const.
        :param other: Const = Const to divide Data with
        :return: Data = result of division
        """

        result = str(self.value / other.value)
        unit = self.unit / other.unit
        error = str(self.error / other.value)
        significant_digits = self.n
        return Data(result, error, sign=unit, n=significant_digits)

    def __data_div(self, other):
        """
        Helper function of division of Data with Data.
        :param other: Data = Data to divide Data with
        :return: Data = result of division
        """

        result = self.value / other.value
        significant_digits = min(self.n, other.n)
        error = str(result * ((self.error / self.value)**2 +
                              (other.error / other.value)**2)**0.5)
        result = str(result)
        unit = self.unit / other.unit
        return Data(result, error, sign=unit, n=significant_digits)

    @instancemethod
    def __truediv__(self, other):
        """
        Division of a Data object with other.
        :param other: Union[Data, Const, int, float] = object to divide with
        :return: Data = result of the division
        """

        type_other = type(other)
        functions = {
            int: self.__number_div,
            float: self.__number_div,
            Const: self.__const_div,
            Data: self.__data_div
        }
        if type_other not in functions:
            raise ValueError(
                f"Unsupported operation '/' for Data and {type_other}")

        return functions[type_other](other)

    @instancemethod
    def __rtruediv__(self, other):
        """
        Division of a Data object with other.
        :param other: Union[int, float] = object to divide with
        :return: Data = result of the division
        """
        typ_other = type(other)
        if typ_other == int or typ_other == float:
            result = other / self.value
            unit = self.unit.flip()
            return Data(str(result),
                        str(result * (self.error / self.value)),
                        sign=unit,
                        n=self.n)
        else:
            raise ValueError(
                f"Unsupported operation '/' for Data and {typ_other}")

    def __pow__(self, other):
        """
        Power of a Data object with other.
        :param other: Union[int, float] = object to power with
        :return: Data = result of the calculation
        """

        typ_other = type(other)
        if typ_other == int or typ_other == float:
            result = self.value**other
            unit = self.unit**other
            return Data(str(result),
                        str(result * (self.error / self.value)),
                        sign=unit,
                        n=self.n)
        elif typ_other == Data:
            raise ArithmeticError("Try to use a Formula instead!")
        else:
            raise TypeError(
                f"Unsupported operation '**' for Data and {typ_other}")

    # Data comparisons

    @unit_control
    def __data_lt(self, other):
        """
        Helper function of lt comparison of Data with Data.
        :param other: Data = Data to compare with
        :return: bool = result of comparison
        """

        return self.value + self.error + other.error < other.value

    @unit_control
    def __const_lt(self, other):
        """
        Helper function of lt comparison of Data with Const.
        :param other: Const = Const to compare with
        :return: bool = result of comparison
        """

        return self.value + self.error < other.value

    @instancemethod
    def __lt__(self, other):
        """
        Compare Data with other objects.
        :param other: Union[Data, Const] = object to compare with
        :return: bool = result of comparison
        """

        type_other = type(other)
        functions = {Const: self.__const_lt, Data: self.__data_lt}

        if type_other not in functions:
            raise ValueError(
                f"Unsupported operation '<' for Data and {type_other}")

        return functions[type_other](other)

    @unit_control
    def __data_eq(self, other):
        """
        Helper function of eq comparison of Data with Data.
        :param other: Data = Data to compare with
        :return: bool = result of comparison
        """

        return self.value - self.error - other.error <= other.value <= self.value + self.error + other.error

    @unit_control
    def __const_eq(self, other):
        """
        Helper function of eq comparison of Data with Const.
        :param other: Const = Const to compare with
        :return: bool = result of comparison
        """

        return self.value - self.error <= other.value <= self.value + self.error

    @instancemethod
    def __eq__(self, other):
        """
        Compare Data with other objects.
        :param other: Union[Data, Const] = object to compare with
        :return: bool = result of comparison
        """

        type_other = type(other)
        functions = {Const: self.__const_eq, Data: self.__data_eq}
        if type_other not in functions:
            raise ValueError(
                f"Unsupported operation '==' for Data and {type_other}")

        return functions[type_other](other)

    @unit_control
    def __data_gt(self, other):
        """
        Helper function of gt comparison of Data with Data.
        :param other: Data = Data to compare with
        :return: bool = result of comparison
        """

        return self.value - self.error - other.error > other.value

    @unit_control
    def __const_gt(self, other):
        """
        Helper function of eq comparison of Data with Const.
        :param other: Const = Const to compare with
        :return: bool = result of comparison
        """

        return self.value - self.error > other.value

    @instancemethod
    def __gt__(self, other):
        """
        Compare Data with other objects.
        :param other: Union[Data, Const] = object to compare with
        :return: bool = result of comparison
        """

        type_other = type(other)
        functions = {Const: self.__const_gt, Data: self.__data_gt}
        if type_other not in functions:
            raise ValueError(
                f"Unsupported operation '==' for Data and {type_other}")

        return functions[type_other](other)

    @instancemethod
    def __ne__(self, other):
        """
        Compare Data with other objects.
        :param other: Union[Data, Const] = object to compare with
        :return: bool = result of comparison
        """

        return not self.__eq__(other)

    @instancemethod
    def __ge__(self, other):
        """
        Compare Data with other objects.
        :param other: Union[Data, Const] = object to compare with
        :return: bool = result of comparison
        """

        return self.__gt__(other) or self.__eq__(other)

    @instancemethod
    def __le__(self, other):
        """
        Compare Data with other objects.
        :param other: Union[Data, Const] = object to compare with
        :return: bool = result of comparison
        """

        return self.__lt__(other) or self.__eq__(other)