Unary operation: SignExtend

        :param new_length: New length after sign-extension
        :return: A new StridedInterval

def agnostic_extend(self, new_length):
        """
        Unary operation: SignExtend

        :param new_length: New length after sign-extension
        :return: A new StridedInterval
        """
        '''
        In a sign-agnostic implementation of strided-intervals a number can be signed or unsigned both.
        Given a SI, we must pay attention how we extend its lower bound and upper bound.
        Assuming that the lower bound is in the left emishpere (positive number).
        Let's assume first that the SI is signed and its upper bound is in the right emisphere. Extending it with leading
        1s (i.e., its MSB)  is correct given that its values would be preserved.
        On the other hand if the number is unsigned we should not replicate its MSB, since this would increase the value
        of the upper bound in the new interval. In this case the correct approach would be to add 0 in front of the number,
        i.e., moving it to the left emisphere. But this approach wouldn't be correct in the first scenario (signed SI).
        The solution in this case is extend the upper bound with 1s. This gives us an overapproximation of the original
        SI.

        Extending this intuition, the implementation follows the below rules:
        (UB: upper bound, LB: lower bound, RE: right emisphere, LE: left emisphere)
        1* UB:LE and LB:LE: add leading 0s (sound).
        2* UB:RE and LB:RE and the LB is closer to the north pole: add leading 0s to LB and leading 1s to the UB (sound)
        3* UB:RE and LB:RE and UB is closer to the north pole: add leading 1s to LB and UB both (sound).
        4* UB:LE and LB:RE: add leading 0s to UB and leading 0s to LB (sound).
        5* UB:RE and LB:LE: add leading 0s to LB and leading 1s to UB (sound).
        6* UB:RE and LB:RE and LB = UB: add leading 0s to LB and 1s to UB and add stride from LB to UB ****
        '''

        si = self.copy()
        si._bits = new_length

        leading_1_lb = False
        leading_1_ub = False

        ub_msb = self._get_msb(self.upper_bound, self.bits)
        lb_msb = self._get_msb(self.lower_bound, self.bits)
        #the only one which chages the stride
        case_6 = False

        # LB:RE cases
        if lb_msb == 1:
            #2
            if ub_msb == 1 and self.upper_bound > self.lower_bound:
                leading_1_ub = True
            #3
            if ub_msb == 1 and self.lower_bound > self.upper_bound:
                leading_1_ub = True
                leading_1_lb = True
            #6
            if ub_msb == 1 and self.lower_bound == self.upper_bound:
                leading_1_ub = True
                case_6 = True
        #5
        elif ub_msb == 1:
            leading_1_ub = True

        if leading_1_lb:
            mask = (2 ** new_length - 1) - (2 ** self.bits - 1)
            si._lower_bound |= mask
        if leading_1_ub:
            mask = (2 ** new_length - 1) - (2 ** self.bits - 1)
            si._upper_bound |= mask
        if case_6:
            si.stride = si.upper_bound - si.lower_bound

        return si