mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
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:
@@ -22,6 +22,8 @@ int32_t math_ATan(int32_t i);
|
||||
int32_t math_ATan2(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_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_Ceil(int32_t i);
|
||||
int32_t math_Floor(int32_t i);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#define RGBDS_OBJECT_VERSION_STRING "RGB%1u"
|
||||
#define RGBDS_OBJECT_VERSION_NUMBER 9U
|
||||
#define RGBDS_OBJECT_REV 6U
|
||||
#define RGBDS_OBJECT_REV 7U
|
||||
|
||||
enum AssertionType {
|
||||
ASSERT_WARN,
|
||||
@@ -29,6 +29,7 @@ enum RPNCommand {
|
||||
RPN_DIV = 0x03,
|
||||
RPN_MOD = 0x04,
|
||||
RPN_UNSUB = 0x05,
|
||||
RPN_EXP = 0x06,
|
||||
|
||||
RPN_OR = 0x10,
|
||||
RPN_AND = 0x11,
|
||||
|
||||
@@ -182,6 +182,8 @@ static struct KeywordMapping {
|
||||
{"FLOOR", T_OP_FLOOR},
|
||||
{"DIV", T_OP_FDIV},
|
||||
{"MUL", T_OP_FMUL},
|
||||
{"POW", T_OP_POW},
|
||||
{"LOG", T_OP_LOG},
|
||||
{"SIN", T_OP_SIN},
|
||||
{"COS", T_OP_COS},
|
||||
{"TAN", T_OP_TAN},
|
||||
@@ -488,7 +490,7 @@ struct KeywordDictNode {
|
||||
uint16_t children[0x60 - ' '];
|
||||
struct KeywordMapping const *keyword;
|
||||
/* 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 */
|
||||
static inline uint8_t dictIndex(char c)
|
||||
@@ -1606,13 +1608,20 @@ static int yylex_NORMAL(void)
|
||||
lexer_GetLineNo(), lexer_GetColNo());
|
||||
for (;;) {
|
||||
int c = nextChar();
|
||||
char secondChar;
|
||||
|
||||
switch (c) {
|
||||
/* Ignore whitespace and comments */
|
||||
|
||||
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;
|
||||
}
|
||||
warning(WARNING_OBSOLETE,
|
||||
"'*' is deprecated for comments, please use ';' instead\n");
|
||||
/* fallthrough */
|
||||
@@ -1651,7 +1660,6 @@ static int yylex_NORMAL(void)
|
||||
return T_COMMA;
|
||||
|
||||
/* Handle ambiguous 1- or 2-char tokens */
|
||||
char secondChar;
|
||||
case '/': /* Either division or a block comment */
|
||||
secondChar = peek(0);
|
||||
if (secondChar == '*') {
|
||||
|
||||
@@ -125,6 +125,22 @@ int32_t math_Div(int32_t i, int32_t 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
|
||||
*/
|
||||
|
||||
@@ -353,6 +353,11 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg)
|
||||
%left T_OP_SHL T_OP_SHR
|
||||
%left T_OP_MUL T_OP_DIV T_OP_MOD
|
||||
%left T_OP_NOT
|
||||
|
||||
%left NEG /* negation -- unary minus */
|
||||
|
||||
%left T_OP_EXP
|
||||
|
||||
%left T_OP_DEF
|
||||
%left T_OP_BANK T_OP_ALIGN
|
||||
%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_FDIV
|
||||
%left T_OP_FMUL
|
||||
%left T_OP_POW
|
||||
%left T_OP_LOG
|
||||
%left T_OP_ROUND
|
||||
%left T_OP_CEIL
|
||||
%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_STRFMT
|
||||
|
||||
%left NEG /* negation -- unary minus */
|
||||
|
||||
%token <tzSym> T_LABEL
|
||||
%token <tzSym> T_ID
|
||||
%token <tzSym> T_LOCAL_ID
|
||||
@@ -1131,6 +1136,9 @@ relocexpr_no_str : scoped_anon_id { rpn_Symbol(&$$, $1); }
|
||||
| relocexpr T_OP_MOD relocexpr {
|
||||
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_SUB relocexpr %prec NEG { rpn_UNNEG(&$$, &$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 {
|
||||
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 {
|
||||
rpn_Number(&$$, math_Sin($3));
|
||||
}
|
||||
|
||||
@@ -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 Li \&( \&) Ta Precedence override
|
||||
.It Li FUNC() Ta Built-in function call
|
||||
.It Li ** Ta Exponent
|
||||
.It Li ~ + - Ta Unary complement/plus/minus
|
||||
.It Li * / % Ta Multiply/divide/modulo
|
||||
.It Li << >> Ta Shift left/right
|
||||
@@ -190,12 +191,17 @@ delim $$
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn DIV x y Ta $x \[di] y$
|
||||
.It Fn MUL x y Ta $x \[mu] y$
|
||||
.It Fn SIN x Ta $sin ( x )$
|
||||
.It Fn COS x Ta $cos ( x )$
|
||||
.It Fn TAN x Ta $tan ( x )$
|
||||
.It Fn ASIN x Ta $asin ( x )$
|
||||
.It Fn ACOS x Ta $acos ( x )$
|
||||
.It Fn ATAN x Ta $atan ( x )$
|
||||
.It Fn POW x y Ta $x$ to the $y$ power
|
||||
.It Fn LOG x y Ta Logarithm of $x$ to the base $y$
|
||||
.It Fn ROUND x Ta Round $x$ to the nearest integer
|
||||
.It Fn CEIL x Ta Round $x$ up to an integer
|
||||
.It Fn FLOOR x Ta Round $x$ down to an integer
|
||||
.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 )$
|
||||
.El
|
||||
.EQ
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
if (!rpn_isSymbol(expr))
|
||||
@@ -415,6 +429,15 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr,
|
||||
else
|
||||
expr->nVal = src1->nVal % src2->nVal;
|
||||
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_UNNOT:
|
||||
|
||||
@@ -21,6 +21,20 @@
|
||||
|
||||
#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 asr(int32_t value, int32_t shiftamt)
|
||||
{
|
||||
@@ -237,6 +251,18 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
||||
case RPN_UNSUB:
|
||||
value = -popRPN();
|
||||
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:
|
||||
value = popRPN() | popRPN();
|
||||
|
||||
40
test/asm/math.asm
Normal file
40
test/asm/math.asm
Normal 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
0
test/asm/math.err
Normal file
0
test/asm/math.out
Normal file
0
test/asm/math.out
Normal file
Reference in New Issue
Block a user