Есть ли вероятность того, что упрощенные формы определенных математических выражений вызовут ошибки переполнения, а сложные — нет?

Я пытался решить задачу №69 для лит-кода, которая включает в себя поиск квадратного корня из заданного числа.

Я применил подход бинарного поиска.

int mySqrt(int x) {
    if (x==0 || x==1){
        return x;
    }
    int start =1;
    int end=x;
    int mid=-1;
    while(start<=end){
        mid=(end+start)/2;//the problem lies here
        long long square=static_cast<long long>(mid)*mid;
        if (square>x){
            end=mid-1;
        }
        else if (square==x){
            return mid;
        }
        else{
            start=mid+1;
        }
     }
     return static_cast<int>(round(end));
}

Когда я изменил middle=start+(end-start)/2; тестовые примеры прошли нормально, однако для x=2147483647 выдалось «переполнение целого числа со знаком», говоря: «Ошибка времени выполнения: переполнение целого числа со знаком: 2147483647 + 1 не может быть представлено в типе 'инт''.

Вы задавали этот вопрос на форумах LeetCode? Если нет, то почему? Если да, пожалуйста, дайте ссылку на это обсуждение здесь, чтобы мы могли прочитать ответы.

Eljay 04.04.2024 14:48

Да, 2147483647 + 1 переполняется, а 1 + (2147483647 - 1)/2 нет. (И тестовый пример создан специально для того, чтобы вызвать это.) Это известная ошибка в Java SDK, которая оставалась незамеченной в течение многих лет.

molbdnilo 04.04.2024 15:12

Частичный ответ на заданный вопрос: да. Для вашего конкретного случая.... Если start и end оба положительны, и end > start, то (1) end - start представимо как int, а также (end - start)/2 и (2) start + (end - start)/2 находится между start и end, поэтому также никогда не переполняется. Однако (end+start)/2 сначала вычисляет end + start, который (для достаточно больших значений, например start = INT_MAX - 10 и end = INT_MAX - 5) может переполниться. В этом случае поведение не определено — деление на 2 не устраняет волшебным образом последствия переполнения. Вы можете провести аналогичные рассуждения и для других случаев.

Peter 04.04.2024 15:52
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы ответить на вопрос напрямую: да, разные математически эквивалентные выражения могут давать разные результаты в программе.

В этом случае, если сначала сложить start и end, а затем разделить на 2, если их сумма больше INT_MAX, получится целочисленное переполнение. Однако если вы вычтете start из end, переполнения быть не может, поскольку вы вычитаете два положительных числа.

Однако даже этот подход не совсем корректен, если числа могут быть отрицательными. Предпочитайте использовать std::midpoint (начиная с C++ 20) из заголовка <numeric>, чтобы вообще избежать этих проблем.

#include <numeric>
// ...
mid = std::midpoint(start, end);

@OP - Замена строки кода, вызывающей проблему, на std::midpoint(start, end); решает проблему (принимается Leetcode). Я предлагаю вам принять ответ Unmitigated.

PaulMcKenzie 04.04.2024 15:46

Другие вопросы по теме