Почему деление двух int не дает правильного значения при присвоении double?


110

Как получилось, что в следующем фрагменте

int a = 7;
int b = 3;
double c = 0;
c = a / b;

cв конечном итоге получает значение 2, а не 2.3333, как можно было бы ожидать. Если aи bявляются двойными, то ответ действительно равен 2,333. Но наверняка, потому что c уже есть двойник, он должен был работать с целыми числами?

Так почему int/int=doubleже не работает?


Ответы:


161

Это связано с тем, что вы используете версию с целочисленным делением operator/, которая занимает 2 intс и возвращает int. Чтобы использовать doubleверсию, которая возвращает a double, по крайней мере один из ints должен быть явно приведен к a double.

c = a/(double)b;

9
Я бы предпочел явно преобразовать оба aи bв doubleпросто для ясности, но на самом деле это не имеет значения.
Джон Диблинг,

31
Поскольку вопрос помечен как C ++, я бы предпочел видеть static_cast <>, а не приведение C.
Мартин Йорк

16
Лично я считаю, что приведение в стиле C более четкое (приведение в большинстве других распространенных языков выполняется в стиле C). static_cast<>мне всегда казалось, что надо запыхаться. В случае примитивов, не существует на самом деле никакой опасности получать static_cast<>и reinterpret_cast<>перепутаны.
Чад Ла Гуардия,

6
@ Tux-D: Для арифметических слепков? Я бы предпочел избежать static_castэтого и вместо этого использовать приведение в стиле C. Здесь нет никакой пользы от использования приведения в стиле C ++, и они загромождают код намного больше, чем приведения в стиле C. Арифметическое приведение - это именно тот контекст, в котором приведение в стиле C совершенно уместно и фактически более уместно, чем другие приведения.
AnT

19
Иногда можно перехитрить людей, "не придерживающихся стиля Си", написав double(b). Они не всегда понимают, что это преобразование, поскольку оно выглядит так же, как явный вызов конструктора.
Стив Джессоп,

12

Вот:

а) При intделении на два всегда выполняется целочисленное деление. Так что результатом a/bв вашем случае может быть только файл int.

Если вы хотите сохранить aи bкак ints, но при этом полностью разделить их, вы должны преобразовать хотя бы одно из них в двойное: (double)a/bили a/(double)bили (double)a/(double)b.

б) cявляется double, поэтому он может принять в intзначение на присваивания: intавтоматически преобразуются doubleи назначено c.

c) Помните, что при присваивании сначала= вычисляется выражение справа от (в соответствии с правилом (a) выше и без учета переменной слева от ), а затем присваивается переменной слева от (согласно ( б) выше). Я считаю, что это завершает картину.==


11

За очень немногими исключениями (я могу придумать только одно) C ++ определяет полное значение выражения (или подвыражения) из самого выражения. Что вы делаете с результатами выражения, не имеет значения. В вашем случае в выражении a / bнет и doubleвидно; все есть int. Таким образом, компилятор использует целочисленное деление. Только получив результат, он решает, что с ним делать, и преобразует его в double.


3
Единственное исключение, о котором я могу думать, - это выбор перегрузки функции при получении указателя - значение &funcnameзависит от того, к какому типу вы его приводите .
Стив Джессоп,

2
@ Стив Джессоп Это единственное исключение, о котором я тоже могу думать. (Но, учитывая размер и сложность стандарта, я не хотел бы поклясться, что не пропустил ни одного.)
Джеймс Канце

6

c- doubleпеременная, но присвоенное ей intзначение является значением, потому что оно получается в результате деления на два ints, что дает вам «целочисленное деление» (отбрасывание остатка). Итак, что происходит в строке c=a/b,

  1. a/b оценивается, создавая временный тип int
  2. значение временного присваивается cпосле преобразования в тип double.

Значение a/bопределяется без привязки к его контексту (присвоение double).


6

Когда вы делите два целых числа, результатом будет целое число, независимо от того, что вы сохраняете его в двойном формате.


5

