Как ефективно да изчислим модул в C на ръка?

Написах основен вариант с фиксирана запетая на синусоидалната функция, която използва справочна таблица (насочена към AVR микроконтролер без FPU). Моята реализация също така приема отрицателни стойности и стойности, които надвишават 2π, както прави висулката с плаваща запетая в math.h.

Така че трябва да картографирам дадената стойност в диапазон между 0 и 2π (т.е. техните аналогове с фиксирана точка). За положителни аргументи е лесно да ги отрежете с вградения в C оператор за остатък %. Тъй като това не е опция за отрицателни стойности, използвам следния (очевиден) подход:

    unsigned int modulus = (a - (INT_FLOOR(a / b) * b) + b) % b;

a и b са стойности с целочислен тип и INT_FLOOR() се има предвид само като намек, че дробната част на (a/b) е съкратена. Тази формула гарантира, че изчисленият модул (който се използва като индекс за масива от таблици) е винаги положителен и че също така отрицателните аргументи се картографират с положителните им двойници (поддържайки фазови измествания в двете посоки).

Проблемът ми с този подход е, че изглежда прекалено сложен, тъй като включва не по-малко от пет аритметични операции. Има ли по-ефективен подход, който пропускам?


person Developer's Destiny    schedule 03.05.2012    source източник
comment

Този въпрос е изключително стар във времето на интернет, но най-простият начин... е просто да си бъркате в Objective-C... Съжалявам.

Моят общ начин за подход към това е следният:

  • Можете да продължите да правите цялото си основно програмиране на C++, няма проблем.
  • Създайте ново "Cocoa приложение" в XCode.
  • В създателя на интерфейс (където редактирате вашия .xib файл), намерете NSOpenGLView обект в браузъра на обекти и го чукнете върху прозореца си.
  • Този бит е важен: В инспектора на обекти променете подкласа на ваш собствен подклас (това все още трябва да се направи). Можете да го кръстите както искате, например MyRenderer или нещо подобно.
  • Натиснете [cmd]+[n] и създайте нов клас Objective-C, MyRenderer.
  • Важно! Променете разширението от MyRenderer.m на MyRenderer.mm. По този начин XCode знае, че трябва да компилира за Objective-C++ вместо Objective-C.
  • In MyRenderer.mm, override at least the following methods of NSOpenGLView.
    • - (void) awakeFromNib: Do your class initialization here. Do not initialize OpenGL here, or your program will crash.
    • - (void) drawRect:(NSRect)dirtRect: Рисувайте тук.
    • - (void) prepareOpenGL: Инициализирайте OpenGL тук.
    • - (void) reshape:(NSRect)bounds: Когато изгледът се преоразмерява.

Добрата новина е, че можете свободно да смесвате C++ функции и класове в MyRenderer.mm. Можете също да използвате директиви C++ #include заедно с директиви Objective-C #import. Можете да правите C++ вътре в drawRect без проблеми.

Лошата новина е, че NSOpenGLView не преначертава сцената на всеки x милисекунди. Той ще преначертае сцената само след като сметне за необходимо. За това ще трябва да използвате класа NSTimer на Cocoa.

Редактиране: Допълнителна забележка: В метода drawRect не забравяйте да извикате glFlush(), както и [[self openGLContext] flushBuffer]. По някаква причина само извикването на [[self openGLContext] flushBuffer] не рисува нищо в изгледа за мен.

  -  person Daniel Fischer    schedule 04.05.2012
comment
Можете ли да използвате fmod, за да правите остатък от плаващи елементи? Работи правилно за отрицателни числа, както и за положителни.   -  person Sergey Kalinichenko    schedule 04.05.2012
comment
Изглежда, че искате по същество остатъка от абсолютните стойности, така че защо не го направите директно: unsigned in modulus = abs(a) % abs(b); Като алтернатива, нещо като: modulus = a % b; if (modulus < 0) modulus += b;   -  person Jerry Coffin    schedule 04.05.2012
comment
@DanielFischer: Благодаря ви много, пропуснах очевидното (което може да обясни защо някой маркира това като домашно). За съжаление, моят наивен подход все още изглежда по-доброто решение, тъй като полученият размер на кода е по-къс с 26 байта (avr-gcc -O2). dasblinkenlight: fmod е само за математика с плаваща запетая, което е нещо, което искам да избегна на всяка цена на тази архитектура JerryCoffin: използването на abs() тук и там увеличава размера на кода повече, отколкото използването на моя наивен подход, avr-gcc понякога прави странни неща   -  person Developer's Destiny    schedule 04.05.2012


Отговори (2)


Освен ако вашите целочислени аргументи не са мащабирани така, че да са от гледна точка на кратни на π (напр. 65536 означава 2π), опитът да направите намаляване на аргумента вероятно е погрешно насочен, тъй като 2π е ирационално и всяко намаляване на мода 2π ще въведе грешка, която мащабира с броят периоди, докато целият резултат от намалението стане грешка. Това всъщност е сериозен проблем в много реализации на тригонометри с плаваща запетая.

Бих препоръчал или изобщо да не правите намаляване на аргументите, или да използвате ъглова скала, базирана на степен две, а не на радиани (така че, например, 0x10000 или 0x1000000 съответства на 2π или 360 градуса). Тогава редуцирането на аргументи става една побитова операция.

person R.. GitHub STOP HELPING ICE    schedule 03.05.2012
comment
Наясно съм с натрупването на грешки при закръгляване. За щастие, те са в приемливи диапазони по отношение на задачите ми дори след няколкостотин кръга (не надхвърлям 1000 кръга и междинните резултати не са дълбоко вложени). Тъй като използвам справочни таблици на архитектури с (най-много) 65536 байта флаш памет, не виждам как мога да се измъкна, без да използвам намаляване на аргументите. Второто ти предложение обаче е много интересно. Това включва модификации на всички заобикалящи процедури, които използват тази синусова функция, но може много да повиши прецизността. Определено нещо, върху което трябва да помисля повече. - person Developer's Destiny; 04.05.2012
comment
Мисля, че ще увеличи много както прецизността, така и производителността. - person R.. GitHub STOP HELPING ICE; 04.05.2012

Виждам формулата ви правилна, макар и не толкова красива.

Ако направите b степен на 2, тогава някои операции могат да се извършват с битови маски. Нещо от рода на:

unsigned int modulus = (a - (a & ~(b-1)) + b) & (b-1);

И ако b е константа или макрос, трябва да се оптимизира доста.

person rodrigo    schedule 03.05.2012
comment
b наистина е макрос, но не е практично да се модифицират той или околните изчисления, за да се справят със степени на две (което води както до увеличаване на размера на кода, така и до загуба на точност). В моя случай b е броят на стъпките на времевото квантуване на моята приближена синусоидална крива. Трябва да бъде делител на моята версия с фиксирана запетая на 2π, така че да мога ефективно да картографирам ъгли с фиксирана запетая към съответната им стъпка на квантуване (която наистина включва само едно изместване на бита). Но все пак благодаря за подсказката. - person Developer's Destiny; 04.05.2012
comment
И затова старите училищни математически програми (например MS-DOS игрите) разделяха кръга на 2^x (512 или 256) двоични степени. Този стар обичай на 360 градуса няма приложение в компютрите. - person rodrigo; 04.05.2012