Co-authored-by: Drashna Jaelre <drashna@live.com> Co-authored-by: Ryan <fauxpark@gmail.com>
		
			
				
	
	
		
			262 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			262 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
This is the modified version of [calculator by MWWorks](https://github.com/MWWorks/mw_calc_numpad/blob/master/calc.c). Below is the quote from [MWWorks](https://github.com/MWWorks).
 | 
						|
 | 
						|
   Calculator for QMK-based keyboard by MWWorks, https://mwworks.uk
 | 
						|
   This is free, usual disclaimers, don't use it to calculate megaton yields, surgery plans, etc
 | 
						|
 | 
						|
   I did not plan to reinvent the wheel for this - I figured surely somebody somewhere has working calculator code?
 | 
						|
   Found lots but none that actually work like you expect a calculator to, hence DIYing it
 | 
						|
 | 
						|
   As such, this is probably a bit janky, especially as I am a bit of a hack at C
 | 
						|
   Seems to be working well, with occasional glitchs, solved by clearing it
 | 
						|
   And some occasional floating-point issues - eg get a long decimal rather than the whole number you were expecting
 | 
						|
   Feel free to fix it! I think it needs to detect the precision of the two operands and then figure out what the precision of the result should be
 | 
						|
 | 
						|
*/
 | 
						|
#include "rubi.h"
 | 
						|
 | 
						|
static uint8_t calc_current_operand = 0;
 | 
						|
static char calc_operand_0[CALC_DIGITS+1] = "";
 | 
						|
static char calc_operand_1[CALC_DIGITS+1] = "";
 | 
						|
char calc_result[CALC_DIGITS+1] = "";
 | 
						|
static char calc_status[CALC_DIGITS+1] = "";
 | 
						|
static char calc_operator = ' ';
 | 
						|
static bool calc_reset = false;
 | 
						|
 | 
						|
 | 
						|
void calcBegin(void){
 | 
						|
}
 | 
						|
 | 
						|
//update display
 | 
						|
void calcUpdate(void){
 | 
						|
    if (calc_display_lines == 2) {
 | 
						|
        if((calc_current_operand == 1) || (calc_reset)){
 | 
						|
            strcpy(calc_status, calc_operand_0);
 | 
						|
            if((strlen(calc_operand_0)>0) || (strlen(calc_operand_1)>0)){
 | 
						|
                uint8_t len = strlen(calc_status);
 | 
						|
                if (!(calc_operator == 's' || calc_operator == 'r' || calc_operator == 'n')) {
 | 
						|
                    calc_status[len] = calc_operator;
 | 
						|
                }
 | 
						|
                calc_status[len+1] = 0;
 | 
						|
                if(calc_reset
 | 
						|
                        && !(calc_operator == 's' || calc_operator == 'r' || calc_operator == 'n')){
 | 
						|
                    strncat(calc_status, calc_operand_1, CALC_DIGITS-strlen(calc_status));
 | 
						|
                    calc_operator = ' ';
 | 
						|
                }
 | 
						|
            }
 | 
						|
            strcpy(calc_status_display, calc_status);
 | 
						|
        }
 | 
						|
    } else if (calc_display_lines == 1) {
 | 
						|
        if(calc_reset
 | 
						|
                && !(calc_operator == 's' || calc_operator == 'r' || calc_operator == 'n')){
 | 
						|
            calc_operator = ' ';
 | 
						|
        }
 | 
						|
    }
 | 
						|
    calc_operator_display = calc_operator;
 | 
						|
    strcpy(calc_result_display, calc_result);
 | 
						|
}
 | 
						|
 | 
						|
//perform calculation on the 2 operands
 | 
						|
void calcOperands(void){
 | 
						|
    float result = 0;
 | 
						|
    switch (calc_operator){
 | 
						|
 | 
						|
        //standard operators
 | 
						|
        case '+':
 | 
						|
            result = strtod(calc_operand_0, NULL) + strtod(calc_operand_1, NULL);
 | 
						|
            break;
 | 
						|
 | 
						|
        case '-':
 | 
						|
            result = strtod(calc_operand_0, NULL) - strtod(calc_operand_1, NULL);
 | 
						|
            break;
 | 
						|
 | 
						|
        case '/':
 | 
						|
            result = strtod(calc_operand_0, NULL) / strtod(calc_operand_1, NULL);
 | 
						|
            break;
 | 
						|
 | 
						|
        case '*':
 | 
						|
            result = strtod(calc_operand_0, NULL) * strtod(calc_operand_1, NULL);
 | 
						|
            break;
 | 
						|
 | 
						|
            //single operand operators - these are all in 2
 | 
						|
        case 's':
 | 
						|
            result = sqrt(strtod(calc_operand_0, NULL));
 | 
						|
            break;
 | 
						|
 | 
						|
        case 'r':
 | 
						|
            result = 1/(strtod(calc_operand_0, NULL));
 | 
						|
            break;
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    //now convert the float result into a string
 | 
						|
    //we know the total string size but we need to find the size of the integer component to know how much we have for decimals
 | 
						|
    uint8_t magnitude = ceil(log10(result));
 | 
						|
    uint8_t max_decimals = CALC_DIGITS-magnitude-1;
 | 
						|
    //but max it at 7 because that seems the useful limit of our floats
 | 
						|
    if(max_decimals>7){
 | 
						|
        max_decimals = 7;
 | 
						|
    }
 | 
						|
    dtostrf(result, CALC_DIGITS, max_decimals, calc_result);
 | 
						|
 | 
						|
    //now to clean up the result - we need it clean as it may be the input of next calculation
 | 
						|
    //this seems a lot of code to format this string :| note that this c doesn't support float in sprintf
 | 
						|
    uint8_t i;
 | 
						|
 | 
						|
    //first find if theres a dot
 | 
						|
    uint8_t dotpos = CALC_DIGITS+1;
 | 
						|
    for(i=0; i<strlen(calc_result); i++){
 | 
						|
        if(calc_result[i] == '.'){
 | 
						|
            dotpos = i;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    //if there is, work back to it and remove trailing 0 or .
 | 
						|
    if(dotpos>=0){
 | 
						|
        for(i=strlen(calc_result)-1; i>=dotpos; i--){
 | 
						|
            if((calc_result[i] == '0') || (calc_result[i] == '.')){
 | 
						|
                calc_result[i] = 0;
 | 
						|
            }else{
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    //now find how many leading spaces
 | 
						|
    uint8_t spaces = 0;
 | 
						|
    for(i=0; i<strlen(calc_result); i++){
 | 
						|
        if(calc_result[i] == ' '){
 | 
						|
            spaces++;
 | 
						|
        }else{
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    //and shift the string
 | 
						|
    for(i=0; i<strlen(calc_result)-spaces; i++){
 | 
						|
        calc_result[i] = calc_result[i+spaces];
 | 
						|
    }
 | 
						|
    calc_result[strlen(calc_result)-spaces] = 0;
 | 
						|
 | 
						|
    calcUpdate();
 | 
						|
    //the result is available as the first operand for another calculation
 | 
						|
    strcpy(calc_operand_0, calc_result);
 | 
						|
    calc_operand_1[0] = 0;
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
void calcInput(char input){
 | 
						|
    char *operand = calc_operand_0;
 | 
						|
    if(calc_current_operand == 1){
 | 
						|
        operand = calc_operand_1;
 | 
						|
    }
 | 
						|
    uint8_t len = strlen(operand);
 | 
						|
 | 
						|
    if(
 | 
						|
            ((input >= 48) && (input <= 57)) ||
 | 
						|
            (input == '.')
 | 
						|
      ){
 | 
						|
        //if this is following an equals, then we start from scratch as if new calculation
 | 
						|
        if(calc_reset == true){
 | 
						|
            calc_reset = false;
 | 
						|
            calc_current_operand = 0;
 | 
						|
            calc_operand_0[0] = 0;
 | 
						|
            calc_operand_1[0] = 0;
 | 
						|
            operand = calc_operand_0;
 | 
						|
            len = 0;
 | 
						|
        }
 | 
						|
 | 
						|
        if(len<CALC_DIGITS){
 | 
						|
            operand[len] = input;
 | 
						|
            operand[len+1] = 0;
 | 
						|
            strcpy(calc_result, operand);
 | 
						|
            calcUpdate();
 | 
						|
        }
 | 
						|
 | 
						|
        //special input to backspace
 | 
						|
    }else if(input == 'x'){
 | 
						|
        operand[len-1] = 0;
 | 
						|
        strcpy(calc_result, operand);
 | 
						|
        calcUpdate();
 | 
						|
 | 
						|
        //clear
 | 
						|
    }else if(input == 'c'){
 | 
						|
        operand[0] = 0;
 | 
						|
        calc_operand_0[0] = 0;
 | 
						|
        calc_operand_1[0] = 0;
 | 
						|
        calc_operator = ' ';
 | 
						|
        calc_reset = true;
 | 
						|
        strcpy(calc_result, operand);
 | 
						|
        calcUpdate();
 | 
						|
 | 
						|
        //special input switch neg/pos
 | 
						|
    }else if((input == 'n') && (len>0)){
 | 
						|
        uint8_t i;
 | 
						|
 | 
						|
        if(operand[0] == '-'){
 | 
						|
            for(i=1; i<=len; i++){
 | 
						|
                operand[i-1] = operand[i];
 | 
						|
            }
 | 
						|
        }else if(len<CALC_DIGITS){
 | 
						|
            for(i=0; i<=len; i++){
 | 
						|
                operand[len-i+1] = operand[len-i];
 | 
						|
            }
 | 
						|
            operand[0] = '-';
 | 
						|
        }
 | 
						|
        calc_operator = input;
 | 
						|
        strcpy(calc_result, operand);
 | 
						|
        calcUpdate();
 | 
						|
 | 
						|
 | 
						|
        //standard 2 operand operators
 | 
						|
    }else if((input == '+') || (input == '-') || (input == '*')  || (input == '/')){
 | 
						|
 | 
						|
        //get ready for second operand
 | 
						|
        if(calc_current_operand == 0){
 | 
						|
            calc_operator = input;
 | 
						|
            calc_current_operand = 1;
 | 
						|
            calcUpdate();
 | 
						|
 | 
						|
            //we pressed = we now expect a new second operand
 | 
						|
        }else if(calc_reset){
 | 
						|
            calc_operator = input;
 | 
						|
            calc_reset = false;
 | 
						|
            calc_operand_1[0] = 0;
 | 
						|
            calcUpdate();
 | 
						|
 | 
						|
        }else {
 | 
						|
            //if we use this on the second operand, calculate first, then ready for a second operand again
 | 
						|
            if (strlen(calc_operand_1)>0){
 | 
						|
                calcOperands();
 | 
						|
            }
 | 
						|
            calc_operand_1[0] = 0;
 | 
						|
            calc_operator = input;
 | 
						|
            calcUpdate();
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
    }else if(input == '='){
 | 
						|
        //only accept = if we are on the second operand
 | 
						|
        if(calc_current_operand == 1){
 | 
						|
            //keep the second operand for a subsequent press of =; but flag to reset if start entry of new operand
 | 
						|
            calc_reset = true;
 | 
						|
            calcOperands();
 | 
						|
        }
 | 
						|
 | 
						|
        //single operands - square root and reciprocal - needs to operate on 0 so it works after a previous = result
 | 
						|
    }else if((input == 's') || (input == 'r')){
 | 
						|
        //but maybe we started entering 1
 | 
						|
        if(calc_current_operand == 1 && !calc_reset){
 | 
						|
            strcpy(calc_operand_0, calc_operand_1);
 | 
						|
        }
 | 
						|
        calc_current_operand = 1;
 | 
						|
        calc_operand_1[0] = 0;
 | 
						|
        calc_operator = input;
 | 
						|
        calc_reset = true; //simulate another =
 | 
						|
        calcOperands();
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
}
 |