как я могу увеличить точность без поплавков C - PullRequest
1 голос
/ 03 апреля 2012

Прямо сейчас я делаю следующее:

uint8_t ManualFlow = 40; // 0 -> 255     Unsigned Char

uint24_t ME; // 0 -> 16777215 Unsigned Short Long
ME = (uint24_t) ManualFlow*10; // Have to make this hack otherwise malfunction in calculation
ME /= 6;
ME *= (80 - 60);
ME /= 100;
ME *= 414;

Конечный результат:

40*10 = 400
400/6 = 66
66*20 = 1320
1320/100 = 13
13*414 = 5382

То, что я бы любил, похоже на это:

4/60 = 0,0667 * 20 * 4188 * 0,998 = 5576 (more accurate).

Как я могу сделать это более точно, не используя float s или double s, и самое главное, не увеличивайте размер моего кода слишком сильно.

С наилучшими пожеланиями Sonite

Ответы [ 6 ]

4 голосов
/ 03 апреля 2012

Возможно, вы захотите взглянуть на арифметику с фиксированной точкой:

http://en.wikipedia.org/wiki/Fixed-point_arithmetic

И Технический отчет 18037 от WG14 (последняя версия ISO / IEC TR 18037: 2008, к сожалению, не бесплатна):

http://www.open -std.org / ОТК1 / SC22 / WG14 / WWW / Docs / n1169.pdf

1 голос
/ 03 апреля 2012

Вы имеете в виду арифметику с фиксированной запятой (в отличие от плавающей запятой). Вы всегда можете увеличить размер ваших целых чисел до чего-то вроде uint64_t и умножить более чем на 10, чтобы достичь желаемой точности.

Однако я бы порекомендовал использовать фиксированную точку base-2 (то есть сдвиг влево на определенное количество битов, а не умножение на 10 до определенной степени). Это (намного) быстрее и точнее.

1 голос
/ 03 апреля 2012

Если вы уверены, что результат никогда не будет переполнен, выполните все операции умножения до деления:

uint24_t ME;
ME = (uint24_t)ManualFlow*10;
ME *= (80 - 60);
ME *= 414;
ME /= (6 * 14);

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

0 голосов
/ 04 мая 2012

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

uint16_t x = somevalue;  //somevalue known to be between 0 and 65535
x /= 107;

Вы можете использовать:

uint32_t x = somevalue;
x *= 39199;  //chosen to be 2^n/107
             //n chosen to maximum value without exceeding 65536
x >>= 22;    //n = 22 in this case

Примечание: это менее читаемый код, но если это алгоритм, где производительность критична, эту оптимизацию можно использовать (экономно).

0 голосов
/ 03 апреля 2012

вывод:

Мой исходный код имел не очень хорошую точность и был "таким большим".

Делая это ниже, я увеличил код на "38 байт" и получил лучшую точность

ME = (uint24_t) ManualFlow*100;
ME /= 6;
ME *= (Port[2].AD - Port[3].AD);
ME /= 100;
ME *= 414;
ME /= 10;

Лучшая точность, которую я получил, работая с фиксированной запятой, но он увеличил код до «1148 байт ->

// Utility macros for dealing with 16:16 fixed-point numbers
#define I2X(v) ((int32_t) ((v) * 65536.0 + 0.5))    // Int to Fix32
#define X2I(v) ((int16_t) ((v) + 0x8000 >> 16))     // Fix to Int

ME = I2X(ManualFlow*10); //400 * 65536.0 + 0.5 =   26214400
ME = I2X(ME/6); // 26214400 / 6 = 4369066
ME = I2X(ME * 20); // = 87381320
ME = I2X(ME / 100); // = 873813
ME = I2X(ME * 414); // 361758582
ME = X2I(ME); // 158FFF76 + 8000 >> 16 15907F76 >> 16 = 5520

Надеюсь, это поможет кому-то еще!

С наилучшими пожеланиями Sonite

0 голосов
/ 03 апреля 2012

Умножьте все свои входные данные, например, (1 << 8) на более крупные типы данных, затем выполните необходимые математические расчеты, а затем разделите ответ на (1 << 8). </p>

...