JavaScript output prints numbers the MBASIC way¶
Status: Done (GitHub issue 9)
Test: tests/regression/integration/test_js_print_format.py
Problem¶
The JavaScript that --compile-js makes printed numbers with JavaScript
rules. PRINT 42 gave 42, not 42. PRINT 1/3 gave
0.3333333333333333. STR$ and WRITE had the same fault.
What the compiler does now¶
The code generator reads the type of each expression with
numeric_digits() in src/number_format.py. The interpreter uses the same
function. The type is 0 (integer), 6 (single) or 16 (double). The compiler
writes this number into the JavaScript next to each value.
The JavaScript runtime has these parts:
_fmt_numand_fmt_printare a port offormat_numberandformat_for_print. They follow the same rounding rules. The code rounds a single twice (7 digits, then 6) from its shortest decimal form. The code rounds a double once from its exact binary value._print_listhandles one PRINT statement. It keeps the cursor column, so the comma zones (14 characters),TABandSPCwork across statements. It also works forPRINT #n. It wraps lines at_width(set byWIDTH, 80 at the start) and follows the TAB and comma rules of real MBASIC. The rules are in PRINT_COLUMNS.md._sgrounds the result of single-precision arithmetic to binary32 (Math.fround). It leaves whole numbers alone, as the interpreter does. The code rounds single literals and the results ofSQR,SIN,COS,TAN,ATN,LOG,EXPandRNDthe same way._strtakes the digits of its argument, soSTR$(1/3#)has 16 digits._write_formatleaves out the padding spaces around numbers._print_usingis a port ofUsingFormatter. For a double, the port starts from the shortest decimal form of the value, asUsingFormatterdoes. The exact binary of 1D-11 is just below 1E-11, and the real binary has no carry there, so##.##^^^^shows 1.00D-11. A fixed field with a value of 1E16 or more prints%, a space and the PRINT form of the number (% 1E+16,% 1.70141E+38for the clamped 10^40). The sign,$and trailing sign are dropped, as on the real binary._toIntrounds halves away from zero and raises Overflow outside -32768 to 32767, like the interpreter.- Arithmetic follows the interpreter (
docs/dev/MBASIC_521_DIVERGENCES_TODO.md, section 1)._sg(single) and_dg(double) turn a result that is infinite in its precision intoOverflow._overflowand_divzeroprint the message, give machine infinity with the sign of the true result, and carry on. WhenON ERRORis armed they raise ERR 6 or ERR 11 instead._div,_pow,_sqr,_log,_idivand_modraiseDivision by zeroorIllegal function call.\andMODround their operands and need 16 bits. _errorgives the error an ERR number. WithON ERRORin the program the main loop catches it, setsERRandERL, and jumps to the handler.RESUME NEXTgoes to the statement after the one that failed (docs/dev/JS_CONTROL_FLOW.md)._valand_radix_wordfollowval_textandradix_word.&HFFFFis -1 andHEX$(-1)isFFFF.- A comparison gives -1 or 0. AND, OR, XOR, EQV, IMP and NOT are 16-bit
integer operators (see
docs/dev/JS_LOGICAL_OPERATORS.md).
Changes in the interpreter¶
STR$uses the type of its argument (it always used single before).WRITEuses the number format ofPRINT(it printed Python floats).- The parser keeps the separator list in step with the expression list.
PRINT ,XandPRINT A,,Bget an empty string item.PRINT "A" "B"gets an implied;. /and^of two integers are single, so5%/2%is 2.5.- The parser accepts a sign on the exponent:
2^-3is .125 and0^-1is a division by zero. It was a syntax error.
Test method¶
fixtures/js_print_format/*.bas run in the interpreter and in Node.js. The
two outputs must be equal byte for byte. The test compares the issue example with
real MBASIC 5.21. A random test compares _fmt_print with format_for_print
for 3000 values.
fixtures/number_semantics/*.bas have a .real file with the output of real
MBASIC 5.21. The interpreter test and the Node.js test both compare with it.
They cover overflow, domain errors, VAL, &H and &O constants, and
HEX$ and OCT$.
Differences that remain¶
Real MBASIC 5.21 and the interpreter differ in these cases. The JavaScript output follows the interpreter.
- A double literal with 16 or more digits (
3.14159265358979#,1D-20) is not the same as in real MBASIC. Real MBASIC reads decimal text a little inexactly (seeto_singleinsrc/number_format.py). SQR(2#)and the other maths functions keep double precision here. Real MBASIC gives a single.- Doubles that come from MBF arithmetic can differ in the last digit.
1D300^2andEXP(1000#)give a double machine infinity here. Real MBASIC gives a single one. The MBF range is not emulated (see MBASIC_521_DIVERGENCES_TODO.md, section 10).- A single value below about 2.9E-39 prints as a binary32 denormal
(
1.4013E-45). Real MBASIC reads such a value as 0. The interpreter prints the same text as the JavaScript. ^with a double base and a fractional exponent keeps double precision (X#^.5). Real MBASIC gives a single.- A fatal error stops the JavaScript with
<message> in <line>, for exampleOverflow in 20, as real MBASIC does. The JavaScript starts a new line first when the cursor is not in column 0, as real MBASIC does. LPRINTshows the number format only.PRINT #n, USINGworks.- A single-precision whole number above 2^24 is not rounded, because the interpreter keeps Python integers exact.