Integer Vs Long Confusion

He visto a muchos creer en lo siguiente

VBA convierte todos los valores enteros para escribir Long

De hecho, incluso el artículo de MSDN dice

“En versiones recientes, sin embargo, VBA convierte todos los valores enteros para escribir Long, incluso si están declarados como tipo Integer”.

enter image description here

¿Cómo es esto posible? Considera este simple ejemplo.

Sub Sample() Dim I As Integer I = 123456789 End Sub 

Si VBA convierte todos los valores Integer para escribir Long incluso si están declarados como tipo entero , entonces lo anterior nunca debería darle el error de Overflow .

¿Que me estoy perdiendo aqui? ¿O debería considerar que la statement es incorrecta y prestar atención a eso que el enlace dice al principio?

“Este contenido ya no se mantiene activamente. Se proporciona como está, para cualquier persona que aún pueda estar usando estas tecnologías, sin garantías ni afirmaciones de exactitud con respecto a la versión más reciente del producto o la versión del servicio”.

NOTA: Estoy agregando las tags Excel-VBA/Powerpoint-VBA/Word-VBA/Access-VBA con la esperanza de que un experto de VBA pueda ver esto en caso de que el experto no supervise solo la etiqueta de VBA . Eliminaré estas tags innecesarias una vez que se haya buscado una solución.

Un entero declarado como un Integer todavía está marcado como un Integer . La documentación de msdn hace referencia a cómo la variable se almacena internamente. En un sistema de 32 bits, un entero se almacenará en 32 bits no Bytes , mientras que en un sistema de 16 bits el valor se almacenará en un espacio o registro de 16 bits, se habría almacenado en 16. De ahí el tamaño máximo.

No hay conversión de tipo en lo que concierne a VBA. Un int es un int y un long es largo, aunque ocupan tanto espacio .

He pasado mucho tiempo trabajando en el entorno de VBA y tengo todos los motivos para creer que la afirmación de este artículo es, en el mejor de los casos, engañosa.

Nunca me encontré con una situación en la que se realiza una conversión automática inesperada. Por supuesto, la asignación por valor a un tipo más grande (como un Double o Long ) estaría implícito.

Un caso específico donde la conversión automática sería un cambio radical sería una asignación a un tipo de Variant . Sin una conversión, el tipo sería VT_I2, con la conversión VT_I4.

Pasar un entero Escriba ByRef en una función que espera que Long emita un error de tipo en Office 2013.

Sospecho que se están refiriendo al almacenamiento interno del Integer : es muy probable que no estén alineados en palabras de 16 bits en memoria (compárese con un miembro de estructura short en C / C ++). Probablemente están hablando de eso.

La conversión es solo para optimización de memoria, no para código de usuario. Para el progtwigdor, prácticamente no hay cambios, ya que los límites mínimo / máximo de los tipos de datos siguen siendo los mismos.

Si tomas ese para como un todo, te darás cuenta de que esa afirmación está en el contexto del rendimiento solamente, y no de otra manera. Esto se debe a que el tamaño predeterminado de los números es Int32 o Int64 (dependiendo de si se trata de un sistema de 32 bits o de 64 bits). El procesador puede procesar hasta ese gran número de una vez. Si declara una unidad más pequeña que esta, el comstackdor tiene que reducirla, y eso requiere más esfuerzos que simplemente usar el tipo predeterminado. Y el procesador tampoco tiene ninguna ganancia. Por lo tanto, aunque declare su variable como Integer , el comstackdor le asigna una memoria Long , porque sabe que tiene que trabajar más sin obtener ninguna ganancia.

Como progtwigdor de VBA, lo que tiene importancia para usted es: Declare your variables as LONG instead of INTEGER even if you want to store small numbers in them.

“En versiones recientes, sin embargo, VBA convierte todos los valores enteros para escribir Long, incluso si están declarados como tipo Integer”.

No creo esa documentación. Considere el siguiente ejemplo simple (ejecutado en Excel 2010):

 Sub checkIntegerVsLong() 'Check the total memory allocation for an array Dim bits As Integer 'or long? :) Dim arrInteger() As Integer ReDim arrInteger(1 To 5) arrInteger(1) = 12 arrInteger(2) = 456 'get total memory allocation for integer in array bits = VarPtr(arrInteger(2)) - VarPtr(arrInteger(1)) Debug.Print "For integer: " & bits & " bits and " & bits * 8 & " bytes." Dim arrLong() As Long ReDim arrLong(1 To 5) arrLong(1) = 12 arrLong(2) = 456 'get memory allocation for long bits = VarPtr(arrLong(2)) - VarPtr(arrLong(1)) Debug.Print "For long: " & bits & " bits and " & bits * 8 & " bytes." End Sub 

Esto imprime:

Para entero: 2 bits y 16 bytes.

Por mucho tiempo: 4 bits y 32 bytes.

También puede probar esto en variables individuales usando lo siguiente:

 Sub testIndividualValues() Dim j As Long Dim i As Integer Dim bits As Integer bits = LenB(i) Debug.Print "Length of integer: " & bits & " bits and " & bits * 8 & " bytes." bits = LenB(j) Debug.Print "Length of long: " & bits & " bits and " & bits * 8 & " bytes." End Sub 

que imprime

Longitud del entero: 2 bits y 16 bytes.

Longitud de largo: 4 bits y 32 bytes.

