Простыми словами о представлении форматов float32 и double64 в памяти компьютера

Форматы float32 и double64 на пальцах

Вкратце, идея довольна простая: исходное число необходимо привести к нормализованному виду 1.NNN2 в двоичной системе счисления с помощью битового сдвига, затем записать дробную часть этого числа в мантиссу, а количество сдвигов в экспоненту. И завершающим штрихом сохранить знак исходного числа.

Формат float32 имеет такой вид: 1s8e23m, где s — количество бит под знак, e — экспонента, m — мантисса. Для формата double64 вид такой — 1s11e52m.

Первый бит кодирует знак числа. Если это ноль, число положительно, в противном случае число отрицательное.

Затем идут восемь бит экспоненты. Если совсем простыми словами, то экспонента — это количество сдвигов запятой в исходном числе, представленном в двоичном виде, для получения нормализованного числа вида 1.NNN2. Чтобы получить количество сдвигов из этих восьми бит надо отнять 12710. Отрицательная экспонента — сдвиг влево, положительная — сдвиг вправо.

После экспоненты следуют 23 бита мантиссы. Это дробная часть числа 1.NNN2.

А теперь практическая часть!

Перевод десятичной дроби во float32

Попробуем с полученными знаниями закодировать число -2.62510.

В двоичном виде это число имеет вид 10.1012. Для получения нормализованного числа необходимо запятую сдвинуть влево на один разряд. Получим число 1.01012 и экспоненту равной 1.

Для сохранения экспоненты к ней надо прибавить 12710. Получится 12610 или 011111102 в двоичном виде.

Берем нормализованное число 1.01012 и выделяем мантиссу 01012. Для сохранения этого числа, которое занимает 4 бита, надо добавить нули справа до 23 бит. Получится число 010100000000000000000002.

Знак числа отрицательный, значит первый бит равен единице.

Итог: s=12 e=011111102 m=010100000000000000000002, с чем я вас и поздравляю.

Обратный перевод: из float32 в число

Разберем пример из википедии. Есть число во float32 0xC000000016. В двоичной системе это будет 110000000000000000000000000000002.

Разобъем его на компоненты: s=12 e=100000002 m=000000000000000000000002.

Мантисса равна нулю, но, как уже было сказано выше, сохраняется только дробная часть мантиссы, а единица отбрасывается. Значит мантисса равна 1.000000000000000000000002.

Экспонента равна 100000002 или 12810, отнимаем 12710 и получается, что экспонента равна единице.

Возьмем мантиссу и сдвинем точку вправо на эту единицу, получится 10.00000000000000000000002, это 210 в десятичной системе счисления.

Знак числа равен единице, значит исходное число отрицательное.

Решение: -210, что и требовалось доказать.

P.S. Маленькие циферки справа от числа обозначают систему счисления, если кто не знает. Пример: два в десятичной системе — 210, один‑ноль‑один в двоичной системе — 1012. Кстати, красным цветом выделен знак числа, зелёным — экспонента, а мантисса, соответственно, синим.

Полезные ссылки

Решение проблемы с нехваткой памяти при загрузке текстур в Android

Как известно, в популярной нынче мобильной ОС Android есть фатальный недостаток — это язык Java. Более того, каждому приложению выделяется кусок памяти размером от 32-х мегабайт до бесконечности в зависимости от желания производителя мобильного телефона.

Если вы делаете большое приложение с использованием OpenGLES и пачкой больших текстур размером 2048×2048 (а это 16 мегабайт для формата RGBA), то памяти для загрузки у вас не будет. После загрузки второй-третьей, пятой текстуры с помощью BitmapFactory приложение упадет с OutOfMemoryError, что есть очень плохо.

Хочу поделиться решением этой проблемы. Она состоит из двух пунктов: загружать изображения из PNG с помощью libpng и исключить работу с большими блоками данных из Java части.

Решение первой части и исходники для прямой загрузки изображения из PNG файла можно найти на stackoverflow.com.

Для работы с памятью создадим вот такой Java класс:

package com.example.memory;
 
import java.nio.IntBuffer;
 
public class MemoryManager {
    public static native IntBuffer create(int sizeInBytes);
 
 
    public static native void release(IntBuffer buffer);
}

И нативную часть (язык cpp):

#include <jni.h>
 
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jobject JNICALL Java_com_example_memory_MemoryManager_create(
    JNIEnv *env,
    jclass cls,
    jint sizeInBytes)
{
    // Выделяем блок памяти
    void *buf = malloc(sizeInBytes);
    // Создаем буфер 
    return env->NewDirectByteBuffer(buf, 0);
}
 
JNIEXPORT void JNICALL Java_com_example_memory_MemoryManager_release(
    JNIEnv *env,
    jclass cls,
    jobject buffer)
{
    // Освобождаем память
    free(env->GetDirectBufferAddress(buffer));
}
#ifdef __cplusplus
}
#endif

Далее алгоритм работы такой:

  1. Читаем высоту и ширины изображения из первых 24 байт PNG (подробности в статье на хабре: Получаем тип и размеры изображения без скачивания его целиком, используя Python)
  2. Выделяем IntBuffer с помощью MemoryManager размером W*H*4 (32bit RGBA, 4 байта на пиксель)
  3. Считываем в него PNG файл
  4. Загружаем эти данные в текстуру с помощью glTexImage2d
  5. Освобождаем IntBuffer в MemoryManager

Честно говоря, я слабо понимаю — почему в нативе вызывается функция NewDirectByteBuffer, а в Java приходит IntBuffer, а не ByteBuffer. Загадка какая-то.

UPD: Если вы решили пойти стандартным путем с загрузкой Bitmap и GLUtil.texImage2D, то не забывайте вызывать bitmap.recycle() после загрузки изображения в текстуру, дабы освободить нативную память.

Самая маленькая программа на C/C++, которая вызывает segmentation fault

Самый маленький код, который после компиляции с помощью gcc вызывает segmentation fault:

main;

Компилируем:

$ gcc crash.c
crash.c:1: warning: data definition has no type or storage class

Запускаем:

$ ./a.out
Segmentation fault

Увидел не так давно на хабре, делюсь.

Что такое программирование простыми словами

Программист: ну представь, что ты писатель и поддерживаешь проект «Война и мир». У тебя ТЗ — написать главу как Наташа Ростова гуляла под дождём по парку. Ты пишешь «шёл дождь», сохраняешь, вылетает сообщение об ошибке «Наташа Ростова умерла, продолжение невозможно». Почему умерла? Начинаешь разбираться. Выясняется, что у Пьера Безухова скользкие туфли, он упал, его пистолет ударился о землю и выстрелил в столб, а пуля от столба срикошетила в Наташу. Что делать? Зарядить пистолет холостыми? Поменять туфли? Решили убрать столб. Получаем сообщение «Поручик Ржевский умер.» Выясняется, что он в следующей главе облокачивается о столб, которого уже нет…

Автор, к сожалению, неизвестен. Найдено на просторах интернета.

Билинейная фильтрация на JavaScript

Билинейная фильтрация — это способ масштабирования изображения с относительно хорошим качеством. Для каждого пикселя нового изображения выбирается четыре пикселя из старого и хитрым образом интерполируются между собой. Подробнее можно прочитать в википедии.

Давно хочу написать длинную и подробную статью про билинейную фильтрацию с помощью NEON, но как-то все не нахожу времени и желания. Поэтому решил начать с малого — реализация алгоритма на JavaScript.

(далее…)

Блог Евгения Жирнова