Подтвердить что ты не робот

Объявление float, почему default type double?

Мне любопытно, почему плавающие литералы должны быть объявлены так:

float f = 0.1f;

Вместо

float f = 0.1;

Почему тип по умолчанию является двойным, почему компилятор не может сделать вывод о том, что это float, который смотрит в левую сторону задания? Google только объясняет, что такое значения по умолчанию, а не почему они так.

4b9b3361

Ответ 1

Почему тип по умолчанию двойной?

Это вопрос, который лучше всего задать разработчикам языка Java. Они - единственные люди, которые знают реальные причины, по которым было принято решение о создании языка. Но я ожидаю, что рассуждение было следующим:

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

Предположим, что они сделали "поплавок" по умолчанию для литералов, рассмотрим этот пример

// (Hypothetical "java" code ... )
double d = 0.1;
double d2 = 0.1d;

В приведенном выше тексте d и d2 будут иметь разные значения. В первом случае значение низкой точности float преобразуется в значение с более высокой точностью double в точке назначения. Но вы не можете восстановить точность, которой нет.

Я полагаю, что дизайн языка, в котором эти два утверждения являются законными, и означает разные вещи, является идеей BAD... учитывая, что фактический смысл первого утверждения отличается от "естественного" значения.

Выполняя это так, как они это сделали:

double d = 0.1f;
double d2 = 0.1;

являются законными и снова означают разные вещи. Но в первом утверждении намерение программиста ясно, а второе утверждение "естественным" значением является то, что получает программист. И в этом случае:

float f = 0.1f;
float f2 = 0.1;    // compilation error!

... компилятор подбирает несоответствие.


Я предполагаю, что использование float - это исключение, а не правило (используя вместо него двойники) с современным оборудованием, поэтому в какой-то момент было бы разумно предположить, что пользователь намеревается 0.1f, когда пишет float f = 0.1;

Они уже могли это сделать. Но проблема связана с набором правил преобразования типов, которые работают... и достаточно просты, что вам не нужна степень в Java-ology, чтобы реально понять. Наличие 0.1 означает, что разные вещи в разных контекстах будут путать. И подумайте об этом:

void method(float f) { ... }
void method(double d) { ... }

// Which overload is called in the following?
this.method(1.0);

Дизайн языка программирования сложный. Изменение в одной области может иметь последствия для других.


UPDATE, чтобы ответить на некоторые вопросы, поднятые @supercat.

@supercat: Учитывая приведенные выше перегрузки, какой метод будет вызываться для метода (16777217)? Это лучший выбор?

Я неправильно комментировал... ошибку компиляции. На самом деле ответ method(float).

JLS говорит об этом:

15.12.2.5. Выбор наиболее конкретного метода

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

...

[Символы m1 и m2 обозначают методы, которые применимы.]

[If] m2 не является общим, а m1 и m2 применимы строгими или free invocation и где m1 имеет формальные типы параметров S1,..., Sn и m2 имеет формальные типы параметров T1,..., Tn, тип Si больше специфический, чем Ti для аргумента ei для всех я (1 ≤ я ≤ n, n = k).

...

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

Тип S более специфичен, чем тип T для любого выражения, если S <: T (§4.10).

В этом случае мы сравниваем method(float) и method(double), которые оба применимы к вызову. Поскольку float <: double, это более специфично, и поэтому будет выбран method(float).

@supercat: Такое поведение может вызвать проблемы, если, например, выражение например, int2 = (int) Math.Round(int1 * 3.5) или long2 = Math.Round(long1 * 3.5) заменяется на int1 = (int) Math.Round(int2 * 3) или long2 = Math.Round(long1 * 3)

Изменение будет выглядеть безобидно, но первые два выражения исправить до 613566756 или 2573485501354568, а последние два сбой выше 5592405 [последний полностью подделка выше 715827882].

Если вы говорите о человеке, делающем это изменение... ну да.

Однако компилятор не будет делать это за вашей спиной. Например, int1 * 3.5 имеет тип double (int преобразуется в double), поэтому вы вызываете Math.Round(double).

Как правило, арифметика Java будет неявно преобразовывать из "меньших" в "более крупные" числовые типы, но не от "большего" до "меньшего".

Однако вам все равно нужно быть осторожным, так как (в примере округления):

  • произведение целого числа и плавающей запятой не может быть представлено с достаточной точностью, поскольку (скажем) a float имеет меньшее количество бит точности, чем int.

  • листинг результата Math.Round(double) для целочисленного типа может привести к преобразованию в наименьшее/наибольшее значение целочисленного типа.

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

Ответ 2

Ха, это только верхушка айсберга, мой друг.

Программисты, поступающие с других языков, конечно, не возражают против добавления небольшого F к литералу по сравнению с:

SomeReallyLongClassName x = new SomeReallyLongClassName();

Довольно избыточно, правильно?

Это правда, что вам придется поговорить с основными разработчиками Java, чтобы получить больше информации. Но как чистое объяснение на уровне поверхности, одна важная концепция для понимания - это выражение. В Java (я не эксперт, так что принимайте это с солью), я считаю, что на уровне компилятора ваш код анализируется с точки зрения выражений; так:

float f

имеет тип, а

0.1f

также имеет тип (float).

Вообще говоря, если вы собираетесь назначить одно выражение другому, типы должны согласиться. Есть несколько очень конкретных случаев, когда это правило ослаблено (например, боксирование примитива типа int в ссылочном типе, таком как Integer); но в целом он имеет место.

В этом случае это может показаться глупым, но здесь очень похожий случай, когда это не кажется таким глупым:

double getDouble() {
    // some logic to return a double
}

void example() {
    float f = getDouble();
}

Теперь в этом случае мы видим, что компилятор имеет смысл обнаружить что-то неправильно. Значение, возвращаемое getDouble, будет иметь 64 бита точности, тогда как F может содержать только 32 бита; поэтому, без явного приведения, возможно, что программист допустил ошибку.

Эти два сценария явно отличаются от человеческой точки зрения; но моя точка зрения заключается в том, что когда код сначала разбивается на выражения и затем анализируется, они одинаковы.

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

Для перспективы, много языков могут делать вывод типа; в С#, например, вы можете сделать это:

var x = new SomeReallyLongClassName();

И тип x будет выведен компилятором на основе этого назначения.

Для литералов, однако, С# аналогичен Java в этом отношении.

Ответ 3

float имеет очень небольшую точность, и поэтому более интересный вопрос: почему он вообще поддерживается? Есть редкие ситуации, когда float может иметь такую ​​же память (если у вас их миллионы), или вам нужны они для обмена данными с чем-то, что ожидает float.

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

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