2015-08-05 4 views
2

У меня есть ряд временных рядов, каждый из которых содержит последовательность из 400 чисел, которые находятся близко друг к другу. У меня есть тысячи временных рядов; у каждого есть своя серия близких чисел.Серия сжатых чисел в C

TimeSeries1 = 184,56, 184,675, 184,55, 184,77, ...

TimeSeries2 = 145,73, 145,384, 145,96, 145,33, ...

TimeSeries3 = -126,48, -126,78, -126,55,. ..

Я могу хранить 8-байтовый двойной для каждого временного ряда, поэтому для большинства временных рядов я могу сжать каждый двойной бит в один байт, умножив его на 100 и выбрав дельту текущего значения и предыдущее значение. Вот мой компресс/распаковка код:

struct{ 
    double firstValue; 
    double nums[400]; 
    char compressedNums[400]; 
    int compressionOK; 
} timeSeries; 

void compress(void){ 
    timeSeries.firstValue = timeSeries.nums[0]; 
    double lastValue = timeSeries.firstValue; 
    for (int i = 1; i < 400; ++i){ 
     int delta = (int) ((timeSeries.nums[i] * 100) - (lastValue* 100)); 

     timeSeries.compressionOK = 1; 
     if (delta > CHAR_MAX || delta < -CHAR_MAX){ 
      timeSeries.compressionOK = 0; 
      return; 
     } 
     else{  
      timeSeries.compressedNums[i] = (char) delta; 
      lastValue = timeSeries.nums[i]; 
     } 
    } 
} 

double decompressedNums[400];  

void decompress(void){ 
    if (timeSeries.compressionOK){ 
     double lastValue = timeSeries.firstValue; 

     for (int i = 1; i < 400; ++i){ 
      decompressedNums[i] = lastValue + timeSeries.compressedNums[i]/100.0; 
      lastValue = decompressedNums[i]; 
     } 
    } 
} 

Я могу терпеть некоторые lossiness, порядка .005 на число. Тем не менее, я получаю больше потерь, чем могу терпеть, тем более, что прецизионная потеря в одной из сжатых серий переносится вперед и вызывает увеличение потерь.

Так что мои вопросы:

  1. Есть ли что-то можно изменить, чтобы уменьшить lossiness?
  2. Есть ли вообще другой метод сжатия, который сопоставим или лучше, чем это соотношение 8 к 1?
+0

Возможно, вы должны использовать явный 'подписанный символ сжатыйNums [400];' поскольку простой символ может быть подписан или без знака. –

+0

Обратите внимание, что если разница между последовательными значениями больше ± 1,27, ваша схема сжатия не будет работать. Очевидно, вы можете заметить это при сжатии, и вам нужно будет знать, как его обрабатывать. Вместо этого вы могли бы использовать «короткий», который позволил бы ± 320 между последовательными значениями (с разрешением 0,01 или ± 32 с разрешением 0,001). Если дельта между последовательными значениями достаточно мала, вы должны приблизиться к совершенному поведению. –

ответ

0

Перед тем, как преобразовать его в int, необходимо округлить значения, чтобы избежать проблем, как в этом коде.

#include <limits.h> 
#include <math.h> 
#include <stdio.h> 
#include <stdlib.h> 

enum { TS_SIZE = 400 }; 

typedef struct 
{ 
    double firstValue; 
    double nums[TS_SIZE]; 
    signed char compressedNums[TS_SIZE]; 
    int compressionOK; 
} timeSeries; 

static 
void compress(timeSeries *t1) 
{ 
    t1->firstValue = t1->nums[0]; 
    double lastValue = t1->firstValue; 
    for (int i = 1; i < TS_SIZE; ++i) 
    { 
     int delta = (int) round((t1->nums[i] - lastValue) * 100.0); 

     t1->compressionOK = 1; 
     if (delta > CHAR_MAX || delta < -CHAR_MAX) 
     { 
      printf("Delta too big: %d (%.3f) vs %d (%.3f) = delta %.3f\n", 
        i-1, t1->nums[i-1], i, t1->nums[i], t1->nums[i] - t1->nums[i-1]); 
      t1->compressionOK = 0; 
      return; 
     } 
     else 
     { 
      t1->compressedNums[i] = (char) delta; 
      lastValue = t1->nums[i]; 
     } 
    } 
} 

