Целочисленное деление: как вы производите двойное число?


248

Для этого блока кода:

int num = 5;
int denom = 7;
double d = num / denom;

значение dесть 0.0. Его можно заставить работать, приведя:

double d = ((double) num) / denom;

Но есть ли другой способ получить правильный doubleрезультат? Я не люблю кастовать примитивов, кто знает, что может случиться.



9
приведение int к двойному значению безопасно, вы всегда получите одно и то же значение без потери точности.
Питер Лори

1
Я хотел бы знать, являются ли следующие правильные шаги, предпринятые компилятором для деления: 1) приведение числа к числу с плавающей точкой 2) приведение числа к числу с плавающей точкой 2) разделить число на число. Пожалуйста, дайте мне знать, если я ошибаюсь.
Мэнни

Ответы:


156
double num = 5;

Это позволяет избежать броска. Но вы обнаружите, что приведенные преобразования четко определены. Вам не нужно угадывать, просто проверьте JLS . int to double - расширяющееся преобразование. Из §5.1.2 :

Расширяющиеся примитивные преобразования не теряют информацию об общей величине числового значения.

[...]

Преобразование int или длинного значения в плавающее или длинного значения в удвоенное значение может привести к потере точности, то есть результат может потерять некоторые из младших значащих битов значения. В этом случае результирующее значение с плавающей запятой будет правильно округленной версией целочисленного значения с использованием режима округления до ближайшего стандарта IEEE 754 (§4.2.4) .

5 можно выразить ровно как двойной.


60
+1. Хватит бояться кастинга. Узнайте, как это работает. Это все четко определено.
Марк Питерс

1
@FabricioPH Это работает в любой ситуации и идентично решению * 1.0.
Витрувий

3
@Saposhiente Это выходит за рамки работы или нет. Изменение типа переменной кажется мне грязным кодом. Если у вас есть что-то, что ДОЛЖНО БЫТЬ целым числом, и вы изменяете представление для числа с плавающей точкой просто для того, чтобы выполнять математическую операцию, вы можете рисковать собой. Также в некотором контексте чтение переменной в виде целого числа облегчает понимание кода.
Fabricio PH

2
@FabricioPH, умножение на 1.0все равно меняет тип результата, просто по-другому. «Мне кажется, грязный код» не объясняет, в каких сценариях вы считаете, что умножение на 1.0самом деле лучше (или почему это может быть).
Мэтью Флэшен

4
@FabricioPH Вы и OP, похоже, работаете под ложным убеждением (где вы говорите: «Изменение типа переменной»), что (double)numкаким-то образом меняется num. Это не - это создает временную двойную для контекста утверждения.
Пол Томблин

100

Что не так с примитивами?

Если по какой-то причине вы не хотите снимать, вы можете сделать

double d = num * 1.0 / denom;

63
... который выполняет неявное приведение перед умножением
Алиса Перселл

2
В большинстве случаев это лучше, чем изменение типа другой переменной.
Fabricio PH

3
@FabricioPH Ничто не говорит о том, что это правда, если у вас нет источника для цитирования.
Витрувий

1
@Saposhiente ответил в вашем другом подобном комментарии
Фабрика PH

1
Вы также можете использовать постфикс «D» (для выполнения того же неявного приведения); - так, например:double d = num * 1D / denom;
BrainSlugs83

73

Я не люблю кастовать примитивов, кто знает, что может случиться.

Почему у вас иррациональный страх кастовать примитивов? Ничего плохого не случится , когда вы приводите intк double. Если вы просто не знаете, как это работает, посмотрите это в Спецификации языка Java . Приведение intк doubleявляется расширяющимся примитивным обращением .

Вы можете избавиться от лишней пары скобок, приведя знаменатель вместо числителя:

double d = num / (double) denom;

2
Вы также можете сделать double d = (double) num / denom;... (ОК, это зависит от приоритета)
user85421

27

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

double d = 5 / (double) 20; //cast to double, to do floating point calculations

Обратите внимание, что приведение результата не сделает этого

double d = (double)(5 / 20); //produces 0.0

17

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

1. double d = (double)5 / 20;
2. double v = (double)5 / (double) 20;
3. double v = 5 / (double) 20;

Обратите внимание, что приведение результата не сделает этого. Потому что первое деление выполняется согласно правилу приоритета.

double d = (double)(5 / 20); //produces 0.0

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


10

Типовое литье - единственный путь


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

Теперь, есть несколько способов , которыми мы можем попытаться получить точное doubleзначение (где numи denomявляются intтипом, и, конечно , с литьем ) -

  1. с явным приведением:

    • double d = (double) num / denom;
    • double d = ((double) num) / denom;
    • double d = num / (double) denom;
    • double d = (double) num / (double) denom;

но нет double d = (double) (num / denom);

  1. с неявным приведением:

    • double d = num * 1.0 / denom;
    • double d = num / 1d / denom;
    • double d = ( num + 0.0 ) / denom;
    • double d = num; d /= denom;

но не double d = num / denom * 1.0;
и неdouble d = 0.0 + ( num / denom );


2

использовать что-то вроде:

double step = 1d / 5;

(1d приведен к удвоению)


5
1d не актерский состав, это просто декларация.
Sefier Tang

0

Вы можете рассмотреть обертывание операций. Например:

class Utils
{
    public static double divide(int num, int denom) {
        return ((double) num) / denom;
    }
}

Это позволяет вам посмотреть (только один раз), выполняет ли актерство именно то, что вы хотите. Этот метод также может быть проверен, чтобы убедиться, что он продолжает делать то, что вы хотите. Также не имеет значения, какой трюк вы используете, чтобы вызвать разделение (вы можете использовать любой из ответов здесь), если это приводит к правильному результату. Везде, где вам нужно разделить два целых числа, теперь вы можете просто позвонить Utils::divideи поверить, что это правильно.



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