Cada vez hay más integración entre la web y la domática, «internet of things» o «web of things» son ejemplos de ello. Uno de los problemas que te puedes encontrar es que a nivel de programación son dos mundos muy dispares. En los dispositivos electrónicos reina C/C++ y similares, mientras que en la web el más usado es JavaScript. Aunque ambos lenguajes no trabajan directamente uno con otro lo hacen a través de API a veces surge el problema tratar los datos y es que mientras que C tiene un tipado de datos muy específico el de JS es menos concreto. ¿Qué podemos hacer cuando necesitamos un tipado de datos muy concreto que nos permita incluso trabajar a nivel de bits?. Simplemente usar datos tipados en JS.
Datos tipados en JavaScript
Pese a su fama JS tiene más tipos de datos que los conocidos String, boolean, number.
En este caso vamos a centrarnos en los siguientes tipos que podemos usar para imitar los tipos habituales de C/C++. Podemos elegir el tamaño en bits, si es con signo o sin signo incluso la forma en que se comporta cuando hay un desbordamiento. La única pega, solo funciona con arrays lo que nos supone una incomodidad a la hora de trabajar con un único elemento (tendremos que usar un array de tamaño 1).
Tipo | Rango | Tamaño (bytes) | Descripción | Tipo en C/C++ |
---|---|---|---|---|
Int8Array | -128…127 | 1 | 8-bit con signo | int8_t |
Uint8Array | 0…255 | 1 | 8-bit sin signo | uint8_t |
Uint8ClampedArray | 0…255 | 1 | 8-bit sin signo (clamped) | uint8_t |
Int16Array | -32768…32767 | 2 | 16-bit entero con signo | int16_t |
Uint16Array | 0…65535 | 2 | 16-bit entero sin signo | uint16_t |
Int32Array | -2147483648… 2147483647 | 4 | 32-bit entero con signo | int32_t |
Uint32Array | 0…4294967295 | 4 | 32-bit entero sin signo | uint32_t |
Float32Array | 1.2E-38…3.4E38 | 4 | 32-bit punto flotante | float |
Float64Array | 5E-324…1.8E308 | 8 | 64-bit punto flotante | double |
BigInt64Array | -2^63…2^63 – 1 | 8 | 64-bit entero con signo | int64_t |
BigUint64Array | 0…2^64 – 1 | 8 | 64-bit entero sin signo | uint64_t |
Lo valores en punto flotante siguen el estándar IEEE.
Veamos algunos ejemplos de como usarlos y alguna propiedades interesantes (BYTES_PER_ELEMENT, byte que ocupa cada elemento; byteLength, longitud total del array en bytes):
var int8 = new Int8Array(3);
int8[0] = 42;
console.log(int8[0]); //42
console.log(int8.length); //3
console.log(int8.BYTES_PER_ELEMENT); //1
console.log(int8.byteLength); //3 = length * BYTES_PER_ELEMENT
var int16 = new Int16Array(3);
int16[0] = 42;
console.log(int16[0]); //42
console.log(int16.length); //3
console.log(int16.BYTES_PER_ELEMENT); //2
console.log(int16.byteLength); //6 = length * BYTES_PER_ELEMENT
Un dato clamped (acotado) es un dato que cuando se produce un desbordamiento de su valor por arriba o por abajo el dato toma su mayor y menor valor. O más simplemente explicado si intentas asignarle un valor mayor de 255 o menor que 0 se le asignará el valor 255 y 0 respectivamente. Lo datos que no son de tipo clamped simplemente ignoran los bits del desbordamiento. Podemos verlo mejor con un ejemplo:
var uInt8Clamped = new Uint8ClampedArray(1);
var uInt8 = new Uint8Array(1);
uInt8Clamped[0] = 256;
uInt8[0] = 256;
console.log(uInt8Clamped[0]); //255
console.log(uInt8[0]); //0
uInt8Clamped[0] = 265;
uInt8[0] = 265;
console.log(uInt8Clamped[0]); //255
console.log(uInt8[0]); //9
ArrayBuffer y Dataview
Aún hay un truco más que permite JS para trabajar con estos datos. Crear un ArrayBuffer que permite reservar un «espacio de memoria» indicando el tamaño del mismo en bytes. Desde un ArrayBuffer no puedes ni leer ni escribir esta memoria, para ello tienes que asignarlo a un array tipado como los uqe hemos visto antes. Puedes asignar el mismo ArrayBuffer a varios arrays tipados que lo compartirán:
var buffer = new ArrayBuffer(2);
var view8 = new Uint8Array(buffer);
var view16 = new Uint16Array(buffer);
view16[0] = 258;
console.log(view16[0]);// 258 -> 00000001 00000010
console.log(view8[0]); // 2 -> 00000010
console.log(view8[1]); // 1 -> 00000001
En el ejemplo se ve como un ArrayBuffer de 2 bytes se puede leer desde un array de 1 elemento de 16 bits o un array de un array de 2 elementos de 8 bits y como los valores se solapan.
Hay otra forma de hacerlo, con un DataView. Las ventajas de los DataView son dos:
- Permiten escribir y leer en el ArrayBuffer con cualquier tipo de datos usando el métodoset y get correspondiente getUint8(), setUint8(), getInt8(), setInt8(), getUint16(), setUint16(), …
- El orden en que se recuperan los bytes es más intuitivo
El ejemplo anterior con DataView
const buffer = new ArrayBuffer(2);
var view = new DataView(buffer, 0);
view.setUint16(0, 258); // (max unsigned 16-bit integer)
console.log(view.getUint16(0));// 258 -> 00000001 00000010
console.log(view.getUint8(0)); // 1 -> 00000001
console.log(view.getUint8(1)); // 2 -> 00000010