static 
void decompress(timeSeries *t1) 
{ 
    if (t1->compressionOK) 
    { 
     double lastValue = t1->firstValue; 

     for (int i = 1; i < TS_SIZE; ++i) 
     { 
      t1->nums[i] = lastValue + t1->compressedNums[i]/100.0; 
      lastValue = t1->nums[i]; 
     } 
    } 
} 

static void compare(const timeSeries *t0, const timeSeries *t1) 
{ 
    for (int i = 0; i < TS_SIZE; i++) 
    { 
     char c = (fabs(t0->nums[i] - t1->nums[i]) > 0.005) ? '!' : ' '; 
     printf("%c %03d: %.3f vs %.3f = %+.3f\n", c, i, t0->nums[i], t1->nums[i], t0->nums[i] - t1->nums[i]); 
    } 
} 

int main(void) 
{ 
    timeSeries t1; 
    timeSeries t0; 
    int i; 

    for (i = 0; i < TS_SIZE; i++) 
    { 
     if (scanf("%lf", &t0.nums[i]) != 1) 
      break; 
    } 
    if (i != TS_SIZE) 
    { 
     printf("Reading problems\n"); 
     return 1; 
    } 

    t1 = t0; 

    for (i = 0; i < 10; i++) 
    { 
     printf("Cycle %d:\n", i+1); 
     compress(&t1); 
     decompress(&t1); 
     compare(&t0, &t1); 
    } 
    return 0; 
} 