Por último, puedes usar una comparación de tipos aquí:

 Public Type myIntegerType a As Integer b As Integer End Type Public Type myLongType a As Long b As Long End Type Public Sub testWithTypes() Dim testInt As myIntegerType Dim testLong As myLongType Dim bits As Integer bits = VarPtr(testInt.b) - VarPtr(testInt.a) Debug.Print "For integer in types: " & bits & " bits and " & bits * 8 & " bytes." bits = VarPtr(testLong.b) - VarPtr(testLong.a) Debug.Print "For long in types: " & bits & " bits and " & bits * 8 & " bytes." End Sub 

que también imprime:

Para enteros en tipos: 2 bits y 16 bytes.

Para long in types: 4 bits y 32 bytes.

Esta es una evidencia bastante convincente para mí de que VBA en realidad trata a Integer y Long diferente.

Si VBA se convierte silenciosamente entre bastidores, esperaría que esos devuelvan el mismo número de bits / bytes para cada una de las ubicaciones de asignación de puntero. Pero en el primer caso, con Entero, solo está asignando 16 bits, mientras que para las variables Largas, asigna 32 bits.

¿Y qué?

Entonces a tu pregunta de

Si VBA convierte todos los valores enteros para escribir Long incluso si están declarados como tipo entero, entonces lo anterior nunca debería darle el error de desbordamiento.

Tiene todo el sentido que obtendría un error de desbordamiento, ya que VBA no ha asignado realmente la memoria para una statement Long a la Integer .

También me gustaría saber si esto devuelve lo mismo en todas las versiones de Office. Solo puedo probar en Office 2010 en Windows 7 de 64 bits.

En lo que respecta a mis pruebas, un entero de VBA aún requiere dos bytes (Probado en Access 2016, comstackción 8201).

La conversión implícita a una operación larga (y viceversa, si se trata de una operación de escritura) ocurre para las operaciones , no para el almacenamiento , hasta donde puedo encontrar. Por ejemplo, si hago myInt + 1 , myInt se lanza a un tiempo largo, luego uno se agrega a ese tiempo, y luego el resultado se devuelve a un int, lo que resulta en una pérdida de rendimiento en comparación con solo usar un Long . Entonces, aunque cuesta menos memoria usar un número entero, todas las operaciones sufrirán en rendimiento.

Como señaló Mathieu Guindon en respuesta a Enderland / Elysian Fields, probar el almacenamiento de VBA con funciones de VBA no puede probar nada, así que vamos a ver más bajo nivel y ver directamente lo que está almacenado en la memoria, y manipular esa memoria.

Primero, declaraciones:

 Declare PtrSafe Sub CopyMemory Lib "Kernel32.dll" Alias "RtlMoveMemory" (ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As Long) Public Function ToBits(b As Byte) As String Dim i As Integer For i = 7 To 0 Step -1 ToBits = ToBits & IIf((b And 2 ^ i) = (2 ^ i), 1, 0) Next End Function 

Ahora, voy a probar dos cosas:

  1. La memoria a la que apunta VarPtr contiene enteros de 16 bits
  2. La manipulación de esta memoria manipula los enteros que utiliza VBA, incluso si la manipulas fuera de VBA

El código:

 Dim i(0 To 1) As Integer 'Using negatives to prove things aren't longs, because of the sign bit i(0) = -2 ^ 15 + (2 ^ 0) '10000000 00000001 i(1) = -2 ^ 15 + (2 ^ 1) '10000000 00000010 Dim bytes(0 To 3) As Byte CopyMemory VarPtr(bytes(0)), VarPtr(i(0)), 4 Dim l As Long For l = 3 To 0 Step -1 Debug.Print ToBits(bytes(l)) & " "; 'Prints 10000000 00000010 10000000 00000001 Next 'Now, let's write something back bytes(0) = &HFF '0xFFFF = signed -1 bytes(1) = &HFF CopyMemory VarPtr(i(0)), VarPtr(bytes(0)), 2 Debug.Print i(0) '-1 

Entonces, podemos estar seguros de que VBA, de hecho, escribe enteros de 2 bytes en la memoria y los lee desde la memoria, cuando declaramos cosas como un entero.

Hasta ahora, no he visto a nadie mencionar el problema de la alineación de bytes. Para manipular los números, se debe cargar en el registro y, como regla general, un registro no puede contener más de una variable. Creo que los registros también deben borrarse de las instrucciones anteriores, por lo que para garantizar que la variable se cargue correctamente, es necesario volver a alinearlos, lo que también puede implicar extender o poner a cero el registro .

También puede observar la alineación de bytes usando el código VBA:

 Public Type x a As Integer b As Integer l As Long End Type Public Type y a As Integer l As Long b As Integer End Type Public Sub test() Dim x As x Dim y As y Debug.Print LenB(x) Debug.Print LenB(xa), LenB(xb), LenB(xl) Debug.Print LenB(y) Debug.Print LenB(ya), LenB(yl), LenB(yb) End Sub 

Aunque el UDT y contiene la misma cantidad de miembros y cada miembro tiene el mismo tipo de datos; con la única diferencia que es el orden de los miembros, LenB() dará resultados diferentes; en una plataforma de 32 bits, x consume solo 8 bytes, mientras que y necesitará 12 bytes. La palabra alta entre xa y xl y después de xb simplemente se ignora.

El otro punto es que el problema no es exclusivo de VBA. Por ejemplo, C ++ tiene las mismas consideraciones que se ilustran aquí y aquí . De modo que este es en realidad un nivel mucho más bajo y, por lo tanto, no se puede “ver” el comportamiento de extensión de signo / extensión cero al cargar las variables en los registros para realizar la operación. Para ver eso, necesitas el desassembly.