Declarando carrozas, ¿por qué el tipo predeterminado es doble?

Tengo curiosidad sobre por qué los literales float deben declararse así:

float f = 0.1f; 

En lugar de

 float f = 0.1; 

¿Por qué el tipo predeterminado es un doble? ¿Por qué el comstackdor no puede inferir que se trata de una flotación mirando desde el lado izquierdo de la tarea? Google solo da una explicación sobre cuáles son los valores predeterminados, no por qué son así.

¿Por qué el tipo predeterminado es un doble?

Esa es una pregunta que sería mejor hacer a los diseñadores del lenguaje Java. Son las únicas personas que conocen las verdaderas razones por las que se tomó la decisión de diseño del idioma. Pero espero que el razonamiento sea algo así como lo siguiente:

Necesitaban distinguir entre los dos tipos de literales porque realmente significan valores diferentes … desde una perspectiva matemática.

Supongamos que hacen que “flote” el valor predeterminado para los literales, considere este ejemplo

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

En lo anterior, d y d2 realidad tendrían diferentes valores. En el primer caso, un valor float baja precisión se convierte a un valor double mayor precisión en el punto de asignación. Pero no puedes recuperar la precisión que no está allí.

Postulo que un diseño de lenguaje donde esas dos declaraciones son legales y significan cosas diferentes es una idea MALA … considerando que el significado real de la primera afirmación es diferente al significado “natural”.

Al hacerlo de la manera en que lo han hecho:

 double d = 0.1f; double d2 = 0.1; 

son ambos legales, y significan cosas diferentes de nuevo. Pero en la primera statement, la intención del progtwigdor es clara, y la segunda afirmación, el significado “natural” es lo que obtiene el progtwigdor. Y en este caso:

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

… el comstackdor recoge el desajuste.


Supongo que usar flotadores es la excepción y no la regla (usando dobles en su lugar) con hardware moderno, así que en algún momento tendría sentido suponer que el usuario tiene la intención de 0.1f cuando escribe float f = 0.1;

Podrían hacer eso ya. Pero el problema es crear un conjunto de reglas de conversión de tipo que funcionen … y son lo suficientemente simples como para que no necesites un título en Java -ology que realmente lo entienda. Tener 0.1 significa diferentes cosas en diferentes contextos sería confuso. Y considera esto:

 void method(float f) { ... } void method(double d) { ... } // Which overload is called in the following? this.method(1.0); 

El diseño del lenguaje de progtwigción es complicado. Un cambio en un área puede tener consecuencias en otros.


ACTUALIZAR para abordar algunos puntos planteados por @supercat.

@supercat: Dadas las sobrecargas anteriores, ¿qué método se invocará para el método (16777217)? ¿Es esa la mejor opción?

Cometí incorrectamente … error de comstackción. De hecho, la respuesta es method(float) .

El JLS dice esto:

15.12.2.5. Elegir el método más específico

Si más de un método de miembro es accesible y aplicable a una invocación de método, es necesario elegir uno para proporcionar el descriptor para el envío del método en tiempo de ejecución. El lenguaje de progtwigción Java usa la regla de que se elige el método más específico.

[Los símbolos m1 y m2 denotan los métodos que son aplicables.]

[Si] m2 no es genérico, y m1 y m2 son aplicables por invocación estricta o suelta , y donde m1 tiene tipos de parámetros formales S1, …, Sn y m2 tiene tipos de parámetros formales T1, …, Tn, el tipo Si es más específico que Ti para el argumento ei para todo i (1 ≤ i ≤ n, n = k).

Las condiciones anteriores son las únicas circunstancias bajo las cuales un método puede ser más específico que otro.

Un tipo S es más específico que un tipo T para cualquier expresión si S <: T (§4.10).

En este caso, estamos comparando el method(float) y el method(double) que son aplicables a la llamada. Como float <: double , es más específico y, por lo tanto, se seleccionará el method(float) .

@supercat: Tal comportamiento puede causar problemas si, por ejemplo, una expresión como int2 = (int) Math.Round(int1 * 3.5) o long2 = Math.Round(long1 * 3.5) se reemplaza con int1 = (int) Math.Round(int2 * 3) o long2 = Math.Round(long1 * 3)