со следующими данными (полученных из целых чисел в диапазоне 18456..18855, деленная на 100 и случайным образом, возмущенного небольшим количеством (около 0,3%, чтобы сохранить значения достаточно близко друг к другу), я получил то же самое данные снова и снова, для всех 10 циклов сжатия и декомпрессии.

184.60 184.80 184.25 184.62 184.49 184.94 184.95 184.39 184.50 184.96 
184.54 184.72 184.84 185.02 184.83 185.01 184.43 185.00 184.74 184.88 
185.04 184.79 184.55 184.94 185.07 184.60 184.55 184.57 184.95 185.07 
184.61 184.57 184.57 184.98 185.24 185.11 184.89 184.72 184.77 185.29 
184.98 184.91 184.76 184.89 185.26 184.94 185.09 184.68 184.69 185.04 
185.39 185.05 185.41 185.41 184.74 184.77 185.16 184.84 185.31 184.90 
185.18 185.15 185.03 185.41 185.18 185.25 185.01 185.31 185.36 185.29 
185.62 185.48 185.40 185.15 185.29 185.19 185.32 185.60 185.39 185.22 
185.66 185.48 185.53 185.59 185.27 185.69 185.29 185.70 185.77 185.40 
185.41 185.23 185.84 185.30 185.70 185.18 185.68 185.43 185.45 185.71 
185.60 185.82 185.92 185.40 185.85 185.65 185.92 185.80 185.60 185.57 
185.64 185.39 185.48 185.36 185.69 185.76 185.45 185.72 185.47 186.04 
185.81 185.80 185.94 185.64 186.09 185.95 186.03 185.55 185.65 185.75 
186.03 186.02 186.24 186.19 185.62 186.13 185.98 185.84 185.83 186.19 
186.17 185.80 186.15 186.10 186.32 186.25 186.09 186.20 186.06 185.80 
186.02 186.40 186.26 186.15 186.35 185.90 185.98 186.19 186.15 185.84 
186.34 186.20 186.41 185.93 185.97 186.46 185.92 186.19 186.15 186.32 
186.06 186.25 186.47 186.56 186.47 186.33 186.55 185.98 186.36 186.35 
186.65 186.60 186.52 186.13 186.39 186.55 186.50 186.45 186.29 186.24 
186.81 186.61 186.80 186.60 186.75 186.83 186.86 186.35 186.34 186.53 
186.60 186.69 186.32 186.23 186.39 186.71 186.65 186.37 186.37 186.54 
186.81 186.84 186.78 186.50 186.47 186.44 186.36 186.59 186.87 186.70 
186.90 186.47 186.50 186.74 186.80 186.86 186.72 186.63 186.78 186.52 
187.22 186.71 186.56 186.90 186.95 186.67 186.79 186.99 186.85 187.03 
187.04 186.89 187.19 187.33 187.09 186.92 187.35 187.29 187.04 187.00 
186.79 187.32 186.94 187.07 186.92 187.06 187.39 187.20 187.35 186.78 
187.47 187.54 187.33 187.07 187.39 186.97 187.48 187.10 187.52 187.55 
187.06 187.24 187.28 186.92 187.60 187.05 186.95 187.26 187.08 187.35 
187.24 187.66 187.57 187.75 187.15 187.08 187.55 187.30 187.17 187.17 
187.13 187.14 187.40 187.71 187.64 187.32 187.42 187.19 187.40 187.66 
187.93 187.27 187.44 187.35 187.34 187.54 187.70 187.62 187.99 187.97 
187.51 187.36 187.82 187.75 187.56 187.53 187.38 187.91 187.63 187.51 
187.39 187.54 187.69 187.84 188.16 187.61 188.03 188.06 187.53 187.51 
187.93 188.04 187.77 187.69 188.03 187.81 188.04 187.82 188.14 187.96 
188.05 187.63 188.35 187.65 188.00 188.27 188.20 188.21 187.81 188.04 
187.87 187.96 188.18 187.98 188.46 187.89 187.77 188.18 187.83 188.03 
188.48 188.09 187.82 187.90 188.40 188.32 188.33 188.29 188.58 188.53 
187.88 188.32 188.57 188.14 188.02 188.25 188.62 188.43 188.19 188.54 
188.20 188.06 188.31 188.19 188.48 188.44 188.69 188.63 188.34 188.76 
188.32 188.82 188.45 188.34 188.44 188.25 188.39 188.83 188.49 188.18 

До тех пор, пока я не поместит округление, значения будут быстро дрейфовать друг от друга.


Если у вас нет round() - который был добавлен к стандартной C в стандарте C99 - то вы можете использовать эти строки вместо round():

int delta; 
if (t1->nums[i] > lastValue) 
    delta = (int) (((t1->nums[i] - lastValue) * 100.0) + 0.5); 
else 
    delta = (int) (((t1->nums[i] - lastValue) * 100.0) - 0.5); 

Это округляет правильно для положительных и отрицательные значения. Вы также можете разделить это на функцию; в C99 вы можете сделать функцию inline, но если это сработает, у вас также будет функция round() в библиотеке. Сначала я использовал этот код, прежде чем переключиться на функцию round().

+0

Это работает. К сожалению, я все еще на VS2012, у которого нет круглых функций. Но я достиг этого же: int val1 = (int) (currentValue * 100); int val2 = (int) (lastValue * 100); затем delta = val1 - val2. – PaeneInsula

2

Вы можете избежать медленного дрейфа в точности, выработав дельта не от точного значения предыдущего элемента, а скорее из вычисленного приближения предыдущего элемента (т. Е. Суммы дельт). Таким образом, вы всегда получите ближайшее приближение к следующему значению.

Лично я использовал бы целую арифметику для этой цели, но, вероятно, это будет хорошо с арифметикой с плавающей запятой, так как плавающая точка воспроизводится, даже если не точная.

1

Посмотрите на значения, как хранимые в памяти:

184. == 0x4067000000000000ull 
184.56 == 0x406711eb851eb852ull 

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

Для целых дельт, умножьте на 128 вместо 100, это даст вам 7 бит дробной части. Если дельта слишком велика для одного байта, используйте трехбайтную последовательность {0x80, hi_delta, lo_delta}, поэтому для 0x80 используется специальный индикатор. Если дельта оказалась -128, то это будет {0x80, 0xff, 0x80}.

Смежные вопросы