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_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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 == '*') {
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
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