Почему целочисленное деление в C # возвращает целое число, а не число с плавающей запятой?

131

Кто-нибудь знает, почему целочисленное деление в C # возвращает целое число, а не число с плавающей запятой? В чем идея? (Это только наследие C / C ++?)

В C #:

float x = 13 / 4;   
//== operator is overridden here to use epsilon compare
if (x == 3.0)
   print 'Hello world';

Результатом этого кода будет:

'Hello world'

Строго говоря, целочисленного деления не существует (деление по определению - это операция, производящая рациональное число, целые числа - очень небольшое подмножество этого числа).

BanditoBunny
источник
46
потому что это integerразделение, а не floating pointразделение.
Хантер Макмиллен
это действительно должно (в VB.Net), это реализовано по-другому естественным математическим способом, где весь результат операции деления - иррациональное число.
BanditoBunny 01
3
Я думаю, вы имеете в виду рациональные числа . См. Википедию : деление двух целых чисел может привести к получению остатка. Чтобы завершить деление остатка, система счисления расширяется и включает дроби или рациональные числа, как их обычно называют.
crashmstr 01
6
Это причина того, что я не поклонник «копирования синтаксиса» в языках. Я пришел из VB, думая, что «C # - это .NET», а не «C # похож на C». Думаю, это моя ошибка, но в данном случае я предпочитаю путь VB. Если они столкнулись с проблемой генерации ошибки компилятора при использовании неинициализированных простых типов (вы даже не получите предупреждения в C), то почему бы не предупредить вас, когда вы назначаете целочисленное деление для числа с плавающей запятой?
darda

Ответы:

96

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

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

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

По этим (и другим связанным) причинам целочисленное деление дает целое число. Если вы хотите получить деление двух целых чисел с плавающей запятой, вам просто нужно не забыть привести одно к double/ float/ decimal.

Servy
источник
5
В VB.Net .Net архитекторы приняли другое решение: / - всегда деление с плавающей запятой, \ - целочисленное деление, так что это непоследовательно, кроме случаев, когда вы рассматриваете наследие C ++;
BanditoBunny 01
4
Во время компиляции вы можете определить, /будет ли оператор выполнять целочисленное деление или деление с плавающей запятой (если вы не используете динамическое). Если это трудно для вас , чтобы понять это , потому что вы делаете так много на этой одной линии, то я предложил бы ломать эту линию на несколько строк так , чтобы его легче понять, операнды ли целые числа или типа с плавающей точкой. Будущие читатели вашего кода, вероятно, оценят это.
Servy 01
5
Я лично считаю проблематичным, что мне всегда приходится думать, какие переменные я делю, я считаю это расточительным использованием своего внимания.
BanditoBunny
8
@pelesl Поскольку это было бы огромным критическим изменением, если бы астрономическое количество программ было нарушено, я могу с полной уверенностью сказать, что этого никогда не произойдет в C #. Это то, что нужно делать с первого дня на языке или не делать вообще.
Servy
2
@Servy: В C, C ++ и C # есть много подобных вещей. Лично я думаю, что C # был бы лучшим языком, если бы был другой оператор для целочисленного деления, и, чтобы избежать удивительного поведения законного кода, int/intоператор был бы просто недопустимым [с диагностикой, указывающей, что код должен приводить операнд или использовать другой оператор, в зависимости от желаемого поведения]. Если бы для целочисленного деления была доступна какая-либо другая хорошая последовательность токенов, можно было бы отказаться от использования /для этой цели, но я не знаю, что было бы практично.
supercat
77

См. Спецификацию C # . Есть три типа операторов деления

  • Целочисленное деление
  • Деление с плавающей точкой
  • Десятичное деление

В вашем случае у нас есть целочисленное деление со следующими правилами:

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

Я думаю, что причина, по которой C # использует этот тип деления для целых чисел (некоторые языки возвращают плавающий результат), аппаратная - деление целых чисел происходит быстрее и проще.

Сергей Березовский
источник
Какие языки возвращают плавающий результат? @SergeyBerezovskiy
Ilaria
40

Каждый тип данных может перегрузить каждый оператор. Если и числитель, и знаменатель являются целыми числами, целочисленный тип выполнит операцию деления и вернет целочисленный тип. Если вы хотите деление с плавающей запятой, вы должны привести одно или несколько чисел к типам с плавающей запятой, прежде чем делить их. Например:

int x = 13;
int y = 4;
float x = (float)y / (float)z;

или, если вы используете литералы:

float x = 13f / 4f;

Имейте в виду, что числа с плавающей запятой неточны. Если вам важна точность, используйте вместо этого что-то вроде десятичного типа.

