Ti-84 + CE Программа обратной польской нотации

Несколько недель назад я написал программу обратной польской записи для своего калькулятора Ti-84 + CE. Это была преемница программы, которую я написал на TI-BASIC некоторое время назад, но поскольку у нее было слишком много входной задержки, чтобы ее можно было использовать, я решил написать ее на C и скомпилировать ее на родную сборку z80.

Я написал эту программу за ночь до соревнований, на которых собирался ее использовать (на самом деле, я нанес последние штрихи буквально за несколько секунд до начала самого экзамена!), Поэтому имейте в виду, что у меня было нехватка времени, поэтому я не знал Я не собираюсь использовать максимально чистую кодовую базу. Тем не менее, поскольку я не очень разбираюсь в C, я хотел бы знать, какие улучшения я мог бы внести в код.

Вот демонстрационный GIF-файл программы, используемой для поиска ответа на примерный вопрос о вычислителе чисел:

Ti-84 + CE Программа обратной польской нотации

Я опубликовал репозиторий git с программой по адресу https://github.com/arjvik/RPN-Ti84

Это код:

#include <tice.h>

real_t stack[99];
char buffer[50];
uint8_t idx;
bool decimal;
bool negative;
bool constantsmode = false;
bool scimode = true;
bool radians = true;
real_t decimalfactor;

real_t r_0, r_1, r_2, r_3, r_4, r_5, r_6, r_7, r_8, r_9, r_10, r_ln10, r_pi, r_e;

void init_real_constants() {
    r_0  = os_Int24ToReal(0);
    r_1  = os_Int24ToReal(1);
    r_2  = os_Int24ToReal(2);
    r_3  = os_Int24ToReal(3);
    r_4  = os_Int24ToReal(4);
    r_5  = os_Int24ToReal(5);
    r_6  = os_Int24ToReal(6);
    r_7  = os_Int24ToReal(7);
    r_8  = os_Int24ToReal(8);
    r_9  = os_Int24ToReal(9);
    r_10 = os_Int24ToReal(10);
    r_ln10 = os_FloatToReal(2.30258509299);
    r_pi   = os_FloatToReal(3.14159265359);
    r_e    = os_FloatToReal(2.71828182846);
}

void draw_line_clear(bool clear) {
    os_RealToStr(buffer, &stack[idx], 0, 1, -1);
    if (clear) {
        os_SetCursorPos(9, 0);
        os_PutStrFull("               ");
    }
    os_SetCursorPos(9, 0);
    os_PutStrFull(buffer);
}

#define OVERDRAW_IS_REDRAW 0
#if OVERDRAW_IS_REDRAW
void draw_line() {
    draw_line_clear(true);
}
#else
void draw_line() {
    draw_line_clear(false);
}
#endif

void drawdecimal_line() {
    os_SetCursorPos(9, 0);
    os_PutStrFull(buffer);
    os_PutStrFull(".");
}

void draw_stack_clear(uint8_t row, bool clear) {
    if (row >= 9) {
        os_SetCursorPos(8, 0);
        os_PutStrFull("...            ");
        real_t len = os_Int24ToReal((int24_t) idx);
        os_RealToStr(buffer, &len, 0, 1, -1);
        os_SetCursorPos(8, 4);
        os_PutStrFull(buffer);
    } else {
        if (scimode) {
            os_RealToStr(buffer, &stack[row], 0, 2, 2);
        } else {
            os_RealToStr(buffer, &stack[row], 0, 1, -1);
        }
        if (clear) {
            os_SetCursorPos(row, 0);
            os_PutStrFull("               ");
        }
        os_SetCursorPos(row, 0);
        os_PutStrFull(buffer);
    }
}

void draw_stack(uint8_t row) {
    draw_stack_clear(row, false);
}

void draw_full_stack() {
    for (uint8_t row = 0; row < idx && row <= 9; row++)
        draw_stack_clear(row, true);
}

void delete_stack(uint8_t row) {
    if (row < 9) {
        os_SetCursorPos(row, 0);
        os_PutStrFull("               ");
    }
}

void new_entry() {
    decimal = false;
    negative = false;
    stack[idx] = r_0;
    draw_line_clear(true);

}

void new_problem() {
    idx = 0;
    os_ClrHome();
    buffer[0] = 0;
    constantsmode = false;
    new_entry();
}

