shithub: rgbds

Download patch

ref: 3c049983f1008e52c5b04a870d91cd0e03ac5c36
parent: 8553b61a946527d12f2af7b75b1a7c66b8044ca1
author: Rangi <35663410+Rangi42@users.noreply.github.com>
date: Sun Oct 2 06:56:08 EDT 2022

Fixed-point functions can take specific precision (#1086)


--- a/include/asm/fixpoint.h
+++ b/include/asm/fixpoint.h
@@ -13,22 +13,22 @@
 
 extern uint8_t fixPrecision;
 
+uint8_t fix_Precision(void);
 double fix_PrecisionFactor(void);
-void fix_Print(int32_t i);
-int32_t fix_Sin(int32_t i);
-int32_t fix_Cos(int32_t i);
-int32_t fix_Tan(int32_t i);
-int32_t fix_ASin(int32_t i);
-int32_t fix_ACos(int32_t i);
-int32_t fix_ATan(int32_t i);
-int32_t fix_ATan2(int32_t i, int32_t j);
-int32_t fix_Mul(int32_t i, int32_t j);
-int32_t fix_Mod(int32_t i, int32_t j);
-int32_t fix_Div(int32_t i, int32_t j);
-int32_t fix_Pow(int32_t i, int32_t j);
-int32_t fix_Log(int32_t i, int32_t j);
-int32_t fix_Round(int32_t i);
-int32_t fix_Ceil(int32_t i);
-int32_t fix_Floor(int32_t i);
+int32_t fix_Sin(int32_t i, int32_t q);
+int32_t fix_Cos(int32_t i, int32_t q);
+int32_t fix_Tan(int32_t i, int32_t q);
+int32_t fix_ASin(int32_t i, int32_t q);
+int32_t fix_ACos(int32_t i, int32_t q);
+int32_t fix_ATan(int32_t i, int32_t q);
+int32_t fix_ATan2(int32_t i, int32_t j, int32_t q);
+int32_t fix_Mul(int32_t i, int32_t j, int32_t q);
+int32_t fix_Mod(int32_t i, int32_t j, int32_t q);
+int32_t fix_Div(int32_t i, int32_t j, int32_t q);
+int32_t fix_Pow(int32_t i, int32_t j, int32_t q);
+int32_t fix_Log(int32_t i, int32_t j, int32_t q);
+int32_t fix_Round(int32_t i, int32_t q);
+int32_t fix_Ceil(int32_t i, int32_t q);
+int32_t fix_Floor(int32_t i, int32_t q);
 
 #endif // RGBDS_ASM_FIXPOINT_H
--- a/man/rgbasm.5
+++ b/man/rgbasm.5
@@ -346,6 +346,15 @@
 delim off
 .EN
 .Pp
+All of these fixed-point functions can take an optional final argument, which is the precision to use.
+For example,
+.Ql MUL(6.0q8, 7.0q8, 8)
+will evaluate to
+.Ql 42.0q8
+no matter what value is set as the current
+.Cm Q
+option.
+.Pp
 The trigonometry functions (
 .Ic SIN ,
 .Ic COS ,
--- a/src/asm/fixpoint.c
+++ b/src/asm/fixpoint.c
@@ -11,7 +11,6 @@
 #include <inttypes.h>
 #include <math.h>
 #include <stdint.h>
-#include <stdio.h>
 
 #include "asm/fixpoint.h"
 #include "asm/symbol.h"
@@ -21,8 +20,8 @@
 #define M_PI 3.14159265358979323846
 #endif
 
-#define fix2double(i)	((double)((i) / fix_PrecisionFactor()))
-#define double2fix(d)	((int32_t)round((d) * fix_PrecisionFactor()))
+#define fix2double(i, q)	((double)((i) / pow(2.0, q)))
+#define double2fix(d, q)	((int32_t)round((d) * pow(2.0, q)))
 
 // 2*pi radians == 1 turn
 #define turn2rad(f)	((f) * (M_PI * 2))
@@ -30,96 +29,87 @@
 
 uint8_t fixPrecision;
 
-double fix_PrecisionFactor(void)
+uint8_t fix_Precision(void)
 {
-	return pow(2.0, fixPrecision);
+	return fixPrecision;
 }
 
-void fix_Print(int32_t i)
+double fix_PrecisionFactor(void)
 {
-	uint32_t u = i;
-	char const *sign = "";
-
-	if (i < 0) {
-		u = -u;
-		sign = "-";
-	}
-
-	printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> fixPrecision,
-	       ((uint32_t)(fix2double(u) * 100000 + 0.5)) % 100000);
+	return pow(2.0, fixPrecision);
 }
 
-int32_t fix_Sin(int32_t i)
+int32_t fix_Sin(int32_t i, int32_t q)
 {
-	return double2fix(sin(turn2rad(fix2double(i))));
+	return double2fix(sin(turn2rad(fix2double(i, q))), q);
 }
 
-int32_t fix_Cos(int32_t i)
+int32_t fix_Cos(int32_t i, int32_t q)
 {
-	return double2fix(cos(turn2rad(fix2double(i))));
+	return double2fix(cos(turn2rad(fix2double(i, q))), q);
 }
 
-int32_t fix_Tan(int32_t i)
+int32_t fix_Tan(int32_t i, int32_t q)
 {
-	return double2fix(tan(turn2rad(fix2double(i))));
+	return double2fix(tan(turn2rad(fix2double(i, q))), q);
 }
 
-int32_t fix_ASin(int32_t i)
+int32_t fix_ASin(int32_t i, int32_t q)
 {
-	return double2fix(rad2turn(asin(fix2double(i))));
+	return double2fix(rad2turn(asin(fix2double(i, q))), q);
 }
 
-int32_t fix_ACos(int32_t i)
+int32_t fix_ACos(int32_t i, int32_t q)
 {
-	return double2fix(rad2turn(acos(fix2double(i))));
+	return double2fix(rad2turn(acos(fix2double(i, q))), q);
 }
 
-int32_t fix_ATan(int32_t i)
+int32_t fix_ATan(int32_t i, int32_t q)
 {
-	return double2fix(rad2turn(atan(fix2double(i))));
+	return double2fix(rad2turn(atan(fix2double(i, q))), q);
 }
 
-int32_t fix_ATan2(int32_t i, int32_t j)
+int32_t fix_ATan2(int32_t i, int32_t j, int32_t q)
 {
-	return double2fix(rad2turn(atan2(fix2double(i), fix2double(j))));
+	return double2fix(rad2turn(atan2(fix2double(i, q), fix2double(j, q))), q);
 }
 
-int32_t fix_Mul(int32_t i, int32_t j)
+int32_t fix_Mul(int32_t i, int32_t j, int32_t q)
 {
-	return double2fix(fix2double(i) * fix2double(j));
+	return double2fix(fix2double(i, q) * fix2double(j, q), q);
 }
 
-int32_t fix_Div(int32_t i, int32_t j)
+int32_t fix_Div(int32_t i, int32_t j, int32_t q)
 {
-	return double2fix(fix2double(i) / fix2double(j));
+	return double2fix(fix2double(i, q) / fix2double(j, q), q);
 }
 
-int32_t fix_Mod(int32_t i, int32_t j)
+int32_t fix_Mod(int32_t i, int32_t j, int32_t q)
 {
-	return double2fix(fmod(fix2double(i), fix2double(j)));
+	return double2fix(fmod(fix2double(i, q), fix2double(j, q)), q);
 }
 
-int32_t fix_Pow(int32_t i, int32_t j)
+int32_t fix_Pow(int32_t i, int32_t j, int32_t q)
 {
-	return double2fix(pow(fix2double(i), fix2double(j)));
+	return double2fix(pow(fix2double(i, q), fix2double(j, q)), q);
 }
 
-int32_t fix_Log(int32_t i, int32_t j)
+int32_t fix_Log(int32_t i, int32_t j, int32_t q)
 {
-	return double2fix(log(fix2double(i)) / log(fix2double(j)));
+	return double2fix(log(fix2double(i, q)) / log(fix2double(j, q)), q);
 }
 
-int32_t fix_Round(int32_t i)
+int32_t fix_Round(int32_t i, int32_t q)
 {
-	return double2fix(round(fix2double(i)));
+	return double2fix(round(fix2double(i, q)), q);
 }
 
-int32_t fix_Ceil(int32_t i)
+int32_t fix_Ceil(int32_t i, int32_t q)
 {
-	return double2fix(ceil(fix2double(i)));
+	return double2fix(ceil(fix2double(i, q)), q);
 }
 
-int32_t fix_Floor(int32_t i)
+int32_t fix_Floor(int32_t i, int32_t q)
 {
-	return double2fix(floor(fix2double(i)));
+	return double2fix(floor(fix2double(i, q)), q);
 }
--- a/src/asm/parser.y
+++ b/src/asm/parser.y
@@ -550,6 +550,7 @@
 %token	T_OP_BANK "BANK"
 %token	T_OP_ALIGN "ALIGN"
 %token	T_OP_SIZEOF "SIZEOF" T_OP_STARTOF "STARTOF"
+
 %token	T_OP_SIN "SIN" T_OP_COS "COS" T_OP_TAN "TAN"
 %token	T_OP_ASIN "ASIN" T_OP_ACOS "ACOS" T_OP_ATAN "ATAN" T_OP_ATAN2 "ATAN2"
 %token	T_OP_FDIV "FDIV"
@@ -559,6 +560,7 @@
 %token	T_OP_LOG "LOG"
 %token	T_OP_ROUND "ROUND"
 %token	T_OP_CEIL "CEIL" T_OP_FLOOR "FLOOR"
+%type	<constValue> opt_q_arg
 
 %token	T_OP_HIGH "HIGH" T_OP_LOW "LOW"
 %token	T_OP_ISCONST "ISCONST"
@@ -1466,50 +1468,50 @@
 
 			lexer_ToggleStringExpansion(true);
 		}