El cambio parecería inofensivo, pero las primeras dos expresiones son correctas hasta 613566756 o 2573485501354568 y las dos últimas fallan por encima de 5592405 [la última es completamente falsa por encima de 715827882 ].

Si estás hablando de una persona que está haciendo ese cambio … bueno, sí.

Sin embargo, el comstackdor no hará ese cambio a sus espaldas. Por ejemplo, int1 * 3.5 tiene un tipo double (el int se convierte en un double ), por lo que termina llamando al Math.Round(double) .

Como regla general, la aritmética de Java convertirá implícitamente de tipos numéricos “más pequeños” a “más grandes”, pero no de “más grande” a “más pequeño”.

Sin embargo, todavía debe tener cuidado ya que (en su ejemplo de redondeo):

  • el producto de un entero y punto flotante puede no ser representable con suficiente precisión porque (digamos) un float tiene menos bits de precisión que un int .

  • arrojando el resultado de Math.round(double) a un tipo entero puede resultar en la conversión al valor más pequeño / más grande del tipo entero.

Pero todo esto ilustra que el soporte aritmético en un lenguaje de progtwigción es complicado, y hay inevitables problemas para un progtwigdor nuevo o incauto.

Ja, esto es solo la punta del iceberg, amigo mío.

Los progtwigdores que vienen de otros idiomas no tienen inconveniente en agregar un poco de F a un literal en comparación con:

 SomeReallyLongClassName x = new SomeReallyLongClassName(); 

Bastante redundante, ¿verdad?

Es cierto que tendrías que hablar con los desarrolladores centrales de Java para obtener más información. Pero como explicación pura a nivel de superficie, un concepto importante para comprender es qué es una expresión . En Java (no soy un experto, así que tome esto con un grano de sal), creo que en el nivel del comstackdor su código se analiza en términos de expresiones; asi que:

 float f 

tiene un tipo, y

 0.1f 

también tiene un tipo ( float ).

En términos generales, si va a asignar una expresión a otra, los tipos deben estar de acuerdo. Hay algunos casos muy específicos en los que esta regla se relaja (por ejemplo, encajonar una primitiva como int en un tipo de referencia como Integer ); pero en general es válido.

Puede parecer tonto en este caso, pero aquí hay un caso muy similar en el que no parece tan tonto:

 double getDouble() { // some logic to return a double } void example() { float f = getDouble(); } 

Ahora, en este caso, podemos ver que tiene sentido que el comstackdor detecte algo incorrecto. El valor devuelto por getDouble tendrá 64 bits de precisión mientras que f solo puede contener 32 bits; entonces, sin un lanzamiento explícito, es posible que el progtwigdor haya cometido un error.

Estos dos escenarios son claramente diferentes desde un punto de vista humano ; pero mi punto sobre las expresiones es que cuando el código primero se descompone en expresiones y luego se analiza, son las mismas.

Estoy seguro de que los autores del comstackdor podrían haber escrito alguna lógica no tan inteligente para volver a interpretar los literales en función de los tipos de expresiones a las que están asignados; simplemente no lo hicieron. Probablemente no se consideró que valiera la pena el esfuerzo en comparación con otras características.

Para la perspectiva, muchos lenguajes pueden hacer inferencias tipo; en C #, por ejemplo, puedes hacer esto:

 var x = new SomeReallyLongClassName(); 

Y el tipo de x será inferido por el comstackdor basado en esa asignación.

Para los literales, sin embargo, C # es lo mismo que Java a este respecto.

float tiene muy poca precisión, por lo que una pregunta más interesante es; ¿Por qué es compatible en absoluto? Existen situaciones excepcionales en las que float puede compartir algo de memoria (si tiene millones de ellas) o las necesita para intercambiar datos con algo que espera flotar.

En general, usar el double es una mejor opción, casi tan rápido para las PC modernas, y el ahorro de memoria es menor en comparación con la precisión adicional que proporciona.

Java no mira el lado izquierdo en ninguna situación para ver cómo se usa un valor. por ejemplo, el tipo de devolución de un método no es parte de la firma. Se eliminará implícitamente en algunos casos para asignación y asignaciones de operador, pero esto generalmente es para mantener cierta compatibilidad con C y es bastante improvisada en mi humilde opinión.