#define BINARY_OP(os_func)                                              
do {                                                                    
    if (os_RealCompare(&stack[idx], &r_0) != 0) {                       
        if (idx >= 1) {                                                 
            stack[idx-1] = os_func(&stack[idx-1], &stack[idx]);         
            draw_stack_clear(idx-1, true);                              
            new_entry();                                                
        }                                                               
    } else {                                                            
        if (idx >= 2) {                                                 
            stack[idx-2] = os_func(&stack[idx-2], &stack[idx-1]);       
            draw_stack_clear(idx-2, true);                              
            delete_stack(idx-1);                                        
            idx--;                                                      
            new_entry();                                                
        }                                                               
    }                                                                   
} while (false);

#define UNARY_OP(os_func)                                               
do {                                                                    
    if (os_RealCompare(&stack[idx], &r_0) != 0) {                       
        stack[idx] = os_func(&stack[idx]);                              
        draw_line_clear(true);                                          
    } else {                                                            
        if (idx >= 1) {                                                 
            stack[idx-1] = os_func(&stack[idx-1]);                      
            draw_stack_clear(idx-1, true);                              
            new_entry();                                                
        }                                                               
    }                                                                   
} while (false);

#define REAL_TRIG(name, os_func)                                        
real_t name(real_t *a) {                                                
    real_t t;                                                           
    if (radians)                                                        
        t = *a;                                                         
    else                                                                
        t = os_RealDegToRad(a);                                         
    return os_func(&t);                                                 
}

REAL_TRIG(degRadSin, os_RealSinRad)
REAL_TRIG(degRadCos, os_RealCosRad)
REAL_TRIG(degRadTan, os_RealTanRad)

#define REAL_INVTRIG(name, os_func)                                     
real_t name(real_t *a) {                                                
    real_t t = os_func(a);                                              
    if (!radians)                                                       
        t = os_RealRadToDeg(&t);                                        
    return t;                                                           
}

REAL_INVTRIG(radDegAsin, os_RealAsinRad)
REAL_INVTRIG(radDegAcos, os_RealAcosRad)
REAL_INVTRIG(radDegAtan, os_RealAtanRad)

real_t realLogBase10(real_t *a) {
    real_t t = os_RealLog(a);
    return os_RealDiv(&t, &r_ln10);
}

real_t realSquare(real_t *a) {
    return os_RealMul(a, a);
}