-		| T_OP_ROUND T_LPAREN const T_RPAREN {
-			rpn_Number(&$$, fix_Round($3));
+		| T_OP_ROUND T_LPAREN const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_Round($3, $4));
 		}
-		| T_OP_CEIL T_LPAREN const T_RPAREN {
-			rpn_Number(&$$, fix_Ceil($3));
+		| T_OP_CEIL T_LPAREN const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_Ceil($3, $4));
 		}
-		| T_OP_FLOOR T_LPAREN const T_RPAREN {
-			rpn_Number(&$$, fix_Floor($3));
+		| T_OP_FLOOR T_LPAREN const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_Floor($3, $4));
 		}
-		| T_OP_FDIV T_LPAREN const T_COMMA const T_RPAREN {
-			rpn_Number(&$$, fix_Div($3, $5));
+		| T_OP_FDIV T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_Div($3, $5, $6));
 		}
-		| T_OP_FMUL T_LPAREN const T_COMMA const T_RPAREN {
-			rpn_Number(&$$, fix_Mul($3, $5));
+		| T_OP_FMUL T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_Mul($3, $5, $6));
 		}
-		| T_OP_FMOD T_LPAREN const T_COMMA const T_RPAREN {
-			rpn_Number(&$$, fix_Mod($3, $5));
+		| T_OP_FMOD T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_Mod($3, $5, $6));
 		}
