console: %d prints Number(value), as node does

RO
21d ago
+77-73

Stacked on #45935. This PR holds one decision: what %d prints.

Problem

  • console.log('%d', 3.9) prints 3. Node prints 3.9. In Bun, util.format('%d', 3.9), new console.Console() and the console of a node:worker_threads Worker print 3.9 too.
  • %d and %i have shared one integer conversion since 2022 (d3a93d5273). The WHATWG console formatter has parseInt(value, 10) for both. Node has Number(value) for %d.

Change

  • %d prints Number(value): with its fraction, its exponent form and the sign of its zero. %i keeps the integer.
  • This changes behaviour. It is not a fix. A maintainer must choose node's text or the integer. To keep the integer, close this PR: #45935 does not depend on it.
  • Verified: test/js/web/console/console-log.test.ts (the base branch fails 5 tests). 0 of 336 table cells differ from node v26.3.0.

Background

  • The global console is the object that node:console exports. Its native format scanner is the only %d in Bun that truncates.
  • #45935 makes %i and %f convert as node does, prints BigInt arguments and makes the digits of %d exact. It leaves %d an integer.

Downsides

  • %d output changes on 38 of 112 table values: 3.9, 1e21, -0, Infinity, '3.14'. A program or a snapshot that needs the integer must use %i.
  • %d of 1234.5 costs 316 more instructions than on the base branch (2,263 to 2,579 for the call). It prints through the double formatter.
  • After %d of a Number with a fraction, a trailing array can break onto new lines earlier: 5 of 915 swept calls.
Notes

The 38 values, base branch to this PR (this PR equals node on each):

valuebasethis PR
3.9, -3.9, 0.5, 0.1 + 0.23, -3, 0, 03.9, -3.9, 0.5, 0.30000000000000004
-0.5, -0.9999999, -00-0.5, -0.9999999, -0
1e21, -1e21, 1.5e300, Number.MAX_VALUE1, -1, 1, 11e+21, -1e+21, 1.5e+300, 1.7976931348623157e+308
0.000001, 0.00000099, 5e-7, -8.5e-120, 9, 5, -80.000001, 9.9e-7, 5e-7, -8.5e-12
Number.MIN_VALUE, Number.MIN_VALUE * 1.55, 15e-324, 1e-323
2147483648.7, -2147483649.72147483648, -21474836492147483648.7, -2147483649.7
999999999999998.25, -1000000000000000.25999999999999998, -1000000000000000999999999999998.2, -1000000000000000.2
Infinity, -Infinity, 'Infinity', '-Infinity', '+Infinity', '1e400', '-1e400'NaNInfinity or -Infinity
'-0', '-0.0', '-.5', '.5'0-0, -0, -0.5, 0.5
'\u00a042.5', '\ufeff42.5'4242.5
[3.9], { toString: () => '8.5' }, new Number(4.5), new String('6.5')3, 8, 4, 63.9, 8.5, 4.5, 6.5

For node's %d.

  • Node documents console.log as util.format of its arguments, and prints 3.9.
  • Three of the four %d paths in Bun print the Number today: util.format, new console.Console(), and the console of a node:worker_threads Worker. #39592 moves that Worker console onto the native scanner.
  • 5,000 generated strings and 17,644 generated Numbers (fixed seeds): 0 lines differ from util.format under %d, %i and %f on this branch.

For the integer.

  • %d has printed an integer since d3a93d5273 (January 2022). #8733 wrote these arms from the WHATWG formatter, which has Call(%parseInt%, undefined, « current, 10 ») for %d and %i.
  • console-log.expected.txt lines 206, 207, 219 and 220 hold the integer. This PR changes them to node's values.
  • Bun.WebView with console: globalThis.console replays a page's console calls through this scanner. With the WebKit backend a page's %d of 3.9 would print 3.9 where Safari prints 3. That backend was not run.

Cost. Release builds. User instructions of one console.log call, main 017b2439a7, base branch, this PR:

callmainbasethis PR
%d of an int322,3372,3422,344
%d of 17000000000002,4342,3122,314
%d of 1234.52,3932,2632,579
%d of '42'2,6432,5852,589
%d of undefined2,2112,2932,278
%i of 1234.52,3932,2612,278
%f of 1234.53,4702,5782,579
  • size: text 106,743,054 to 106,742,798 bytes (256 fewer than the base branch). The file size does not change.
  • Line layout: 5 of 915 swept calls change, all under %d of 1234.5 with 63 to 67 columns of text before it. 6,000 more calls with one substitution and a trailing value: 0 change.
  • The line-width estimate counts the text of a number that does not print as only digits: a fraction, an exponent form, -0, NaN, Infinity. A whole number that prints as digits adds one column, as on main. console-log.js has a row for %d of Number.MAX_VALUE.
  • cargo clippy for bun_jsc, cargo fmt --check pass. BUN_JSC_validateExceptionChecks=1 on the test file: 10 pass, 1 todo, 0 reports.