void main() {
    uint8_t key;
    
    init_real_constants();
    new_problem();
    
    while ((key = os_GetCSC()) != sk_Graph) {
        if (constantsmode) {
            if (key == sk_Power) {
                stack[idx] = r_pi;
                constantsmode = false;
                draw_line_clear(true);
            } else if (key == sk_Div) {
                stack[idx] = r_e;
                constantsmode = false;
                draw_line_clear(true);
            } else if (key == sk_2nd) {
                constantsmode = false;
            } else if (key == sk_Del) {
                new_problem();
            }
        } else {
            if (key == sk_0 || key == sk_1 || key == sk_2 || key == sk_3 || key == sk_4 ||
                key == sk_5 || key == sk_6 || key == sk_7 || key == sk_8 || key == sk_9 ) {
                if (!decimal) {
                    stack[idx] = os_RealMul(&stack[idx], &r_10);
                    real_t toAdd = r_0;
                    if (key == sk_1) toAdd = r_1;
                    if (key == sk_2) toAdd = r_2;
                    if (key == sk_3) toAdd = r_3;
                    if (key == sk_4) toAdd = r_4;
                    if (key == sk_5) toAdd = r_5;
                    if (key == sk_6) toAdd = r_6;
                    if (key == sk_7) toAdd = r_7;
                    if (key == sk_8) toAdd = r_8;
                    if (key == sk_9) toAdd = r_9;
                    if (!negative)
                        stack[idx] = os_RealAdd(&stack[idx], &toAdd);
                    else
                        stack[idx] = os_RealSub(&stack[idx], &toAdd);
                    draw_line();
                } else {
                    real_t toAdd = r_0;
                    if (key == sk_1) toAdd = r_1;
                    if (key == sk_2) toAdd = r_2;
                    if (key == sk_3) toAdd = r_3;
                    if (key == sk_4) toAdd = r_4;
                    if (key == sk_5) toAdd = r_5;
                    if (key == sk_6) toAdd = r_6;
                    if (key == sk_7) toAdd = r_7;
                    if (key == sk_8) toAdd = r_8;
                    if (key == sk_9) toAdd = r_9;
                    toAdd = os_RealMul(&toAdd, &decimalfactor);
                    if (!negative)
                        stack[idx] = os_RealAdd(&stack[idx], &toAdd);
                    else
                        stack[idx] = os_RealSub(&stack[idx], &toAdd);
                    decimalfactor = os_RealDiv(&decimalfactor, &r_10);
                        
                    draw_line_clear(true);
                }
            } else if (key == sk_Chs) {
                stack[idx] = os_RealNeg(&stack[idx]);
                negative = !negative;
                draw_line_clear(true);
            } else if (key == sk_DecPnt) {
                if (!decimal) {
                    decimal = true;
                    decimalfactor = os_RealDiv(&r_1, &r_10);
                    drawdecimal_line();
                }
            } else if (key == sk_Clear) {
                new_entry();
            } else if (key == sk_Left) {
                if (negative) os_RealNeg(&stack[idx]);
                if (!decimal) {
                    stack[idx] = os_RealDiv(&stack[idx], &r_10);
                } else decimal = false;
                stack[idx] = os_RealFloor(&stack[idx]);
                if (negative) os_RealNeg(&stack[idx]);
                draw_line_clear(true);
            } else if (key == sk_Enter) {
                if (idx == 98) {
                    new_problem();
                } else {
                    draw_stack(idx++);
                    new_entry();
                }
            } else if (key == sk_Mode) {
                scimode = !scimode;
                draw_full_stack();
            } else if (key == sk_Stat) {
                radians = !radians;
                os_SetCursorPos(9, 0);
                os_PutStrFull(radians ? "r" : "d");
            }else if (key == sk_Del) {
                new_problem();
            } else if (key == sk_Add) {
                BINARY_OP(os_RealAdd);
            } else if (key == sk_Sub) {
                BINARY_OP(os_RealSub);
            } else if (key == sk_Mul) {
                BINARY_OP(os_RealMul);
            } else if (key == sk_Div) {
                BINARY_OP(os_RealDiv);
            } else if (key == sk_Power) {
                BINARY_OP(os_RealPow);
            } else if (key == sk_Log) {
                UNARY_OP(realLogBase10);
            } else if (key == sk_Ln) {
                UNARY_OP(os_RealLog);
            } else if (key == sk_Sin) {
                UNARY_OP(degRadSin);
            } else if (key == sk_Cos) {
                UNARY_OP(degRadCos);
            } else if (key == sk_Tan) {
                UNARY_OP(degRadTan);
            } else if (key == sk_Apps) {
                UNARY_OP(radDegAsin);
            } else if (key == sk_Prgm) {
                UNARY_OP(radDegAcos);
            } else if (key == sk_Vars) {
                UNARY_OP(radDegAtan);
            } else if (key == sk_Square) {
                UNARY_OP(realSquare);
            } else if (key == sk_Recip) {
                UNARY_OP(os_RealInv);
            } else if (key == sk_2nd) {
                constantsmode = true;
            } else if (key == sk_Yequ) {
                os_ClrHome();
                os_SetCursorPos(0, 0);
                os_PutStrFull("Arjun's RPN Calculator");
                os_SetCursorPos(1, 0);
                os_PutStrFull("v2.0 (ASM)");
                os_SetCursorPos(3, 0);
                os_PutStrFull("git.io/ti84rpn");
                while (os_GetCSC() == 0);
                os_ClrHome();
                draw_full_stack();
                draw_line_clear(true);
            }
        }
    }
}

В tice.h Я использовал это из CE-программирование / набор инструментов

1 ответ
1

Вместо

#define OVERDRAW_IS_REDRAW 0
#if OVERDRAW_IS_REDRAW
void draw_line() {
    draw_line_clear(true);
}
#else
void draw_line() {
    draw_line_clear(false);
}
#endif

почему нет

#define OVERDRAW_IS_REDRAW false

void draw_line() {
    draw_line_clear(OVERDRAW_IS_REDRAW);
}

?

В противном случае: мне не совсем понятны возможности вашего компилятора z80, но