-		| T_OP_POW T_LPAREN const T_COMMA const T_RPAREN {
-			rpn_Number(&$$, fix_Pow($3, $5));
+		| T_OP_POW T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_Pow($3, $5, $6));
 		}
-		| T_OP_LOG T_LPAREN const T_COMMA const T_RPAREN {
-			rpn_Number(&$$, fix_Log($3, $5));
+		| T_OP_LOG T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_Log($3, $5, $6));
 		}
-		| T_OP_SIN T_LPAREN const T_RPAREN {
-			rpn_Number(&$$, fix_Sin($3));
+		| T_OP_SIN T_LPAREN const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_Sin($3, $4));
 		}
-		| T_OP_COS T_LPAREN const T_RPAREN {
-			rpn_Number(&$$, fix_Cos($3));
+		| T_OP_COS T_LPAREN const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_Cos($3, $4));
 		}
-		| T_OP_TAN T_LPAREN const T_RPAREN {
-			rpn_Number(&$$, fix_Tan($3));
+		| T_OP_TAN T_LPAREN const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_Tan($3, $4));
 		}
-		| T_OP_ASIN T_LPAREN const T_RPAREN {
-			rpn_Number(&$$, fix_ASin($3));
+		| T_OP_ASIN T_LPAREN const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_ASin($3, $4));
 		}
