Calculates the minimal integer that appears in both StridedIntervals.
        It's equivalent to finding an integral solution for equation `ax + b = cy + d` that makes `ax + b` minimal
        si_0.stride, si_1.stride being a and c, and si_0.lower_bound, si_1.lower_bound being b and d, respectively.
        Upper bounds are used to check whether the minimal common integer exceeds the bound or not. None is returned
        if no minimal common integers can be found within the range.

        Some assumptions:
        # - None of the StridedIntervals straddles the south pole. Consequently, we have x <= max_int(si.bits) and y <=
        #   max_int(si.bits)
        # - a, b, c, d are all positive integers
        # - x >= 0, y >= 0

        :param StridedInterval si_0: the first StridedInterval
        :param StridedInterval si_1: the second StrideInterval
        :return: the minimal common integer, or None if there is no common integer

def _minimal_common_integer_splitted(si_0, si_1):
        """
        Calculates the minimal integer that appears in both StridedIntervals.
        It's equivalent to finding an integral solution for equation `ax + b = cy + d` that makes `ax + b` minimal
        si_0.stride, si_1.stride being a and c, and si_0.lower_bound, si_1.lower_bound being b and d, respectively.
        Upper bounds are used to check whether the minimal common integer exceeds the bound or not. None is returned
        if no minimal common integers can be found within the range.

        Some assumptions:
        # - None of the StridedIntervals straddles the south pole. Consequently, we have x <= max_int(si.bits) and y <=
        #   max_int(si.bits)
        # - a, b, c, d are all positive integers
        # - x >= 0, y >= 0

        :param StridedInterval si_0: the first StridedInterval
        :param StridedInterval si_1: the second StrideInterval
        :return: the minimal common integer, or None if there is no common integer
        """

        a, c = si_0.stride, si_1.stride
        b, d = si_0.lower_bound, si_1.lower_bound

        # if any of them is an integer
        if si_0.is_integer:
            if si_1.is_integer:
                return None if si_0.lower_bound != si_1.lower_bound else si_0.lower_bound
            elif si_0.lower_bound >= si_1.lower_bound and \
                    si_0.lower_bound <= si_1.upper_bound and \
                    (si_0.lower_bound - si_1.lower_bound) % si_1.stride == 0:
                return si_0.lower_bound
            else:
                return None
        elif si_1.is_integer:
            return StridedInterval._minimal_common_integer_splitted(si_1, si_0)

        # shortcut
        if si_0.upper_bound < si_1.lower_bound or si_1.upper_bound < si_0.lower_bound:
            # They don't overlap at all
            return None

        if (d - b) % StridedInterval.gcd(a, c) != 0:
            # They don't overlap
            return None

        """
        Given two strided intervals a = sa[lba, uba] and b = sb[lbb, ubb], the first integer shared
        by them is found by finding the minimum values of ka and kb which solve the equation:
            ka * sa + lba = kb * sb + lbb
        In particular one can solve the above diophantine equation and find the parameterized solutions
        of ka and kb, with respect to a parameter t.
        The minimum natural value of the parameter t which gives two positive natural values of ka and kb
        is used to resolve ka and kb, and finally to solve the above equation and get the minimum shared integer.
        """
        x, y = StridedInterval.diop_natural_solution_linear(-(b-d), a, -c)
        if a is None or b is None:
            return None
        first_integer = x * a + b
        assert first_integer == y*c + d
        if first_integer >= si_0.lower_bound and first_integer <= si_0.upper_bound and \
            first_integer >= si_1.lower_bound and first_integer <= si_1.upper_bound:
            return first_integer

        else:
            return None