Update mathematical functions (#675)

Document the existing `ROUND`, `CEIL`, and `FLOOR` functions
Also update the trig function docs for searchability

Implement `POW` and `LOG`
Addresses part of #675

Implement ** for integer exponents
** has higher precedence than -, like Python, so -3**4 == -(3**4) == 81
This commit is contained in:
Rangi
2021-01-01 19:39:20 -05:00
committed by GitHub
parent 7bb6f71f0b
commit 895ec5564d
11 changed files with 148 additions and 12 deletions

View File

@@ -22,6 +22,8 @@ int32_t math_ATan(int32_t i);
int32_t math_ATan2(int32_t i, int32_t j); int32_t math_ATan2(int32_t i, int32_t j);
int32_t math_Mul(int32_t i, int32_t j); int32_t math_Mul(int32_t i, int32_t j);
int32_t math_Div(int32_t i, int32_t j); int32_t math_Div(int32_t i, int32_t j);
int32_t math_Pow(int32_t i, int32_t j);
int32_t math_Log(int32_t i, int32_t j);
int32_t math_Round(int32_t i); int32_t math_Round(int32_t i);
int32_t math_Ceil(int32_t i); int32_t math_Ceil(int32_t i);
int32_t math_Floor(int32_t i); int32_t math_Floor(int32_t i);

View File

@@ -14,7 +14,7 @@
#define RGBDS_OBJECT_VERSION_STRING "RGB%1u" #define RGBDS_OBJECT_VERSION_STRING "RGB%1u"
#define RGBDS_OBJECT_VERSION_NUMBER 9U #define RGBDS_OBJECT_VERSION_NUMBER 9U
#define RGBDS_OBJECT_REV 6U #define RGBDS_OBJECT_REV 7U
enum AssertionType { enum AssertionType {
ASSERT_WARN, ASSERT_WARN,
@@ -29,6 +29,7 @@ enum RPNCommand {
RPN_DIV = 0x03, RPN_DIV = 0x03,
RPN_MOD = 0x04, RPN_MOD = 0x04,
RPN_UNSUB = 0x05, RPN_UNSUB = 0x05,
RPN_EXP = 0x06,
RPN_OR = 0x10, RPN_OR = 0x10,
RPN_AND = 0x11, RPN_AND = 0x11,

View File

@@ -182,6 +182,8 @@ static struct KeywordMapping {
{"FLOOR", T_OP_FLOOR}, {"FLOOR", T_OP_FLOOR},
{"DIV", T_OP_FDIV}, {"DIV", T_OP_FDIV},
{"MUL", T_OP_FMUL}, {"MUL", T_OP_FMUL},
{"POW", T_OP_POW},
{"LOG", T_OP_LOG},
{"SIN", T_OP_SIN}, {"SIN", T_OP_SIN},
{"COS", T_OP_COS}, {"COS", T_OP_COS},
{"TAN", T_OP_TAN}, {"TAN", T_OP_TAN},
@@ -488,7 +490,7 @@ struct KeywordDictNode {
uint16_t children[0x60 - ' ']; uint16_t children[0x60 - ' '];
struct KeywordMapping const *keyword; struct KeywordMapping const *keyword;
/* Since the keyword structure is invariant, the min number of nodes is known at compile time */ /* Since the keyword structure is invariant, the min number of nodes is known at compile time */
} keywordDict[350] = {0}; /* Make sure to keep this correct when adding keywords! */ } keywordDict[352] = {0}; /* Make sure to keep this correct when adding keywords! */
/* Convert a char into its index into the dict */ /* Convert a char into its index into the dict */
static inline uint8_t dictIndex(char c) static inline uint8_t dictIndex(char c)
@@ -1606,13 +1608,20 @@ static int yylex_NORMAL(void)
lexer_GetLineNo(), lexer_GetColNo()); lexer_GetLineNo(), lexer_GetColNo());
for (;;) { for (;;) {
int c = nextChar(); int c = nextChar();
char secondChar;
switch (c) { switch (c) {
/* Ignore whitespace and comments */ /* Ignore whitespace and comments */
case '*': case '*':
if (!lexerState->atLineStart) if (!lexerState->atLineStart) { /* Either MUL or EXP */
secondChar = peek(0);
if (secondChar == '*') {
shiftChars(1);
return T_OP_EXP;
}
return T_OP_MUL; return T_OP_MUL;
}
warning(WARNING_OBSOLETE, warning(WARNING_OBSOLETE,
"'*' is deprecated for comments, please use ';' instead\n"); "'*' is deprecated for comments, please use ';' instead\n");
/* fallthrough */ /* fallthrough */
@@ -1651,7 +1660,6 @@ static int yylex_NORMAL(void)
return T_COMMA; return T_COMMA;
/* Handle ambiguous 1- or 2-char tokens */ /* Handle ambiguous 1- or 2-char tokens */
char secondChar;
case '/': /* Either division or a block comment */ case '/': /* Either division or a block comment */
secondChar = peek(0); secondChar = peek(0);
if (secondChar == '*') { if (secondChar == '*') {

View File

@@ -125,6 +125,22 @@ int32_t math_Div(int32_t i, int32_t j)
return double2fx(fx2double(i) / fx2double(j)); return double2fx(fx2double(i) / fx2double(j));
} }
/*
* Power
*/
int32_t math_Pow(int32_t i, int32_t j)
{
return double2fx(pow(fx2double(i), fx2double(j)));
}
/*
* Logarithm
*/
int32_t math_Log(int32_t i, int32_t j)
{
return double2fx(log(fx2double(i)) / log(fx2double(j)));
}
/* /*
* Round * Round
*/ */

View File

@@ -353,6 +353,11 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg)
%left T_OP_SHL T_OP_SHR %left T_OP_SHL T_OP_SHR
%left T_OP_MUL T_OP_DIV T_OP_MOD %left T_OP_MUL T_OP_DIV T_OP_MOD
%left T_OP_NOT %left T_OP_NOT
%left NEG /* negation -- unary minus */
%left T_OP_EXP
%left T_OP_DEF %left T_OP_DEF
%left T_OP_BANK T_OP_ALIGN %left T_OP_BANK T_OP_ALIGN
%left T_OP_SIN %left T_OP_SIN
@@ -364,6 +369,8 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg)
%left T_OP_ATAN2 %left T_OP_ATAN2
%left T_OP_FDIV %left T_OP_FDIV
%left T_OP_FMUL %left T_OP_FMUL
%left T_OP_POW
%left T_OP_LOG
%left T_OP_ROUND %left T_OP_ROUND
%left T_OP_CEIL %left T_OP_CEIL
%left T_OP_FLOOR %left T_OP_FLOOR
@@ -381,8 +388,6 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg)
%left T_OP_STRLWR %left T_OP_STRLWR
%left T_OP_STRFMT %left T_OP_STRFMT
%left NEG /* negation -- unary minus */
%token <tzSym> T_LABEL %token <tzSym> T_LABEL
%token <tzSym> T_ID %token <tzSym> T_ID
%token <tzSym> T_LOCAL_ID %token <tzSym> T_LOCAL_ID
@@ -1131,6 +1136,9 @@ relocexpr_no_str : scoped_anon_id { rpn_Symbol(&$$, $1); }
| relocexpr T_OP_MOD relocexpr { | relocexpr T_OP_MOD relocexpr {
rpn_BinaryOp(RPN_MOD, &$$, &$1, &$3); rpn_BinaryOp(RPN_MOD, &$$, &$1, &$3);
} }
| relocexpr T_OP_EXP relocexpr {
rpn_BinaryOp(RPN_EXP, &$$, &$1, &$3);
}
| T_OP_ADD relocexpr %prec NEG { $$ = $2; } | T_OP_ADD relocexpr %prec NEG { $$ = $2; }
| T_OP_SUB relocexpr %prec NEG { rpn_UNNEG(&$$, &$2); } | T_OP_SUB relocexpr %prec NEG { rpn_UNNEG(&$$, &$2); }
| T_OP_NOT relocexpr %prec NEG { rpn_UNNOT(&$$, &$2); } | T_OP_NOT relocexpr %prec NEG { rpn_UNNOT(&$$, &$2); }
@@ -1166,6 +1174,12 @@ relocexpr_no_str : scoped_anon_id { rpn_Symbol(&$$, $1); }
| T_OP_FMUL T_LPAREN const T_COMMA const T_RPAREN { | T_OP_FMUL T_LPAREN const T_COMMA const T_RPAREN {
rpn_Number(&$$, math_Mul($3, $5)); rpn_Number(&$$, math_Mul($3, $5));
} }
| T_OP_POW T_LPAREN const T_COMMA const T_RPAREN {
rpn_Number(&$$, math_Pow($3, $5));
}
| T_OP_LOG T_LPAREN const T_COMMA const T_RPAREN {
rpn_Number(&$$, math_Log($3, $5));
}
| T_OP_SIN T_LPAREN const T_RPAREN { | T_OP_SIN T_LPAREN const T_RPAREN {
rpn_Number(&$$, math_Sin($3)); rpn_Number(&$$, math_Sin($3));
} }

View File

@@ -131,6 +131,7 @@ A great number of operators you can use in expressions are available (listed fro
.It Sy Operator Ta Sy Meaning .It Sy Operator Ta Sy Meaning
.It Li \&( \&) Ta Precedence override .It Li \&( \&) Ta Precedence override
.It Li FUNC() Ta Built-in function call .It Li FUNC() Ta Built-in function call
.It Li ** Ta Exponent
.It Li ~ + - Ta Unary complement/plus/minus .It Li ~ + - Ta Unary complement/plus/minus
.It Li * / % Ta Multiply/divide/modulo .It Li * / % Ta Multiply/divide/modulo
.It Li << >> Ta Shift left/right .It Li << >> Ta Shift left/right
@@ -190,12 +191,17 @@ delim $$
.It Sy Name Ta Sy Operation .It Sy Name Ta Sy Operation
.It Fn DIV x y Ta $x \[di] y$ .It Fn DIV x y Ta $x \[di] y$
.It Fn MUL x y Ta $x \[mu] y$ .It Fn MUL x y Ta $x \[mu] y$
.It Fn SIN x Ta $sin ( x )$ .It Fn POW x y Ta $x$ to the $y$ power
.It Fn COS x Ta $cos ( x )$ .It Fn LOG x y Ta Logarithm of $x$ to the base $y$
.It Fn TAN x Ta $tan ( x )$ .It Fn ROUND x Ta Round $x$ to the nearest integer
.It Fn ASIN x Ta $asin ( x )$ .It Fn CEIL x Ta Round $x$ up to an integer
.It Fn ACOS x Ta $acos ( x )$ .It Fn FLOOR x Ta Round $x$ down to an integer
.It Fn ATAN x Ta $atan ( x )$ .It Fn SIN x Ta Sine of $x$
.It Fn COS x Ta Cosine of $x$
.It Fn TAN x Ta Tangent of $x$
.It Fn ASIN x Ta Inverse sine of $x$
.It Fn ACOS x Ta Inverse cosine of $x$
.It Fn ATAN x Ta Inverse tangent of $x$
.It Fn ATAN2 x y Ta Angle between $( x , y )$ and $( 1 , 0 )$ .It Fn ATAN2 x y Ta Angle between $( x , y )$ and $( 1 , 0 )$
.El .El
.EQ .EQ

View File

@@ -292,6 +292,20 @@ static int32_t shift(int32_t shiftee, int32_t amount)
} }
} }
static int32_t exponent(int32_t base, int32_t power)
{
int32_t result = 1;
while (power) {
if (power % 2)
result *= base;
power /= 2;
base *= base;
}
return result;
}
struct Symbol const *rpn_SymbolOf(struct Expression const *expr) struct Symbol const *rpn_SymbolOf(struct Expression const *expr)
{ {
if (!rpn_isSymbol(expr)) if (!rpn_isSymbol(expr))
@@ -415,6 +429,15 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
else else
expr->nVal = src1->nVal % src2->nVal; expr->nVal = src1->nVal % src2->nVal;
break; break;
case RPN_EXP:
if (src2->nVal < 0)
fatalerror("Exponentiation by negative power\n");
if (src1->nVal == INT32_MIN && src2->nVal == -1)
expr->nVal = 0;
else
expr->nVal = exponent(src1->nVal, src2->nVal);
break;
case RPN_UNSUB: case RPN_UNSUB:
case RPN_UNNOT: case RPN_UNNOT:

View File

@@ -21,6 +21,20 @@
#include "extern/err.h" #include "extern/err.h"
static int32_t exponent(int32_t base, int32_t power)
{
int32_t result = 1;
while (power) {
if (power % 2)
result *= base;
power /= 2;
base *= base;
}
return result;
}
static int32_t asl(int32_t value, int32_t shiftamt); // Forward decl for below static int32_t asl(int32_t value, int32_t shiftamt); // Forward decl for below
static int32_t asr(int32_t value, int32_t shiftamt) static int32_t asr(int32_t value, int32_t shiftamt)
{ {
@@ -237,6 +251,18 @@ static int32_t computeRPNExpr(struct Patch const *patch,
case RPN_UNSUB: case RPN_UNSUB:
value = -popRPN(); value = -popRPN();
break; break;
case RPN_EXP:
value = popRPN();
if (value < 0) {
if (!isError)
error(patch->src, patch->lineNo, "Exponent by negative");
isError = true;
popRPN();
value = 0;
} else {
value = exponent(popRPN(), value);
}
break;
case RPN_OR: case RPN_OR:
value = popRPN() | popRPN(); value = popRPN() | popRPN();

40
test/asm/math.asm Normal file
View File

@@ -0,0 +1,40 @@
X equ 0
test: MACRO
; Test RGBASM
v equs "X +"
static_assert \#
purge v
; Test RGBLINK
v equs "Y +"
assert \#
purge v
ENDM
test (v 2)*(v 10)**(v 2)*(v 2) == (v 400)
test -(v 3)**(v 4) == v -81
assert DIV(5.0, 2.0) == 2.5
assert DIV(-5.0, 2.0) == -2.5
assert DIV(-5.0, 0.0) == $8000_0000
assert MUL(10.0, 0.5) == 5.0
assert MUL(10.0, 0.0) == 0.0
assert POW(10.0, 2.0) == 100.0
assert POW(100.0, 0.5) == 10.0
assert LOG(100.0, 10.0) == 2.0
assert LOG(256.0, 2.0) == 8.0
assert ROUND(1.5) == 2.0
assert ROUND(-1.5) == -2.0
assert CEIL(1.5) == 2.0
assert CEIL(-1.5) == -1.0
assert FLOOR(1.5) == 1.0
assert FLOOR(-1.5) == -2.0
SECTION "Y", ROM0
Y::

0
test/asm/math.err Normal file
View File

0
test/asm/math.out Normal file
View File