ref: 5fd636ac4bc99c6efb8c7bbdcfeb1fa4e2d812db
parent: a6d844a9a5bc0c52a6bd4a97d0d15e9ce0a5acaf
author: Rangi <[email protected]>
date: Sun Feb 21 08:14:44 EST 2021
Implement floored `/` and divisor-sign `%` operators (#745) Fixes #703
--- a/src/asm/rgbasm.5
+++ b/src/asm/rgbasm.5
@@ -143,9 +143,16 @@
complements a value by inverting all its bits.
.Pp
.Ic %
-is used to get the remainder of the corresponding division.
-.Sq 5 % 2
-is 1.
+is used to get the remainder of the corresponding division, so that
+.Sq a / b * b + a % b == a
+is always true.
+The result has the same sign as the divisor.
+This makes
+.Sq a % b .
+equal to
+.Sq (a + b) % b
+or
+.Sq (a - b) % b .
.Pp
Shifting works by shifting all bits in the left operand either left
.Pq Sq <<
@@ -168,7 +175,8 @@
and
.Sq || .
.Pp
-! returns 1 if the operand was 0, and 0 otherwise.
+.Ic \&!
+returns 1 if the operand was 0, and 0 otherwise.
.Ss Fixed‐point Expressions
Fixed-point numbers are basically normal (32-bit) integers, which count 65536th's instead of entire units, offering better precision than integers but limiting the range of values.
The upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
--- a/src/asm/rpn.c
+++ b/src/asm/rpn.c
@@ -283,6 +283,22 @@
}
}
+static int32_t divide(int32_t dividend, int32_t divisor)
+{
+ // Adjust division to floor toward negative infinity,
+ // not truncate toward zero
+ return dividend / divisor - ((dividend % divisor < 0) != (divisor < 0));
+}
+
+static int32_t modulo(int32_t dividend, int32_t divisor)
+{
+ int32_t remainder = dividend % divisor;
+
+ // Adjust modulo to have the sign of the divisor,
+ // not the sign of the dividend
+ return remainder + divisor * ((remainder < 0) != (divisor < 0));
+}
+
static int32_t exponent(int32_t base, uint32_t power)
{
int32_t result = 1;
@@ -410,17 +426,17 @@
PRId32 "\n", INT32_MIN, INT32_MIN);
expr->nVal = INT32_MIN;
} else {
- expr->nVal = src1->nVal / src2->nVal;
+ expr->nVal = divide(src1->nVal, src2->nVal);
}
break;
case RPN_MOD:
if (src2->nVal == 0)
- fatalerror("Division by zero\n");
+ fatalerror("Modulo by zero\n");
if (src1->nVal == INT32_MIN && src2->nVal == -1)
expr->nVal = 0;
else
- expr->nVal = src1->nVal % src2->nVal;
+ expr->nVal = modulo(src1->nVal, src2->nVal);
break;
case RPN_EXP:
if (src2->nVal < 0)
--- a/src/link/patch.c
+++ b/src/link/patch.c
@@ -21,6 +21,22 @@
#include "extern/err.h"
+static int32_t divide(int32_t dividend, int32_t divisor)
+{
+ // Adjust division to floor toward negative infinity,
+ // not truncate toward zero
+ return dividend / divisor - ((dividend % divisor < 0) != (divisor < 0));
+}
+
+static int32_t modulo(int32_t dividend, int32_t divisor)
+{
+ int32_t remainder = dividend % divisor;
+
+ // Adjust modulo to have the sign of the divisor,
+ // not the sign of the dividend
+ return remainder + divisor * ((remainder < 0) != (divisor < 0));
+}
+
static int32_t exponent(int32_t base, uint32_t power)
{
int32_t result = 1;
@@ -235,7 +251,7 @@
popRPN();
value = INT32_MAX;
} else {
- value = popRPN() / value;
+ value = divide(popRPN(), value);
}
break;
case RPN_MOD:
@@ -247,7 +263,7 @@
popRPN();
value = 0;
} else {
- value = popRPN() % value;
+ value = modulo(popRPN(), value);
}
break;
case RPN_UNSUB:
--- /dev/null
+++ b/test/asm/div-mod.asm
@@ -1,0 +1,36 @@
+_ASM equ 0
+
+test: MACRO
+; Test RGBASM
+V equs "_ASM +"
+ static_assert \#
+ PURGE V
+; Test RGBLINK
+V equs "_LINK +"
+ assert \#
+ PURGE V
+ENDM
+
+for x, -300, 301
+ for y, -x - 1, x + 2
+ if y != 0
+q = x / y
+r = x % y
+ test (V (q * y + r)) == (V x)
+ test (V (x + y) % y) == (V r)
+ test (V (x - y) % y) == (V r)
+ endc
+ endr
+endr
+
+for x, -300, 301
+ for p, 31
+y = 2 ** p
+r = x % y
+m = x & (y - 1)
+ test (V r) == (V m)
+ endr
+endr
+
+SECTION "LINK", ROM0
+_LINK::