commit
12275e82ea
2 changed files with 597 additions and 0 deletions
@ -0,0 +1,99 @@
@@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="ru"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<title>Чтение .npz в браузере</title> |
||||
<script src="npz-reader.js"></script> |
||||
<style> |
||||
body { |
||||
font-family: Arial, sans-serif; |
||||
max-width: 1200px; |
||||
margin: 0 auto; |
||||
padding: 20px; |
||||
} |
||||
#input_file { |
||||
margin-bottom: 20px; |
||||
padding: 10px; |
||||
font-size: 16px; |
||||
} |
||||
#output { |
||||
background: #f5f5f5; |
||||
border: 1px solid #ddd; |
||||
border-radius: 4px; |
||||
padding: 15px; |
||||
overflow-x: auto; |
||||
white-space: pre-wrap; |
||||
word-wrap: break-word; |
||||
} |
||||
.info { |
||||
margin-bottom: 10px; |
||||
color: #666; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
<h1>Чтение NPZ файлов в браузере</h1> |
||||
<p class="info">Выберите .npz файл для чтения (без использования внешних библиотек)</p> |
||||
|
||||
<input type="file" id="input_file" accept=".npz"> |
||||
<pre id="output">Ожидание файла...</pre> |
||||
|
||||
<script> |
||||
// Получаем ссылки на DOM-элементы |
||||
const input_file = document.getElementById('input_file'); |
||||
const output_pre = document.getElementById('output'); |
||||
|
||||
// Обработчик выбора файла |
||||
input_file.addEventListener('change', async (event) => { |
||||
const file = event.target.files[0]; |
||||
if (!file) { |
||||
output_pre.textContent = 'Файл не выбран.'; |
||||
return; |
||||
} |
||||
|
||||
output_pre.textContent = 'Чтение файла...\nРазмер файла: ' + (file.size / 1024 / 1024).toFixed(2) + ' МБ'; |
||||
|
||||
try { |
||||
// Чтение файлового объекта как ArrayBuffer |
||||
console.log('Начало чтения файла...'); |
||||
const array_buffer = await file.arrayBuffer(); |
||||
console.log('Файл прочитан, размер:', array_buffer.byteLength, 'байт'); |
||||
|
||||
// Используем наш NPZReader для чтения файла |
||||
console.log('Начало парсинга NPZ...'); |
||||
output_pre.textContent = 'Распаковка ZIP архива...'; |
||||
const result = await NPZReader.readNPZ(array_buffer); |
||||
console.log('NPZ распарсен, количество массивов:', Object.keys(result).length); |
||||
|
||||
// Форматируем вывод для лучшей читаемости |
||||
const formattedResult = {}; |
||||
for (const [key, value] of Object.entries(result)) { |
||||
// Конвертируем данные в обычный массив, обрабатывая BigInt |
||||
let dataArray; |
||||
if (value.data instanceof BigInt64Array || value.data instanceof BigUint64Array) { |
||||
// Для BigInt конвертируем в строки |
||||
dataArray = Array.from(value.data).slice(0, 100).map(x => x.toString()); |
||||
} else { |
||||
dataArray = Array.from(value.data).slice(0, 100); |
||||
} |
||||
|
||||
formattedResult[key] = { |
||||
shape: value.shape, |
||||
dtype: value.dtype, |
||||
data: dataArray, // Показываем первые 100 элементов |
||||
totalElements: value.data.length, |
||||
fortran_order: value.fortran_order |
||||
}; |
||||
} |
||||
|
||||
// Выводим результаты (с обработкой BigInt) |
||||
output_pre.textContent = JSON.stringify(formattedResult, null, 2); |
||||
} catch (err) { |
||||
output_pre.textContent = 'Ошибка: ' + err.message + '\n\n' + err.stack; |
||||
console.error(err); |
||||
} |
||||
}); |
||||
</script> |
||||
|
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,498 @@
@@ -0,0 +1,498 @@
|
||||
/** |
||||
* Чтение NPZ файлов в браузере без внешних библиотек |
||||
* NPZ файл - это ZIP архив, содержащий .npy файлы |
||||
*/ |
||||
|
||||
// Проверка поддержки форматов сжатия
|
||||
const SUPPORTED_DEFLATE_FORMATS = []; |
||||
if (typeof DecompressionStream !== 'undefined') { |
||||
// Проверяем поддержку форматов
|
||||
// Примечание: создание DecompressionStream может не выбрасывать ошибку сразу,
|
||||
// поэтому проверяем при использовании
|
||||
// Приоритет: 'deflate-raw' для ZIP (raw deflate), затем 'deflate' (zlib формат)
|
||||
SUPPORTED_DEFLATE_FORMATS.push('deflate-raw', 'deflate'); |
||||
} |
||||
|
||||
class NPZReader { |
||||
/** |
||||
* Читает NPZ файл из ArrayBuffer |
||||
* @param {ArrayBuffer} buffer - ArrayBuffer с содержимым NPZ файла |
||||
* @returns {Promise<Object>} Объект с ключами - именами массивов, значениями - массивами данных |
||||
*/ |
||||
static async readNPZ(buffer) { |
||||
const zipFiles = await this.parseZIP(buffer); |
||||
console.log(`Найдено файлов в ZIP: ${Object.keys(zipFiles).length}`); |
||||
console.log('Файлы:', Object.keys(zipFiles)); |
||||
|
||||
const result = {}; |
||||
|
||||
for (const [filename, fileData] of Object.entries(zipFiles)) { |
||||
if (filename.endsWith('.npy')) { |
||||
const arrayName = filename.replace('.npy', ''); |
||||
console.log(`Парсинг NPY файла: ${filename} -> ${arrayName}`); |
||||
try { |
||||
result[arrayName] = this.parseNPY(fileData); |
||||
console.log(`NPY файл ${filename} успешно распарсен`); |
||||
} catch (err) { |
||||
console.error(`Ошибка парсинга NPY файла ${filename}:`, err); |
||||
throw err; |
||||
} |
||||
} else { |
||||
console.log(`Пропущен файл (не .npy): ${filename}`); |
||||
} |
||||
} |
||||
|
||||
console.log(`Итого распарсено массивов: ${Object.keys(result).length}`); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Парсит ZIP архив (упрощённая реализация для NPZ файлов) |
||||
* @param {ArrayBuffer} buffer - ArrayBuffer с ZIP архивом |
||||
* @returns {Promise<Object>} Объект с именами файлов и их содержимым |
||||
*/ |
||||
static async parseZIP(buffer) { |
||||
const view = new DataView(buffer); |
||||
const files = {}; |
||||
let offset = 0; |
||||
|
||||
// Ищем центральный каталог в конце файла
|
||||
// ZIP файл имеет структуру: [локальные файлы] [центральный каталог] [конец центрального каталога]
|
||||
|
||||
// Ищем сигнатуру конца центрального каталога (0x06054b50)
|
||||
let eocdOffset = -1; |
||||
for (let i = buffer.byteLength - 22; i >= 0; i--) { |
||||
if (view.getUint32(i, true) === 0x06054b50) { |
||||
eocdOffset = i; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (eocdOffset === -1) { |
||||
throw new Error('Неверный формат ZIP файла'); |
||||
} |
||||
|
||||
// Читаем информацию о центральном каталоге
|
||||
const cdOffset = view.getUint32(eocdOffset + 16, true); |
||||
const cdSize = view.getUint32(eocdOffset + 12, true); |
||||
const totalEntries = view.getUint16(eocdOffset + 10, true); |
||||
|
||||
// Читаем записи центрального каталога
|
||||
let currentOffset = cdOffset; |
||||
for (let i = 0; i < totalEntries; i++) { |
||||
// Сигнатура центрального файла (0x02014b50)
|
||||
if (view.getUint32(currentOffset, true) !== 0x02014b50) { |
||||
throw new Error('Ошибка чтения центрального каталога'); |
||||
} |
||||
|
||||
// Флаги сжатия
|
||||
const flags = view.getUint16(currentOffset + 8, true); |
||||
const method = view.getUint16(currentOffset + 10, true); |
||||
|
||||
// Размеры
|
||||
const compressedSize = view.getUint32(currentOffset + 20, true); |
||||
const uncompressedSize = view.getUint32(currentOffset + 24, true); |
||||
|
||||
// Длина имени файла
|
||||
const filenameLength = view.getUint16(currentOffset + 28, true); |
||||
const extraLength = view.getUint16(currentOffset + 30, true); |
||||
const commentLength = view.getUint16(currentOffset + 32, true); |
||||
|
||||
// Смещение локального заголовка
|
||||
const localHeaderOffset = view.getUint32(currentOffset + 42, true); |
||||
|
||||
// Читаем имя файла
|
||||
const filenameBytes = new Uint8Array(buffer, currentOffset + 46, filenameLength); |
||||
const filename = new TextDecoder('utf-8').decode(filenameBytes); |
||||
|
||||
// Отладочная информация
|
||||
console.log(`Файл: ${filename}, метод сжатия: ${method}, размер: ${compressedSize}/${uncompressedSize}`); |
||||
|
||||
// Переходим к следующей записи
|
||||
currentOffset += 46 + filenameLength + extraLength + commentLength; |
||||
|
||||
// Читаем локальный заголовок файла
|
||||
if (view.getUint32(localHeaderOffset, true) !== 0x04034b50) { |
||||
throw new Error('Ошибка чтения локального заголовка'); |
||||
} |
||||
|
||||
const localFilenameLength = view.getUint16(localHeaderOffset + 26, true); |
||||
const localExtraLength = view.getUint16(localHeaderOffset + 28, true); |
||||
const dataOffset = localHeaderOffset + 30 + localFilenameLength + localExtraLength; |
||||
|
||||
// Читаем данные файла
|
||||
if (method === 0) { |
||||
// Без сжатия (store)
|
||||
console.log(`Чтение несжатого файла ${filename} (размер: ${(uncompressedSize / 1024).toFixed(2)} КБ)...`); |
||||
const fileData = new Uint8Array(buffer, dataOffset, uncompressedSize); |
||||
// Создаём копию данных, чтобы иметь независимый ArrayBuffer
|
||||
const fileDataCopy = new Uint8Array(fileData); |
||||
files[filename] = fileDataCopy.buffer; |
||||
console.log(`Файл ${filename} прочитан (${fileDataCopy.length} байт)`); |
||||
} else if (method === 8) { |
||||
// Deflate сжатие - используем встроенный API браузера
|
||||
// ZIP использует "raw deflate" (без zlib заголовка)
|
||||
console.log(`Начало распаковки файла ${filename} (${(compressedSize / 1024 / 1024).toFixed(2)} МБ -> ${(uncompressedSize / 1024 / 1024).toFixed(2)} МБ)...`); |
||||
const compressedData = new Uint8Array(buffer, dataOffset, compressedSize); |
||||
try { |
||||
const startTime = Date.now(); |
||||
const decompressedData = await this.inflate(compressedData); |
||||
const endTime = Date.now(); |
||||
console.log(`Файл ${filename} распакован за ${((endTime - startTime) / 1000).toFixed(2)} секунд`); |
||||
files[filename] = decompressedData.buffer; |
||||
} catch (err) { |
||||
throw new Error(`Ошибка распаковки файла ${filename}: ${err.message}`); |
||||
} |
||||
} else { |
||||
throw new Error(`Неподдерживаемый метод сжатия: ${method} для файла ${filename}`); |
||||
} |
||||
} |
||||
|
||||
return files; |
||||
} |
||||
|
||||
/** |
||||
* Распаковывает данные, сжатые методом Deflate |
||||
* @param {Uint8Array} data - Сжатые данные |
||||
* @returns {Promise<Uint8Array>} Распакованные данные |
||||
*/ |
||||
static async inflate(data) { |
||||
// Используем встроенный API DecompressionStream (доступен в современных браузерах)
|
||||
if (typeof DecompressionStream === 'undefined') { |
||||
throw new Error('DecompressionStream не поддерживается. Используйте современный браузер (Chrome 80+, Firefox 113+, Safari 16.4+).'); |
||||
} |
||||
|
||||
// ZIP использует "raw deflate" (без zlib заголовка и контрольной суммы)
|
||||
// Используем поддерживаемые форматы
|
||||
const formats = SUPPORTED_DEFLATE_FORMATS.length > 0
|
||||
? SUPPORTED_DEFLATE_FORMATS
|
||||
: ['deflate-raw', 'deflate']; // Fallback на стандартные форматы
|
||||
|
||||
let lastError = null; |
||||
|
||||
for (const format of formats) { |
||||
try { |
||||
console.log(`Попытка распаковки с форматом: ${format}`); |
||||
// Создаём новый stream для каждой попытки
|
||||
const stream = new DecompressionStream(format); |
||||
const writer = stream.writable.getWriter(); |
||||
const reader = stream.readable.getReader(); |
||||
|
||||
// Запускаем чтение параллельно с записью
|
||||
const readPromise = (async () => { |
||||
const chunks = []; |
||||
let done = false; |
||||
let totalRead = 0; |
||||
while (!done) { |
||||
const { done: readerDone, value } = await reader.read(); |
||||
done = readerDone; |
||||
if (value) { |
||||
chunks.push(value); |
||||
totalRead += value.length; |
||||
// Логируем прогресс каждые 10 МБ
|
||||
if (totalRead % (10 * 1024 * 1024) < value.length) { |
||||
console.log(`Распаковано: ${(totalRead / 1024 / 1024).toFixed(2)} МБ`); |
||||
} |
||||
} |
||||
} |
||||
console.log(`Распаковка завершена, всего: ${(totalRead / 1024 / 1024).toFixed(2)} МБ`); |
||||
return { chunks, totalRead }; |
||||
})(); |
||||
|
||||
// Записываем данные частями, чтобы не блокировать поток
|
||||
console.log('Запись данных в stream...'); |
||||
const chunkSize = 1024 * 1024; // 1 МБ за раз
|
||||
let written = 0; |
||||
for (let i = 0; i < data.length; i += chunkSize) { |
||||
const chunk = data.slice(i, Math.min(i + chunkSize, data.length)); |
||||
await writer.write(chunk); |
||||
written += chunk.length; |
||||
if (written % (1024 * 1024 * 5) < chunkSize) { // Логируем каждые 5 МБ
|
||||
console.log(`Записано: ${(written / 1024 / 1024).toFixed(2)} МБ из ${(data.length / 1024 / 1024).toFixed(2)} МБ`); |
||||
} |
||||
} |
||||
await writer.close(); |
||||
console.log('Данные записаны, ожидание завершения распаковки...'); |
||||
|
||||
// Ждём завершения чтения
|
||||
const { chunks, totalRead } = await readPromise; |
||||
|
||||
// Объединяем все чанки в один массив
|
||||
if (chunks.length === 0) { |
||||
throw new Error('Нет данных после распаковки'); |
||||
} |
||||
|
||||
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); |
||||
const result = new Uint8Array(totalLength); |
||||
let offset = 0; |
||||
for (const chunk of chunks) { |
||||
result.set(chunk, offset); |
||||
offset += chunk.length; |
||||
} |
||||
|
||||
return result; |
||||
} catch (err) { |
||||
lastError = err; |
||||
// Пробуем следующий формат
|
||||
console.log(`Формат ${format} не сработал:`, err.message); |
||||
continue; |
||||
} |
||||
} |
||||
|
||||
// Если ни один формат не сработал
|
||||
throw new Error(`Ошибка распаковки: ${lastError?.message || 'неизвестная ошибка'}. Попробованы форматы: ${formats.join(', ')}. ZIP использует raw deflate, который может не поддерживаться в вашем браузере.`); |
||||
} |
||||
|
||||
/** |
||||
* Парсит NPY файл (формат NumPy) |
||||
* @param {ArrayBuffer} buffer - ArrayBuffer с содержимым NPY файла |
||||
* @returns {Object} Объект с данными массива {data, shape, dtype} |
||||
*/ |
||||
static parseNPY(buffer) { |
||||
const view = new DataView(buffer); |
||||
let offset = 0; |
||||
|
||||
// Проверяем магический заголовок (0x93 'NUMPY')
|
||||
if (view.getUint8(offset) !== 0x93) { |
||||
throw new Error('Неверный формат NPY файла: неверный магический байт'); |
||||
} |
||||
offset++; |
||||
|
||||
const magic = String.fromCharCode( |
||||
view.getUint8(offset++), |
||||
view.getUint8(offset++), |
||||
view.getUint8(offset++), |
||||
view.getUint8(offset++), |
||||
view.getUint8(offset++) |
||||
); |
||||
|
||||
if (magic !== 'NUMPY') { |
||||
throw new Error('Неверный формат NPY файла: неверная магическая строка'); |
||||
} |
||||
|
||||
// Читаем версию формата
|
||||
const majorVersion = view.getUint8(offset++); |
||||
const minorVersion = view.getUint8(offset++); |
||||
|
||||
// Читаем длину заголовка
|
||||
let headerLength; |
||||
if (majorVersion === 1) { |
||||
headerLength = view.getUint16(offset, true); |
||||
offset += 2; |
||||
} else if (majorVersion === 2 || majorVersion === 3) { |
||||
headerLength = view.getUint32(offset, true); |
||||
offset += 4; |
||||
} else { |
||||
throw new Error(`Неподдерживаемая версия NPY формата: ${majorVersion}.${minorVersion}`); |
||||
} |
||||
|
||||
// Читаем заголовок (Python dict в виде строки)
|
||||
const headerBytes = new Uint8Array(buffer, offset, headerLength); |
||||
const headerStr = new TextDecoder('utf-8').decode(headerBytes); |
||||
offset += headerLength; |
||||
|
||||
// Парсим заголовок (упрощённый парсинг Python dict)
|
||||
console.log('Заголовок NPY:', headerStr); |
||||
const header = this.parseHeader(headerStr); |
||||
console.log('Распарсенный заголовок:', header); |
||||
|
||||
// Читаем данные
|
||||
const dataOffset = offset; |
||||
const dataLength = buffer.byteLength - dataOffset; |
||||
const data = new Uint8Array(buffer, dataOffset, dataLength); |
||||
|
||||
console.log(`Данные NPY: offset=${dataOffset}, length=${dataLength}, shape=${JSON.stringify(header.shape)}, dtype=${header.descr}`); |
||||
|
||||
// Проверяем, что shape и descr найдены
|
||||
if (!header.descr) { |
||||
throw new Error('Не найден dtype (descr) в заголовке NPY'); |
||||
} |
||||
if (!header.shape || header.shape.length === 0) { |
||||
throw new Error('Не найден shape в заголовке NPY'); |
||||
} |
||||
|
||||
// Конвертируем данные в нужный тип
|
||||
const typedArray = this.convertToTypedArray(data, header.descr, header.shape); |
||||
console.log(`Конвертировано: ${typedArray.length} элементов`); |
||||
|
||||
return { |
||||
data: typedArray, |
||||
shape: header.shape, |
||||
dtype: header.descr, |
||||
fortran_order: header.fortran_order || false |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Парсит заголовок NPY файла (Python dict) |
||||
* @param {string} headerStr - Строка заголовка |
||||
* @returns {Object} Распарсенный заголовок |
||||
*/ |
||||
static parseHeader(headerStr) { |
||||
// Упрощённый парсер Python dict
|
||||
// Формат: {'descr': '<f8', 'fortran_order': False, 'shape': (2, 3), }
|
||||
|
||||
const result = {}; |
||||
|
||||
// Извлекаем dtype (descr)
|
||||
const descrMatch = headerStr.match(/descr['"]\s*:\s*['"]([^'"]+)['"]/); |
||||
if (descrMatch) { |
||||
result.descr = descrMatch[1]; |
||||
} else { |
||||
console.warn('Не найден descr в заголовке:', headerStr); |
||||
} |
||||
|
||||
// Извлекаем fortran_order
|
||||
const fortranMatch = headerStr.match(/fortran_order['"]\s*:\s*(True|False)/); |
||||
if (fortranMatch) { |
||||
result.fortran_order = fortranMatch[1] === 'True'; |
||||
} |
||||
|
||||
// Извлекаем shape
|
||||
// Формат может быть: 'shape': (3,) или 'shape': (3, 4) или 'shape': (3,)
|
||||
const shapeMatch = headerStr.match(/shape['"]\s*:\s*\(([^)]*)\)/); |
||||
if (shapeMatch) { |
||||
const shapeStr = shapeMatch[1].trim(); |
||||
if (shapeStr) { |
||||
// Разбиваем по запятой и парсим числа
|
||||
result.shape = shapeStr.split(',').map(s => { |
||||
const trimmed = s.trim(); |
||||
return trimmed ? parseInt(trimmed, 10) : null; |
||||
}).filter(x => x !== null); // Убираем null значения
|
||||
} else { |
||||
result.shape = []; // Скаляр
|
||||
} |
||||
} else { |
||||
console.warn('Не найден shape в заголовке:', headerStr); |
||||
result.shape = []; |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Конвертирует байты в типизированный массив |
||||
* @param {Uint8Array} data - Сырые данные |
||||
* @param {string} dtype - Описание типа данных (например, '<f8', '<i4') |
||||
* @param {Array<number>} shape - Форма массива |
||||
* @returns {TypedArray} Типизированный массив |
||||
*/ |
||||
static convertToTypedArray(data, dtype, shape) { |
||||
// Парсим dtype: '<f8' -> little-endian, float64
|
||||
// Формат: [endian][type][size]
|
||||
// endian: '<' (little), '>' (big), '=' (native), '|' (not applicable, single byte)
|
||||
// type: 'f' (float), 'i' (int), 'u' (uint), 'c' (complex), 'b' (bool)
|
||||
// size: количество байт (1, 2, 4, 8)
|
||||
|
||||
console.log(`convertToTypedArray: dtype=${dtype}, shape=${JSON.stringify(shape)}, data.length=${data.length}`); |
||||
|
||||
const endian = dtype[0]; |
||||
const type = dtype[1]; |
||||
const size = parseInt(dtype.substring(2), 10); |
||||
|
||||
// Проверяем, что size валидный
|
||||
if (isNaN(size) || size <= 0) { |
||||
throw new Error(`Неверный размер dtype: ${dtype}`); |
||||
} |
||||
|
||||
const isLittleEndian = endian === '<' || (endian === '=' && this.isLittleEndian()); |
||||
|
||||
// Вычисляем общее количество элементов
|
||||
const totalElements = shape.length === 0 ? 1 : shape.reduce((a, b) => a * b, 1); |
||||
|
||||
let typedArray; |
||||
|
||||
switch (type) { |
||||
case 'f': // float
|
||||
if (size === 4) { |
||||
typedArray = new Float32Array(data.buffer, data.byteOffset, totalElements); |
||||
} else if (size === 8) { |
||||
typedArray = new Float64Array(data.buffer, data.byteOffset, totalElements); |
||||
} else { |
||||
throw new Error(`Неподдерживаемый размер float: ${size}`); |
||||
} |
||||
break; |
||||
|
||||
case 'i': // int (signed)
|
||||
if (size === 1) { |
||||
typedArray = new Int8Array(data.buffer, data.byteOffset, totalElements); |
||||
} else if (size === 2) { |
||||
typedArray = new Int16Array(data.buffer, data.byteOffset, totalElements); |
||||
} else if (size === 4) { |
||||
typedArray = new Int32Array(data.buffer, data.byteOffset, totalElements); |
||||
} else if (size === 8) { |
||||
// Int64Array не существует, используем BigInt64Array
|
||||
typedArray = new BigInt64Array(data.buffer, data.byteOffset, totalElements); |
||||
} else { |
||||
throw new Error(`Неподдерживаемый размер int: ${size}`); |
||||
} |
||||
break; |
||||
|
||||
case 'u': // uint (unsigned)
|
||||
if (size === 1) { |
||||
typedArray = new Uint8Array(data.buffer, data.byteOffset, totalElements); |
||||
} else if (size === 2) { |
||||
typedArray = new Uint16Array(data.buffer, data.byteOffset, totalElements); |
||||
} else if (size === 4) { |
||||
typedArray = new Uint32Array(data.buffer, data.byteOffset, totalElements); |
||||
} else if (size === 8) { |
||||
// Uint64Array не существует, используем BigUint64Array
|
||||
typedArray = new BigUint64Array(data.buffer, data.byteOffset, totalElements); |
||||
} else { |
||||
throw new Error(`Неподдерживаемый размер uint: ${size}`); |
||||
} |
||||
break; |
||||
|
||||
case 'b': // bool
|
||||
typedArray = new Uint8Array(data.buffer, data.byteOffset, totalElements); |
||||
break; |
||||
|
||||
case 'c': // complex
|
||||
// Комплексные числа занимают в 2 раза больше места
|
||||
if (size === 8) { |
||||
// complex64 = 2 * float32
|
||||
typedArray = new Float32Array(data.buffer, data.byteOffset, totalElements * 2); |
||||
} else if (size === 16) { |
||||
// complex128 = 2 * float64
|
||||
typedArray = new Float64Array(data.buffer, data.byteOffset, totalElements * 2); |
||||
} else { |
||||
throw new Error(`Неподдерживаемый размер complex: ${size}`); |
||||
} |
||||
break; |
||||
|
||||
default: |
||||
throw new Error(`Неподдерживаемый тип данных: ${type}`); |
||||
} |
||||
|
||||
// Если порядок байт не совпадает, нужно переставить байты
|
||||
if (!isLittleEndian && size > 1) { |
||||
const view = new DataView(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); |
||||
for (let i = 0; i < typedArray.length; i++) { |
||||
const offset = i * size; |
||||
// Переставляем байты для каждого элемента
|
||||
const bytes = new Uint8Array(size); |
||||
for (let j = 0; j < size; j++) { |
||||
bytes[j] = view.getUint8(offset + j); |
||||
} |
||||
bytes.reverse(); |
||||
for (let j = 0; j < size; j++) { |
||||
view.setUint8(offset + j, bytes[j]); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return typedArray; |
||||
} |
||||
|
||||
/** |
||||
* Проверяет, является ли система little-endian |
||||
* @returns {boolean} |
||||
*/ |
||||
static isLittleEndian() { |
||||
const buffer = new ArrayBuffer(2); |
||||
const view = new DataView(buffer); |
||||
view.setUint16(0, 0x0001, true); |
||||
return view.getUint8(0) === 1; |
||||
} |
||||
} |
||||
|
||||
Loading…
Reference in new issue