shithub: rgbds

Download patch

ref: 98a6dffbca71cf33225e07014c1c1793e45c8e00
parent: 889302a9e227be63beb434e8d57c430d496b2511
author: Rangi <35663410+Rangi42@users.noreply.github.com>
date: Sun Sep 4 14:47:32 EDT 2022

Implement `opt Q` for fixed-point precision, and `q` literals (e.g. `12.34q8`) (#958)

Fixes #957

Co-authored-by: ISSOtm <eldredhabert0@gmail.com>

--- a/contrib/bash_compl/_rgbasm.bash
+++ b/contrib/bash_compl/_rgbasm.bash
@@ -39,6 +39,7 @@
 		[M]="dependfile:glob-*.mk *.d"
 		[o]="output:glob-*.o"
 		[p]="pad-value:unk"
+		[Q]="q-precision:unk"
 		[r]="recursion-depth:unk"
 		[W]="warning:warning"
 	)
--- a/contrib/zsh_compl/_rgbasm
+++ b/contrib/zsh_compl/_rgbasm
@@ -56,6 +56,7 @@
 	'*'-MQ"+[Add a target to the rules]:target:_files -g '*.{d,mk,o}'"
 	'(-o --output)'{-o,--output}'+[Output file]:output file:_files'
 	'(-p --pad-value)'{-p,--pad-value}'+[Set padding byte]:padding byte:'
+	'(-Q --q-precision)'{-Q,--q-precision}'+[Set fixed-point precision]:precision:'
 	'(-r --recursion-depth)'{-r,--recursion-depth}'+[Set maximum recursion depth]:depth:'
 	'(-W --warning)'{-W,--warning}'+[Toggle warning flags]:warning flag:_rgbasm_warnings'
 
--- a/include/asm/fixpoint.h
+++ b/include/asm/fixpoint.h
@@ -11,6 +11,9 @@
 
 #include <stdint.h>
 
+extern uint8_t fixPrecision;
+
+double fix_PrecisionFactor(void);
 void fix_Print(int32_t i);
 int32_t fix_Sin(int32_t i);
 int32_t fix_Cos(int32_t i);
--- a/include/asm/opt.h
+++ b/include/asm/opt.h
@@ -14,7 +14,8 @@
 
 void opt_B(char const chars[2]);
 void opt_G(char const chars[4]);
-void opt_P(uint8_t fill);
+void opt_P(uint8_t padByte);
+void opt_Q(uint8_t precision);
 void opt_L(bool optimize);
 void opt_W(char const *flag);
 void opt_Parse(char const *option);
--- a/man/rgbasm.1
+++ b/man/rgbasm.1
@@ -25,6 +25,7 @@
 .Op Fl MQ Ar target_file
 .Op Fl o Ar out_file
 .Op Fl p Ar pad_value
+.Op Fl Q Ar fix_precision
 .Op Fl r Ar recursion_depth
 .Op Fl W Ar warning
 .Ar
@@ -148,6 +149,13 @@
 .It Fl p Ar pad_value , Fl Fl pad-value Ar pad_value
 When padding an image, pad with this value.
 The default is 0x00.
+.It Fl Q Ar fix_precision , Fl Fl q-precision Ar fix_precision
+Use this as the precision of fixed-point numbers after the decimal point, unless they specify their own precision.
+The default is 16, so fixed-point numbers are Q16.16 (since they are 32-bit integers).
+The argument may start with a
+.Ql \&.
+to match the Q notation, for example,
+.Ql Fl Q Ar .16 .
 .It Fl r Ar recursion_depth , Fl Fl recursion-depth Ar recursion_depth
 Specifies the recursion depth past which RGBASM will assume being in an infinite loop.
 The default is 64.
--- a/man/rgbasm.5
+++ b/man/rgbasm.5
@@ -208,13 +208,14 @@
 The instructions in the macro-language generally require constant expressions.
 .Ss Numeric formats
 There are a number of numeric formats.
-.Bl -column -offset indent "Fixed point (Q16.16)" "Prefix"
+.Bl -column -offset indent "Precise fixed-point" "Prefix"
 .It Sy Format type Ta Sy Prefix Ta Sy Accepted characters
 .It Hexadecimal Ta $ Ta 0123456789ABCDEF
 .It Decimal Ta none Ta 0123456789
 .It Octal Ta & Ta 01234567
 .It Binary Ta % Ta 01