скомпилировать его в родную сборку z80

вероятно не то, что происходит. Вы, вероятно, компилируете на нативный z80 Машинный код и больше заботиться об этом машинном коде, чем о сборке. В зависимости от вашего компилятора он может выдавать листинг сборки в качестве промежуточного шага; в gcc это было бы что-то вроде gcc -Wa,-al.

BINARY_OP и UNARY_OP немного неудобно. Они собираются раздуть размер вашего main который уже слишком велик. Вместо этого рассмотрите возможность преобразования их в простые старые функции, которые принимают указатель на функцию для os_func. Помимо прочего, это сократит ваш окончательный размер двоичного файла. Вряд ли накладные расходы на вызовы будут настолько обременительными, чтобы быть заметными, но проверьте это.

я вижу твой while (false) шаблон также отражается в заголовке сторонней цепочки инструментов, которую вы используете. Почему? Если вам просто нужен блок, что разумно, оставьте {} и брось do/while(false). Блоки с анонимной областью видимости просты и бесплатны в C и не нуждаются во взломе цикла. Но опять же, если вы конвертируете _OP функции к обычным функциям это не будет проблемой.

main слишком большой и сложный. Разбейте его на подпрограммы.

Весь этот блок:

                if (key == sk_1) toAdd = r_1;
                if (key == sk_2) toAdd = r_2;
                if (key == sk_3) toAdd = r_3;
                if (key == sk_4) toAdd = r_4;
                if (key == sk_5) toAdd = r_5;
                if (key == sk_6) toAdd = r_6;
                if (key == sk_7) toAdd = r_7;
                if (key == sk_8) toAdd = r_8;
                if (key == sk_9) toAdd = r_9;

можно заменить таблицей поиска. Какой-то маньяк решил, что sk_ значения не должны быть смежными:

#define sk_0                0x21
#define sk_1                0x22
#define sk_4                0x23
#define sk_7                0x24
#define sk_2                0x1A
#define sk_5                0x1B
#define sk_8                0x1C
#define sk_3                0x12
#define sk_6                0x13
#define sk_9                0x14

поэтому, если вы сделаете такую ​​таблицу поиска, она будет иметь «интересный» порядок значений; вы можете инициализировать его как

static real_t r_numerals[sk_9 - sk_0 + 1];

// ...

r_numerals[sk_0 - sk_0] = r_0;
r_numerals[sk_1 - sk_0] = r_1;
r_numerals[sk_2 - sk_0] = r_2;
r_numerals[sk_3 - sk_0] = r_3;
r_numerals[sk_4 - sk_0] = r_4;
r_numerals[sk_5 - sk_0] = r_5;
r_numerals[sk_6 - sk_0] = r_6;
r_numerals[sk_7 - sk_0] = r_7;
r_numerals[sk_8 - sk_0] = r_8;
r_numerals[sk_9 - sk_0] = r_9;

// ...

toAdd = r_numerals[key - sk_0];

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

  • Спасибо за ваш отзыв! Я согласен со всеми улучшениями, которые вы предложили до сих пор. Что касается сборки и машинного кода, я считаю, что вы правы. Набор инструментов Ti84 +, кажется, смешивает эти два термина, возможно потому, что операционная система Ti84 + называет скомпилированные программы «программами ASM» (в отличие от интерпретированных «программ BASIC»).

    — викарджрамун

  • 1

    Советы по использованию указателей на функции кажутся особенно полезными. Я согласен с тем, что замена сгенерированных макросов блоков функцией, принимающей указатель на функцию, будет большим улучшением размера при очень небольшом снижении производительности. Замена длинных цепочек if-else на таблицы поиска указателей функций также кажется отличной идеей.

    — викарджрамун

  • В do { } while (false); Макро-шаблон был взят из множества мест, рекомендующих его использование. Среди них руководство по форматированию ядра Linux и этот ТАК вопрос. Почему в этих местах рекомендуют этот шаблон, когда разрешен обычный блок?

    — викарджрамун


  • Я не думаю, что они рекомендуют такой блок, но пытаются оправдать, почему разработчик, возможно, предпочел его: как неудобную замену break вместо goto (что вам не нужно) или из-за неуместных проблем совместимости компилятора.

    — Райндериен

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *