2014-01-28 6 views
1

Существуют специальные форматы (base-128), предназначенные для передачи целых чисел, используемых в protobufs и elsewhere. Они выгодны, когда большинство целых чисел являются небольшими (для каждого из них требуется один байт для наименьших чисел и может отбрасывать один байт для других).Компактный формат для чисел с плавающей запятой

Интересно, есть ли что-то подобное для чисел с плавающей запятой в предположении, что большинство из них на самом деле являются малыми целыми числами?


Для решения ответ Алиса: Я думал о чем-то вроде

void putCompressedDouble(double x) { 
    int n = (int) x; 
    boolean fits = (n == x); 
    putBoolean(fits); 
    if (fits) { 
     putCompressedInt(n); 
    } else { 
     putUncompressedLong(Double.doubleToLongBits(x)); 
    } 
} 

Это работает (для отрицательного нуля, что я на самом деле не волнует, кроме), но это расточительный в случае fits == true.

ответ

1

Это зависит от объема ваших номеров. Величина не имеет большого значения, поскольку она выражается через поле экспоненты поплавка. Обычно это мантисса, которая способствует наибольшему «весу» с точки зрения хранения.

Если поплавки в основном целые числа, вы можете получить что-то путем преобразования в целое (через Float.floatToIntBits()), и проверить, сколько хвостовых нулей Есть (для небольших ИНТА значений должны быть до 23 завершающих нули). При использовании простой схемы для кодирования небольших Int, вы можете осуществить кодирование поплавки просто:

int raw = Float.floatToIntBits(f); 
raw = Integer.reverse(raw); 
encodeAsInt(raw); 

(Декодирование просто задним ходом процесса). То, что это делает, просто перемещает конечные нули в мантиссе к самым значительным битам представления int, что является дружественным к схемам кодирования, разработанным для небольших целых чисел.

То же самое можно применять для двойного < -> long.

+0

Я принимаю ваше решение, так как это красиво и просто. В моем собственном ответе я даю результаты (лучшее сжатие может быть достигнуто, но это намного сложнее). – maaartinus

0

Наверное, нет, и это почти наверняка не то, что вы хотите.

Как указано at this stack overflow post, числа с плавающей точкой не сохраняются независимым от платформы способом в буферах протоколов; они по существу являются битами для представлений бит, которые затем сбрасываются с использованием объединения. Это означает, что float будет принимать 4 байта и удваивает 8 байтов. Это почти наверняка, что вы хотите.

Почему? Плавающие точки не являются целыми числами. Целые числа представляют собой хорошо сформированную группу; каждое число действительно, каждый бит-шаблон представляет собой число, и они точно представляют собой целое число, которое они представляют. Плавающие точки не могут представлять много важных чисел точно: большинство float не могут точно представлять 0,1, например. Проблема бесконечностей, NAN и т. Д. И т. Д., Все делают сжатый формат нетривиальной задачей.

Если у вас есть маленькие целые числа в поплавке, то затем преобразуйте их в маленькие целые числа или в формат точности фиксированной точки. Например, если вы знаете, что у вас есть только ... 4 sigfigs, вы можете конвертировать из плавающей точки в короткую точку с фиксированной точкой, сохраняя 2 байта. Просто убедитесь, что каждый конец знает, как бороться с этим типом, и вы будете золотыми.

Но любая операция, которую Google может сделать, чтобы попытаться сэкономить место в этом случае, будет одновременно изобретать колесо и потенциально опасно. Вероятно, поэтому они стараются не путать с поплавками вообще.

+0

У меня нет гарантии, что мои 'double' являются целыми числами. Я знаю, что большинство из них есть, и они вписываются в два байта, но также должны обрабатывать общий случай. Я обновил mu вопрос. – maaartinus

0

Мне очень нравится решение Дюрандаля. Несмотря на свою простоту, он работает довольно хорошо, по крайней мере, для float. Для double s с показателем более одного байта может потребоваться дополнительная дополнительная перестановка бит. В следующей таблице приведена длина кодирования для цифр с номерами до D, также считаются отрицательные числа. В каждом столбце первое число задает максимальные байты, необходимые, в то время как число в скобках является средним. были испытаны

D AS_INT REV_FLOAT REV_DOUBLE BEST 
1: 1 (1.0) 2 (1.8) 3 (2.2) 1 (1.0) 
2: 2 (1.4) 3 (2.4) 3 (2.8) 2 (1.7) 
3: 2 (1.9) 3 (2.9) 4 (3.2) 2 (2.0) 
4: 3 (2.2) 4 (3.3) 4 (3.8) 3 (2.6) 
5: 3 (2.9) 4 (3.9) 5 (4.1) 3 (3.0) 
6: 3 (3.0) 5 (4.2) 5 (4.8) 4 (3.5) 
7: 4 (3.9) 5 (4.8) 6 (5.1) 4 (3.9) 
8: 4 (4.0) 5 (4.9) 6 (5.8) 5 (4.3) 
9: 5 (4.9) 5 (4.9) 6 (6.0) 5 (4.9) 

Четыре различные методы:

  • AS_INT: Просто преобразовать число в int. Это непригодно, но дает нам нижнюю границу.
  • REV_FLOAT: метод Durandal применяется к float s.
  • REV_DOUBLE: метод Durandal применяется к double.
  • BEST: Улучшение моего собственного метода, как описано в вопросе. Довольно сложно.
Смежные вопросы