From 5fd636ac4bc99c6efb8c7bbdcfeb1fa4e2d812db Mon Sep 17 00:00:00 2001 From: Rangi <35663410+Rangi42@users.noreply.github.com> Date: Sun, 21 Feb 2021 13:14:44 -0800 Subject: [PATCH] Implement floored `/` and divisor-sign `%` operators (#745) Fixes #703 --- src/asm/rgbasm.5 | 16 ++++++++++++---- src/asm/rpn.c | 22 +++++++++++++++++++--- src/link/patch.c | 20 ++++++++++++++++++-- test/asm/div-mod.asm | 36 ++++++++++++++++++++++++++++++++++++ test/asm/div-mod.err | 0 test/asm/div-mod.out | 0 test/asm/div-mod.out.bin | 0 test/asm/math.out.bin | 0 8 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 test/asm/div-mod.asm create mode 100644 test/asm/div-mod.err create mode 100644 test/asm/div-mod.out create mode 100644 test/asm/div-mod.out.bin create mode 100644 test/asm/math.out.bin diff --git a/src/asm/rgbasm.5 b/src/asm/rgbasm.5 index e0956f2a..9cfb04c7 100644 --- a/src/asm/rgbasm.5 +++ b/src/asm/rgbasm.5 @@ -143,9 +143,16 @@ A great number of operators you can use in expressions are available (listed fro 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 @@ still evaluates both operands of 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). diff --git a/src/asm/rpn.c b/src/asm/rpn.c index 08ff8773..d02f7449 100644 --- a/src/asm/rpn.c +++ b/src/asm/rpn.c @@ -283,6 +283,22 @@ static int32_t shift(int32_t shiftee, int32_t amount) } } +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 @@ void rpn_BinaryOp(enum RPNCommand op, struct Expression *expr, 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) diff --git a/src/link/patch.c b/src/link/patch.c index 5d576e64..c7fe78df 100644 --- 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 @@ static int32_t computeRPNExpr(struct Patch const *patch, popRPN(); value = INT32_MAX; } else { - value = popRPN() / value; + value = divide(popRPN(), value); } break; case RPN_MOD: @@ -247,7 +263,7 @@ static int32_t computeRPNExpr(struct Patch const *patch, popRPN(); value = 0; } else { - value = popRPN() % value; + value = modulo(popRPN(), value); } break; case RPN_UNSUB: diff --git a/test/asm/div-mod.asm b/test/asm/div-mod.asm new file mode 100644 index 00000000..9d9d2a2f --- /dev/null +++ b/test/asm/div-mod.asm @@ -0,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:: diff --git a/test/asm/div-mod.err b/test/asm/div-mod.err new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/div-mod.out b/test/asm/div-mod.out new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/div-mod.out.bin b/test/asm/div-mod.out.bin new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/math.out.bin b/test/asm/math.out.bin new file mode 100644 index 00000000..e69de29b