-		| T_OP_ACOS T_LPAREN const T_RPAREN {
-			rpn_Number(&$$, fix_ACos($3));
+		| T_OP_ACOS T_LPAREN const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_ACos($3, $4));
 		}
-		| T_OP_ATAN T_LPAREN const T_RPAREN {
-			rpn_Number(&$$, fix_ATan($3));
+		| T_OP_ATAN T_LPAREN const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_ATan($3, $4));
 		}
-		| T_OP_ATAN2 T_LPAREN const T_COMMA const T_RPAREN {
-			rpn_Number(&$$, fix_ATan2($3, $5));
+		| T_OP_ATAN2 T_LPAREN const T_COMMA const opt_q_arg T_RPAREN {
+			rpn_Number(&$$, fix_ATan2($3, $5, $6));
 		}
 		| T_OP_STRCMP T_LPAREN string T_COMMA string T_RPAREN {
 			rpn_Number(&$$, strcmp($3, $5));
@@ -1536,7 +1538,7 @@
 uconst		: const {
 			$$ = $1;
 			if ($$ < 0)
-				fatalerror("Constant mustn't be negative: %d\n", $1);
+				fatalerror("Constant must not be negative: %d\n", $1);
 		}
 ;
 
@@ -1547,6 +1549,17 @@
 ;
 
 const_8bit	: reloc_8bit { $$ = rpn_GetConstVal(&$1); }
+;
+
+opt_q_arg	: %empty { $$ = fix_Precision(); }
+		| T_COMMA const {
+			if ($2 >= 1 && $2 <= 31) {
+				$$ = $2;
+			} else {
+				error("Fixed-point precision must be between 1 and 31\n");
+				$$ = fix_Precision();
+			}
+		}
 ;
 
 string		: T_STRING
--- /dev/null
+++ b/test/asm/fixed-point-specific.asm
@@ -1,0 +1,27 @@
+MACRO compare
+	print "\3: "
+	if _NARG == 4
+		def v1 = \3(\4q\1, \1)
+		def v2 = \3(\4q\2, \2)
+	elif _NARG == 5
+		def v1 = \3(\4q\1, \5q\1, \1)
+		def v2 = \3(\4q\2, \5q\2, \2)
+	endc
+	opt Q\1
+	print "{.4f:v1} == "
+	opt Q\2
+	println "{.4f:v2}"
+ENDM
+
+	compare  8, 16, mul, 6.0, 7.0
+	compare 12, 24, div, 115.625, 9.25
+	compare  7, 14, pow, 3.5, 5.5
+
+	compare  4,  8, sin, 0.25
+	compare  5,  9, cos, 0.75
+	compare  6, 10, asin, 1.0
+	compare  7, 11, acos, 0.0
+
+	compare  3,  6, round, 1.75
+	compare 10, 20, ceil, 123.4
+	compare 13, 17, floor, 567.8
--- /dev/null
+++ b/test/asm/fixed-point-specific.out
@@ -1,0 +1,10 @@
+mul: 42.0000 == 42.0000
+div: 12.5000 == 12.5000
+pow: 982.5938 == 982.5943
+sin: 1.0000 == 1.0000
+cos: 0.0000 == 0.0000
+asin: 0.2500 == 0.2500
+acos: 0.2500 == 0.2500
+round: 2.0000 == 2.0000
+ceil: 124.0000 == 124.0000
+floor: 567.0000 == 567.0000