-.It Fixed point (Q16.16) Ta none Ta 01234.56789
+.It Fixed-point Ta none Ta 01234.56789
+.It Precise fixed-point Ta none Ta 12.34q8
 .It Character constant Ta none Ta \(dqABYZ\(dq
 .It Gameboy graphics Ta \` Ta 0123
 .El
@@ -301,9 +302,19 @@
 .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 65536ths 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).
-Since they are still akin to integers, you can use them in normal integer expressions, and some integer operators like
+Fixed-point numbers are basically normal (32-bit) integers, which count fractions instead of whole numbers.
+They offer better precision than integers but limit the range of values.
+By default, the upper 16 bits are used for the integer part and the lower 16 bits are used for the fraction (65536ths).
+The default number of fractional bits can be changed with the
+.Fl Q
+command-line option.
+You can also specify a precise fixed-point value by appending a
+.Dq q
+to it followed by the number of fractional bits, such as
+.Ql 12.34q8 .
+.Pp
+Since fixed-point values are still just integers, you can use them in normal integer expressions.
+Some integer operators like
 .Sq +
 and
 .Sq -
@@ -317,9 +328,9 @@
 .EN
 .Bl -column -offset indent "ATAN2(x, y)"
 .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 FMOD x y Ta $x % y$
+.It Fn DIV x y Ta Fixed-point division $( x \[di] y ) \[mu] ( 2 ^ precision )$
+.It Fn MUL x y Ta Fixed-point multiplication $( x \[mu] y ) \[di] ( 2 ^ precision )$
+.It Fn FMOD x y Ta Fixed-point modulo $( x % y ) \[di] ( 2 ^ precision )$
 .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
@@ -2034,7 +2045,7 @@
 The options that
 .Ic OPT
 can modify are currently:
-.Cm b , g , p , r , h , L ,
+.Cm b , g , p , Q , r , h , L ,
 and
 .Cm W .
 The Boolean flag options
--- a/src/asm/fixpoint.c
+++ b/src/asm/fixpoint.c
@@ -17,17 +17,24 @@
 #include "asm/symbol.h"
 #include "asm/warning.h"
 
-#define fix2double(i)	((double)((i) / 65536.0))
-#define double2fix(d)	((int32_t)round((d) * 65536.0))
-
-// pi radians == 32768 fixed-point "degrees"
-#define fdeg2rad(f)	((f) * (M_PI / 32768.0))
-#define rad2fdeg(r)	((r) * (32768.0 / M_PI))
-
 #ifndef M_PI
 #define M_PI 3.14159265358979323846
 #endif
 
+#define fix2double(i)	((double)((i) / fix_PrecisionFactor()))
+#define double2fix(d)	((int32_t)round((d) * fix_PrecisionFactor()))
+
+// pi*2 radians == 2**fixPrecision fixed-point "degrees"
+#define fdeg2rad(f)	((f) * (M_PI * 2) / fix_PrecisionFactor())
+#define rad2fdeg(r)	((r) * fix_PrecisionFactor() / (M_PI * 2))
+
+uint8_t fixPrecision;
+
+double fix_PrecisionFactor(void)
+{
+	return pow(2.0, fixPrecision);
+}
+
 void fix_Print(int32_t i)
 {
 	uint32_t u = i;
@@ -38,7 +45,7 @@
 		sign = "-";
 	}
 
-	printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> 16,
+	printf("%s%" PRIu32 ".%05" PRIu32, sign, u >> fixPrecision,
 	       ((uint32_t)(fix2double(u) * 100000 + 0.5)) % 100000);
 }
 
--- a/src/asm/format.c
+++ b/src/asm/format.c
@@ -15,6 +15,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "asm/fixpoint.h"
 #include "asm/format.h"
 #include "asm/warning.h"
 
@@ -225,7 +226,7 @@
 	} else if (fmt->type == 'f') {
 		// Special case for fixed-point
 
-		// Default fractional width (C's is 6 for "%f"; here 5 is enough)
+		// Default fractional width (C's is 6 for "%f"; here 5 is enough for Q16.16)
 		size_t fracWidth = fmt->hasFrac ? fmt->fracWidth : 5;
 
 		if (fracWidth > 255) {
@@ -234,7 +235,8 @@
 			fracWidth = 255;
 		}
 
-		snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth, value / 65536.0);
+		snprintf(valueBuf, sizeof(valueBuf), "%.*f", (int)fracWidth,
+			 value / fix_PrecisionFactor());
 	} else {
 		char const *spec = fmt->type == 'd' ? "%" PRId32
 				 : fmt->type == 'u' ? "%" PRIu32
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -27,6 +27,7 @@
 #include "platform.h" // For `ssize_t`
 
 #include "asm/lexer.h"
+#include "asm/fixpoint.h"
 #include "asm/format.h"
 #include "asm/fstack.h"
 #include "asm/macro.h"
@@ -1125,39 +1126,66 @@
 	return value;
 }
 
-static uint32_t readFractionalPart(int32_t integer)
+static uint32_t readFractionalPart(uint32_t integer)
 {
 	uint32_t value = 0, divisor = 1;
+	uint8_t precision = 0;
+	enum {
+		READFRACTIONALPART_DIGITS,
+		READFRACTIONALPART_PRECISION,
+		READFRACTIONALPART_PRECISION_DIGITS,
+	} state = READFRACTIONALPART_DIGITS;
 
 	for (;; shiftChar()) {
 		int c = peek();
 
-		if (c == '_')
-			continue;
-		else if (c < '0' || c > '9')
-			break;
-		if (divisor > (UINT32_MAX - (c - '0')) / 10) {
-			warning(WARNING_LARGE_CONSTANT,
-				"Precision of fixed-point constant is too large\n");
-			// Discard any additional digits
-			shiftChar();
-			while (c = peek(), (c >= '0' && c <= '9') || c == '_')
+		if (state == READFRACTIONALPART_DIGITS) {
+			if (c == '_') {
+				continue;
+			} else if (c == 'q' || c == 'Q') {
+				state = READFRACTIONALPART_PRECISION;
+				continue;
+			} else if (c < '0' || c > '9') {
+				break;
+			}
+			if (divisor > (UINT32_MAX - (c - '0')) / 10) {
+				warning(WARNING_LARGE_CONSTANT,
+					"Precision of fixed-point constant is too large\n");
+				// Discard any additional digits
 				shiftChar();
-			break;
+				while (c = peek(), (c >= '0' && c <= '9') || c == '_')
+					shiftChar();
+				break;
+			}
+			value = value * 10 + (c - '0');
+			divisor *= 10;
+		} else {
+			if (c == '.' && state == READFRACTIONALPART_PRECISION) {
+				state = READFRACTIONALPART_PRECISION_DIGITS;
+				continue;
+			} else if (c < '0' || c > '9') {
+				break;
+			}
+			precision = precision * 10 + (c - '0');
 		}
-		value = value * 10 + (c - '0');
-		divisor *= 10;
 	}
 
-	if (integer > INT16_MAX || integer < INT16_MIN)
+	if (precision == 0) {
+		if (state >= READFRACTIONALPART_PRECISION)
+			error("Invalid fixed-point constant, no significant digits after 'q'\n");
+		precision = fixPrecision;
+	} else if (precision > 31) {
+		error("Fixed-point constant precision must be between 1 and 31\n");
+		precision = fixPrecision;
+	}
+
+	if (integer >= ((uint32_t)1 << (precision - 1)))
 		warning(WARNING_LARGE_CONSTANT, "Magnitude of fixed-point constant is too large\n");
 
-	// Cast to unsigned avoids UB if shifting discards bits
-	integer = (uint32_t)integer << 16;
 	// Cast to unsigned avoids undefined overflow behavior
-	uint16_t fractional = (uint16_t)round(value * 65536.0 / divisor);
+	uint32_t fractional = (uint32_t)round((double)value / divisor * pow(2.0, precision));
 
-	return (uint32_t)integer | (fractional * (integer >= 0 ? 1 : -1));
+	return (integer << precision) | fractional;
 }
 
 char binDigits[2];
@@ -1741,6 +1769,8 @@
 
 static int yylex_NORMAL(void)
 {
+	uint32_t num = 0;
+
 	for (;;) {
 		int c = nextChar();
 		char secondChar;
@@ -1912,10 +1942,12 @@
 		case '7':
 		case '8':
 		case '9':
-			yylval.constValue = readNumber(10, c - '0');
+			num = readNumber(10, c - '0');
 			if (peek() == '.') {
 				shiftChar();
-				yylval.constValue = readFractionalPart(yylval.constValue);
+				yylval.constValue = readFractionalPart(num);
+			} else {
+				yylval.constValue = num;
 			}
 			return T_NUMBER;
 
--- a/src/asm/main.c
+++ b/src/asm/main.c
@@ -19,6 +19,7 @@
 #include <time.h>
 
 #include "asm/charmap.h"
+#include "asm/fixpoint.h"
 #include "asm/format.h"
 #include "asm/fstack.h"
 #include "asm/lexer.h"
@@ -86,7 +87,7 @@
 }
 
 // Short options
-static const char *optstring = "b:D:Eg:Hhi:LlM:o:p:r:VvW:w";
+static const char *optstring = "b:D:Eg:Hhi:LlM:o:p:Q:r:VvW:w";
 
 // Variables for the long-only options
 static int depType; // Variants of `-M`
@@ -116,6 +117,7 @@
 	{ "MQ",               required_argument, &depType, 'Q' },
 	{ "output",           required_argument, NULL,     'o' },
 	{ "pad-value",        required_argument, NULL,     'p' },
+	{ "q-precision",      required_argument, NULL,     'Q' },
 	{ "recursion-depth",  required_argument, NULL,     'r' },
 	{ "version",          no_argument,       NULL,     'V' },
 	{ "verbose",          no_argument,       NULL,     'v' },
@@ -128,7 +130,8 @@
 	fputs(
 "Usage: rgbasm [-EHhLlVvw] [-b chars] [-D name[=value]] [-g chars] [-i path]\n"
 "              [-M depend_file] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
-"              [-o out_file] [-p pad_value] [-r depth] [-W warning] <file>\n"
+"              [-o out_file] [-p pad_value] [-Q precision] [-r depth]\n"
+"              [-W warning] <file>\n"
 "Useful options:\n"
 "    -E, --export-all         export all labels\n"
 "    -M, --dependfile <path>  set the output dependency file\n"
@@ -170,6 +173,7 @@
 	opt_B("01");
 	opt_G("0123");
 	opt_P(0);
+	opt_Q(16);
 	haltnop = true;
 	warnOnHaltNop = true;
 	optimizeLoads = true;
@@ -250,17 +254,34 @@
 			out_SetFileName(musl_optarg);
 			break;
 
-			unsigned long fill;
+			unsigned long padByte;
 		case 'p':
-			fill = strtoul(musl_optarg, &ep, 0);
+			padByte = strtoul(musl_optarg, &ep, 0);
 
 			if (musl_optarg[0] == '\0' || *ep != '\0')
 				errx("Invalid argument for option 'p'");
 
-			if (fill > 0xFF)
+			if (padByte > 0xFF)
 				errx("Argument for option 'p' must be between 0 and 0xFF");
 
-			opt_P(fill);
+			opt_P(padByte);
+			break;
+
+			unsigned long precision;
+			const char *precisionArg;
+		case 'Q':
+			precisionArg = musl_optarg;
+			if (precisionArg[0] == '.')
+				precisionArg++;
+			precision = strtoul(precisionArg, &ep, 0);
+
+			if (musl_optarg[0] == '\0' || *ep != '\0')
+				errx("Invalid argument for option 'Q'");
+
+			if (precision < 1 || precision > 31)
+				errx("Argument for option 'Q' must be between 1 and 31");
+
+			opt_Q(precision);
 			break;
 
 		case 'r':
--- a/src/asm/opt.c
+++ b/src/asm/opt.c
@@ -14,6 +14,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "asm/fixpoint.h"
 #include "asm/fstack.h"
 #include "asm/lexer.h"
 #include "asm/main.h"
@@ -23,7 +24,8 @@
 struct OptStackEntry {
 	char binary[2];
 	char gbgfx[4];
-	int32_t fillByte;
+	uint8_t fixPrecision;
+	uint8_t fillByte;
 	bool haltnop;
 	bool warnOnHaltNop;
 	bool optimizeLoads;
@@ -47,11 +49,16 @@
 	lexer_SetGfxDigits(chars);
 }
 
-void opt_P(uint8_t fill)
+void opt_P(uint8_t padByte)
 {
-	fillByte = fill;
+	fillByte = padByte;
 }
 
+void opt_Q(uint8_t precision)
+{
+	fixPrecision = precision;
+}
+
 void opt_R(size_t newDepth)
 {
 	fstk_NewRecursionDepth(newDepth);
@@ -103,18 +110,41 @@
 	case 'p':
 		if (strlen(&s[1]) <= 2) {
 			int result;
-			unsigned int fillchar;
+			unsigned int padByte;
 
-			result = sscanf(&s[1], "%x", &fillchar);
-			if (result != EOF && result != 1)
+			result = sscanf(&s[1], "%x", &padByte);
+			if (result != 1)
 				error("Invalid argument for option 'p'\n");
+			else if (padByte > 0xFF)
+				error("Argument for option 'p' must be between 0 and 0xFF\n");
 			else
-				opt_P(fillchar);
+				opt_P(padByte);
 		} else {
 			error("Invalid argument for option 'p'\n");
 		}
 		break;
 
+		const char *precisionArg;
+	case 'Q':
+		precisionArg = &s[1];
+		if (precisionArg[0] == '.')
+			precisionArg++;
+		if (strlen(precisionArg) <= 2) {
+			int result;
+			unsigned int precision;
+
+			result = sscanf(precisionArg, "%u", &precision);
+			if (result != 1)
+				error("Invalid argument for option 'Q'\n");
+			else if (precision < 1 || precision > 31)
+				error("Argument for option 'Q' must be between 1 and 31\n");
+			else
+				opt_Q(precision);
+		} else {
+			error("Invalid argument for option 'Q'\n");
+		}
+		break;
+
 	case 'r': {
 		++s; // Skip 'r'
 		while (isblank(*s))
@@ -231,6 +261,8 @@
 	entry->gbgfx[2] = gfxDigits[2];
 	entry->gbgfx[3] = gfxDigits[3];
 
+	entry->fixPrecision = fixPrecision; // Pulled from fixpoint.h
+
 	entry->fillByte = fillByte; // Pulled from section.h
 
 	entry->haltnop = haltnop; // Pulled from main.h
@@ -259,6 +291,7 @@
 	opt_B(entry->binary);
 	opt_G(entry->gbgfx);
 	opt_P(entry->fillByte);
+	opt_Q(entry->fixPrecision);
 	opt_H(entry->warnOnHaltNop);
 	opt_h(entry->haltnop);
 	opt_L(entry->optimizeLoads);
--- a/test/asm/fixed-point-precision.asm
+++ b/test/asm/fixed-point-precision.asm
@@ -12,3 +12,11 @@
 
 fr = MUL(20.0, 0.32)
 	println "32% of 20 = {f:fr} (~{.2f:fr}) (~~{.0f:fr})"
+
+q8 = 1.25q8
+q16 = 1.25Q16
+q24 = 1.25q.24
+	println "Q8 ${x:q8} Q16 ${x:q16} Q24 ${x:q24}"
+
+qerr = 1.25q32
+	println qerr
--- a/test/asm/fixed-point-precision.err
+++ b/test/asm/fixed-point-precision.err
@@ -1,0 +1,3 @@
+error: fixed-point-precision.asm(21):
+    Fixed-point constant precision must be between 1 and 31
+error: Assembly aborted (1 error)!
--- a/test/asm/fixed-point-precision.out
+++ b/test/asm/fixed-point-precision.out
@@ -4,3 +4,5 @@
 `16.12`: 16.119995 -> $00101eb8
 `6.283185`: 6.283188 -> $0006487f
 32% of 20 = 6.40015 (~6.40) (~~6)
+Q8 $140 Q16 $14000 Q24 $1400000
+$14000
--- /dev/null
+++ b/test/asm/opt-Q.asm
@@ -1,0 +1,18 @@
+MACRO test
+	PUSHO
+	OPT Q\1
+	print STRFMT("Q%4s", "\1")
+	def n = 1.14159
+	println STRFMT(" -> %032b", n)
+	POPO
+ENDM
+
+	for x, 1, 32
+		if x < 16
+			test .{d:x}
+		else
+			test {d:x}
+		endc
+	endr
+	test .0 ; error
+	test 32 ; error
--- /dev/null
+++ b/test/asm/opt-Q.err
@@ -1,0 +1,7 @@
+warning: opt-Q.asm(10) -> opt-Q.asm::REPT~1(12) -> opt-Q.asm::test(5): [-Wlarge-constant]
+    Magnitude of fixed-point constant is too large
+error: opt-Q.asm(17) -> opt-Q.asm::test(3):
+    Argument for option 'Q' must be between 1 and 31
+error: opt-Q.asm(18) -> opt-Q.asm::test(3):
+    Argument for option 'Q' must be between 1 and 31
+error: Assembly aborted (2 errors)!
--- /dev/null
+++ b/test/asm/opt-Q.out
@@ -1,0 +1,33 @@
+Q  .1 -> 00000000000000000000000000000010
+Q  .2 -> 00000000000000000000000000000101
+Q  .3 -> 00000000000000000000000000001001
+Q  .4 -> 00000000000000000000000000010010
+Q  .5 -> 00000000000000000000000000100101
+Q  .6 -> 00000000000000000000000001001001
+Q  .7 -> 00000000000000000000000010010010
+Q  .8 -> 00000000000000000000000100100100
+Q  .9 -> 00000000000000000000001001001000
+Q .10 -> 00000000000000000000010010010001
+Q .11 -> 00000000000000000000100100100010
+Q .12 -> 00000000000000000001001001000100
+Q .13 -> 00000000000000000010010010001000
+Q .14 -> 00000000000000000100100100010000
+Q .15 -> 00000000000000001001001000100000
+Q  16 -> 00000000000000010010010000111111
+Q  17 -> 00000000000000100100100001111110
+Q  18 -> 00000000000001001001000011111101
+Q  19 -> 00000000000010010010000111111010
+Q  20 -> 00000000000100100100001111110100
+Q  21 -> 00000000001001001000011111101000
+Q  22 -> 00000000010010010000111111010000
+Q  23 -> 00000000100100100001111110011111
+Q  24 -> 00000001001001000011111100111110
+Q  25 -> 00000010010010000111111001111100
+Q  26 -> 00000100100100001111110011111000
+Q  27 -> 00001001001000011111100111110000
+Q  28 -> 00010010010000111111001111100000
+Q  29 -> 00100100100001111110011111000000
+Q  30 -> 01001001000011111100111110000001
+Q  31 -> 10010010000111111001111100000010
+Q  .0 -> 00000000000000010010010000111111
+Q  32 -> 00000000000000010010010000111111
--- a/test/asm/opt.asm
+++ b/test/asm/opt.asm
@@ -3,11 +3,13 @@
 	opt !h, !L ; already the default, but tests parsing "!"
 
 pusho
-	opt p42, h, L, Wno-div
+	opt p42, Q.4, h, L, Wno-div
 	ds 1
 	ld [$ff88], a
 	halt
 	println $8000_0000 / -1
+	def n = 3.14
+	println "{x:n} = {f:n}"
 popo
 
 	opt H, l
@@ -16,3 +18,5 @@
 	ld [$ff88], a
 	halt
 	println $8000_0000 / -1
+	def n = 3.14
+	println "{x:n} = {f:n}"
--- a/test/asm/opt.err
+++ b/test/asm/opt.err
@@ -1,2 +1,2 @@
-warning: opt.asm(18): [-Wdiv]
+warning: opt.asm(20): [-Wdiv]
     Division of -2147483648 by -1 yields -2147483648
--- a/test/asm/opt.out
+++ b/test/asm/opt.out
@@ -1,2 +1,4 @@
 $80000000
+32 = 3.12500
 $80000000
+323d7 = 3.14000