Стивен Доггарт
источник
1
+1 за упоминание о том, что для деления с плавающей запятой необходимо использовать только один член.
Xynariz
Очевидно, что ваше утверждение о точности является правильным в контексте обучения и делает его несложным для понимания. Поскольку нам нужно быть максимально точными в нашей работе, я все же хочу уточнить точность: согласно IEE 754-1985 вы МОЖЕТЕ получить точный результат (хотя в большинстве случаев это не так). Вы можете получить точный результат, если ранее были представлены точные значения вычислений, и результат представляет собой, попросту говоря, сумму степеней 2. Даже при том, что не может быть лучшей практикой полагаться на эту точность в этих особых случаях.
Л. Монти
Дополнение к точности: вероятность получения точного результата резко улучшается, когда результат приближается к 1 или -1. Это может немного сбивать с толку, что эта вероятность все еще остается 0, поскольку существует бесконечное число и конечное число результатов, которые можно представить точно. :)
Л. Монти
1
@ L.Monty, спасибо, что подняли этот вопрос. С тех пор, как я написал этот ответ, я узнал больше о плавающих точках, и вы говорите справедливо. Технически я бы все же сказал, что мое утверждение «числа с плавающей запятой неточны» приемлемо в том смысле, что то, что что-то может быть точным, иногда не означает, что оно в целом является точным. Как говорится, сломанные часы показывают правильные часы дважды в день, но я бы никогда не назвал их точным прибором. Я на самом деле довольно удивлен , что это та часть , которая беспокоит вас больше , чем мое предположение о том , что тип десятичного является точным.
Стивен Доггарт
Десятичные дроби неточны по тем же причинам, что и числа с плавающей запятой; просто числа с плавающей точкой имеют основание 2, а десятичные числа - основание 10. Например, десятичный тип не может точно содержать точное значение 1/3.
Стивен Доггарт 07
11

Поскольку вы не используете суффикс, литералы 13и 4интерпретируются как целые числа:

Руководство :

Если буквальное не имеет суффикса, он имеет первый из этих типов , в которых его значение может быть представлено: int, uint, long, ulong.

Таким образом, поскольку вы объявляете 13целое число, будет выполнено целочисленное деление:

Руководство :

Для операции формы x / y применяется разрешение перегрузки бинарного оператора, чтобы выбрать конкретную реализацию оператора. Операнды преобразуются в типы параметров выбранного оператора, а тип результата - это тип, возвращаемый оператором.

Предварительно определенные операторы деления перечислены ниже. Все операторы вычисляют частное x и y.

Целочисленное деление:

int operator /(int x, int y);
uint operator /(uint x, uint y);
long operator /(long x, long y);
ulong operator /(ulong x, ulong y);

И так происходит округление в меньшую сторону:

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

Если вы сделаете следующее:

int x = 13f / 4f;

Вы получите ошибку компилятора, поскольку деление с плавающей запятой ( / оператор of 13f) приводит к получению числа с плавающей запятой, которое не может быть неявно преобразовано в int.

Если вы хотите, чтобы деление было делением с плавающей запятой, вам нужно сделать результат с плавающей запятой:

float x = 13 / 4;

Обратите внимание, что вы по-прежнему делите целые числа, которые неявно будут преобразованы в float: результат будет 3.0. Чтобы явно объявить операнды как float, используя fсуффикс ( 13f, 4f).

CodeCaster
источник
+1 за объяснение того, что вы можете получить ответ в виде числа с плавающей запятой, но при этом выполнять целочисленное деление. Кроме того, я видел еще один распространенный способ принудительного деления с плавающей запятой - это умножение первого члена деления на 1.0.
Xynariz
8

Это просто базовая операция .

Вспомните, когда вы научились делить. Вначале мы решили 9/6 = 1 with remainder 3.

9 / 6 == 1  //true
9 % 6 == 3 // true

Оператор / в сочетании с оператором% используются для получения этих значений.

Л. Монти
источник
6

Может быть полезно:

double a = 5.0/2.0;   
Console.WriteLine (a);      // 2.5

double b = 5/2;   
Console.WriteLine (b);      // 2

int c = 5/2;   
Console.WriteLine (c);      // 2

double d = 5f/2f;   
Console.WriteLine (d);      // 2.5
eozten
источник
Пожалуйста, попробуйте добавить несколько пояснений к своему ответу
NetStarter
Последнее выражение произведет 2.5, not 2.
Лассе В. Карлсен
Ага, неверное написание. Спасибо.
eozten
4

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

var a = (byte)5 / (byte)2;  // 2 (Int32)
var b = (short)5 / (byte)2; // 2 (Int32)
var c = 5 / 2;              // 2 (Int32)
var d = 5 / 2U;             // 2 (UInt32)
var e = 5L / 2U;            // 2 (Int64)
var f = 5L / 2UL;           // 2 (UInt64)
var g = 5F / 2UL;           // 2.5 (Single/float)
var h = 5F / 2D;            // 2.5 (Double)
var i = 5.0 / 2F;           // 2.5 (Double)
var j = 5M / 2;             // 2.5 (Decimal)
var k = 5M / 2F;            // Not allowed

Неявного преобразования между типами с плавающей запятой и десятичным типом нет, поэтому деление между ними не допускается. Вы должны явно указать и решить, какой из них вы хотите (Decimal имеет большую точность и меньший диапазон по сравнению с типами с плавающей запятой).

гт
источник