calc.c (8462B)
1 /* 2 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). 3 4 Calculator for QMK-based keyboard by MWWorks, https://mwworks.uk 5 This is free, usual disclaimers, don't use it to calculate megaton yields, surgery plans, etc 6 7 I did not plan to reinvent the wheel for this - I figured surely somebody somewhere has working calculator code? 8 Found lots but none that actually work like you expect a calculator to, hence DIYing it 9 10 As such, this is probably a bit janky, especially as I am a bit of a hack at C 11 Seems to be working well, with occasional glitchs, solved by clearing it 12 And some occasional floating-point issues - eg get a long decimal rather than the whole number you were expecting 13 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 14 15 */ 16 #include <math.h> 17 #include "rubi.h" 18 19 static uint8_t calc_current_operand = 0; 20 static char calc_operand_0[CALC_DIGITS+1] = ""; 21 static char calc_operand_1[CALC_DIGITS+1] = ""; 22 char calc_result[CALC_DIGITS+1] = ""; 23 static char calc_status[CALC_DIGITS+1] = ""; 24 static char calc_operator = ' '; 25 static bool calc_reset = false; 26 27 28 void calcBegin(void){ 29 } 30 31 //update display 32 void calcUpdate(void){ 33 if (calc_display_lines == 2) { 34 if((calc_current_operand == 1) || (calc_reset)){ 35 strcpy(calc_status, calc_operand_0); 36 if((strlen(calc_operand_0)>0) || (strlen(calc_operand_1)>0)){ 37 uint8_t len = strlen(calc_status); 38 if (!(calc_operator == 's' || calc_operator == 'r' || calc_operator == 'n')) { 39 calc_status[len] = calc_operator; 40 } 41 calc_status[len+1] = 0; 42 if(calc_reset 43 && !(calc_operator == 's' || calc_operator == 'r' || calc_operator == 'n')){ 44 strncat(calc_status, calc_operand_1, CALC_DIGITS-strlen(calc_status)); 45 calc_operator = ' '; 46 } 47 } 48 strcpy(calc_status_display, calc_status); 49 } 50 } else if (calc_display_lines == 1) { 51 if(calc_reset 52 && !(calc_operator == 's' || calc_operator == 'r' || calc_operator == 'n')){ 53 calc_operator = ' '; 54 } 55 } 56 calc_operator_display = calc_operator; 57 strcpy(calc_result_display, calc_result); 58 } 59 60 //perform calculation on the 2 operands 61 void calcOperands(void){ 62 float result = 0; 63 switch (calc_operator){ 64 65 //standard operators 66 case '+': 67 result = strtod(calc_operand_0, NULL) + strtod(calc_operand_1, NULL); 68 break; 69 70 case '-': 71 result = strtod(calc_operand_0, NULL) - strtod(calc_operand_1, NULL); 72 break; 73 74 case '/': 75 result = strtod(calc_operand_0, NULL) / strtod(calc_operand_1, NULL); 76 break; 77 78 case '*': 79 result = strtod(calc_operand_0, NULL) * strtod(calc_operand_1, NULL); 80 break; 81 82 //single operand operators - these are all in 2 83 case 's': 84 result = sqrt(strtod(calc_operand_0, NULL)); 85 break; 86 87 case 'r': 88 result = 1/(strtod(calc_operand_0, NULL)); 89 break; 90 91 } 92 93 //now convert the float result into a string 94 //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 95 uint8_t magnitude = ceil(log10(result)); 96 uint8_t max_decimals = CALC_DIGITS-magnitude-1; 97 //but max it at 7 because that seems the useful limit of our floats 98 if(max_decimals>7){ 99 max_decimals = 7; 100 } 101 dtostrf(result, CALC_DIGITS, max_decimals, calc_result); 102 103 //now to clean up the result - we need it clean as it may be the input of next calculation 104 //this seems a lot of code to format this string :| note that this c doesn't support float in sprintf 105 uint8_t i; 106 107 //first find if theres a dot 108 uint8_t dotpos = CALC_DIGITS+1; 109 for(i=0; i<strlen(calc_result); i++){ 110 if(calc_result[i] == '.'){ 111 dotpos = i; 112 break; 113 } 114 } 115 116 //if there is, work back to it and remove trailing 0 or . 117 if(dotpos>=0){ 118 for(i=strlen(calc_result)-1; i>=dotpos; i--){ 119 if((calc_result[i] == '0') || (calc_result[i] == '.')){ 120 calc_result[i] = 0; 121 }else{ 122 break; 123 } 124 } 125 } 126 127 //now find how many leading spaces 128 uint8_t spaces = 0; 129 for(i=0; i<strlen(calc_result); i++){ 130 if(calc_result[i] == ' '){ 131 spaces++; 132 }else{ 133 break; 134 } 135 } 136 137 //and shift the string 138 for(i=0; i<strlen(calc_result)-spaces; i++){ 139 calc_result[i] = calc_result[i+spaces]; 140 } 141 calc_result[strlen(calc_result)-spaces] = 0; 142 143 calcUpdate(); 144 //the result is available as the first operand for another calculation 145 strcpy(calc_operand_0, calc_result); 146 calc_operand_1[0] = 0; 147 148 } 149 150 void calcInput(char input){ 151 char *operand = calc_operand_0; 152 if(calc_current_operand == 1){ 153 operand = calc_operand_1; 154 } 155 uint8_t len = strlen(operand); 156 157 if( 158 ((input >= 48) && (input <= 57)) || 159 (input == '.') 160 ){ 161 //if this is following an equals, then we start from scratch as if new calculation 162 if(calc_reset == true){ 163 calc_reset = false; 164 calc_current_operand = 0; 165 calc_operand_0[0] = 0; 166 calc_operand_1[0] = 0; 167 operand = calc_operand_0; 168 len = 0; 169 } 170 171 if(len<CALC_DIGITS){ 172 operand[len] = input; 173 operand[len+1] = 0; 174 strcpy(calc_result, operand); 175 calcUpdate(); 176 } 177 178 //special input to backspace 179 }else if(input == 'x'){ 180 operand[len-1] = 0; 181 strcpy(calc_result, operand); 182 calcUpdate(); 183 184 //clear 185 }else if(input == 'c'){ 186 operand[0] = 0; 187 calc_operand_0[0] = 0; 188 calc_operand_1[0] = 0; 189 calc_operator = ' '; 190 calc_reset = true; 191 strcpy(calc_result, operand); 192 calcUpdate(); 193 194 //special input switch neg/pos 195 }else if((input == 'n') && (len>0)){ 196 uint8_t i; 197 198 if(operand[0] == '-'){ 199 for(i=1; i<=len; i++){ 200 operand[i-1] = operand[i]; 201 } 202 }else if(len<CALC_DIGITS){ 203 for(i=0; i<=len; i++){ 204 operand[len-i+1] = operand[len-i]; 205 } 206 operand[0] = '-'; 207 } 208 calc_operator = input; 209 strcpy(calc_result, operand); 210 calcUpdate(); 211 212 213 //standard 2 operand operators 214 }else if((input == '+') || (input == '-') || (input == '*') || (input == '/')){ 215 216 //get ready for second operand 217 if(calc_current_operand == 0){ 218 calc_operator = input; 219 calc_current_operand = 1; 220 calcUpdate(); 221 222 //we pressed = we now expect a new second operand 223 }else if(calc_reset){ 224 calc_operator = input; 225 calc_reset = false; 226 calc_operand_1[0] = 0; 227 calcUpdate(); 228 229 }else { 230 //if we use this on the second operand, calculate first, then ready for a second operand again 231 if (strlen(calc_operand_1)>0){ 232 calcOperands(); 233 } 234 calc_operand_1[0] = 0; 235 calc_operator = input; 236 calcUpdate(); 237 } 238 239 240 }else if(input == '='){ 241 //only accept = if we are on the second operand 242 if(calc_current_operand == 1){ 243 //keep the second operand for a subsequent press of =; but flag to reset if start entry of new operand 244 calc_reset = true; 245 calcOperands(); 246 } 247 248 //single operands - square root and reciprocal - needs to operate on 0 so it works after a previous = result 249 }else if((input == 's') || (input == 'r')){ 250 //but maybe we started entering 1 251 if(calc_current_operand == 1 && !calc_reset){ 252 strcpy(calc_operand_0, calc_operand_1); 253 } 254 calc_current_operand = 1; 255 calc_operand_1[0] = 0; 256 calc_operator = input; 257 calc_reset = true; //simulate another = 258 calcOperands(); 259 260 } 261 262 }