В языке C ++ на результат подвыражения никогда не влияет окружающий контекст (за некоторыми редкими исключениями). Это один из принципов, которым тщательно следует язык. Выражение c = a / bсодержит независимое подвыражение a / b, которое интерпретируется независимо от всего, что находится за пределами этого подвыражения. Язык не заботится о том, что вы позже назначите результат для double. a / bявляется целочисленным делением. Все остальное не имеет значения. Вы увидите, что этому принципу следуют во многих уголках спецификации языка. Вот и все, как работает C ++ (и C).

Одним из примеров исключения, упомянутого выше, является назначение / инициализация указателя функции в ситуациях с перегрузкой функции.

void foo(int);
void foo(double);

void (*p)(double) = &foo; // automatically selects `foo(fouble)`

Это тот контекст, в котором левая часть присвоения / инициализации влияет на поведение правой части. (Кроме того, инициализация ссылки на массив предотвращает распад типа массива, что является еще одним примером аналогичного поведения.) Во всех других случаях правая часть полностью игнорирует левую часть.


4

/Оператор может быть использован для целочисленного деления или деления с плавающей точкой. Вы даете ему два целочисленных операнда, поэтому он выполняет целочисленное деление, а затем результат сохраняется в виде двойника.


2

Технически это зависит от языка, но почти все языки относятся к этому предмету одинаково. Когда существует несоответствие типов между двумя типами данных в выражении, большинство языков будет пытаться преобразовать данные с одной стороны, =чтобы они соответствовали данным с другой стороны, в соответствии с набором предопределенных правил.

При делении двух чисел одного типа (целые числа, числа с двойной точностью и т. Д.) Результат всегда будет одного и того же типа (поэтому 'int / int' всегда будет иметь результатом int).

В этом случае у вас есть double var = integer result функция, которая преобразует целочисленный результат в двойной после вычисления, и в этом случае дробные данные уже потеряны. (большинство языков делают это преобразование, чтобы предотвратить неточности типов, не вызывая исключения или ошибки).

Если вы хотите сохранить результат как двойной, вам нужно создать ситуацию, когда у вас есть double var = double result

Самый простой способ сделать это - заставить выражение в правой части уравнения преобразоваться в двойное:

c = a/(double)b

Разделение между целым числом и числом double приведет к преобразованию целого числа в число double (обратите внимание, что при выполнении математических операций компилятор часто выполняет преобразование с повышением частоты до наиболее определенного типа данных, чтобы предотвратить потерю данных).

После апкаста aбудет двойной, и теперь у вас есть деление на два дубля. Это создаст желаемое разделение и назначение.

СНОВА, пожалуйста , обратите внимание, что это зависит от языка (и может быть даже от компилятора), однако почти все языки (конечно, все те, которые я могу придумать в своей голове) относятся к этому примеру одинаково.


Этот вопрос помечен тегом [C ++], и Стандарт C ++ точно определяет, как это работает. Не уверен, что вы имеете в виду под «специфичным для языка» и уж точно не специфичным для компилятора, если предположить, что никакие расширения компилятора не задействованы.
Джон Диблинг,

Также неверно сказать, что «double var = integer result, который приводит двойную var к int». Двойное значение не приводится к типу int. Результат типа int преобразуется в double.
Джон Диблинг,

Я допускал возможность расширения компилятора (однажды у меня действительно была эта проблема, когда моя среда «неверно приводила» результаты, и я не мог понять, почему). И результат зависит от языка, поскольку в некоторых языках не соблюдаются одни и те же правила приведения типов. Я не считал, что это специфический тег C ++. Вы правы насчет комментария "double var = integer result". Отредактировано, чтобы отразить это. Спасибо!
Matthewdunnam

0

Главное, чтобы один из элементов расчета был типом float-double. Затем, чтобы получить двойной результат, вам нужно привести этот элемент, как показано ниже:

c = static_cast<double>(a) / b;

или c = a / static_cast (b);

Или вы можете создать его напрямую:

c = 7.0 / 3;

Обратите внимание, что один из элементов расчета должен иметь значение '.0', чтобы указать деление типа float-double на целое число. В противном случае, несмотря на то, что переменная c является двойной, результат тоже будет нулевым.


Что дает ваш ответ, которого еще нет ни в одном из 9 других ответов?
болов
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.