parseFloat() and console.log("%f") disagree
Categories
(Core :: DOM: Core & HTML, defect)
Tracking
()
People
(Reporter: domfarolino, Unassigned, Mentored)
References
(Depends on 1 open bug)
Details
(Keywords: good-first-bug, parity-chrome, parity-safari)
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
Steps to reproduce:
Per the Console Standard, console.log("%f", ...)
should delegate to ECMAScript's %parseFloat()%. All browsers agree that parseFloat(23)
prints "23", however in Firefox, console.log("%f", 23)
prints "23.000000" (i.e., including an unnecessary number of zeros). If console.log(%f)
were truly delegating to parseFloat()
, then their outputs should be the same.
Additionally, parseFloat(null)
appropriately prints "NaN", which all browsers agree on. However, console.log("%f", null)
prints 0.000000
ONLY in Firefox, which appears to be another spec deviation.
Comment 1•1 year ago
|
||
The Bugbug bot thinks this bug should belong to the 'DevTools::Console' component, and is moving the bug to that component. Please correct in case you think the bot is wrong.
Updated•1 year ago
|
Updated•1 year ago
|
Comment 2•1 year ago
|
||
Quick digging here
float is handle here https://searchfox.org/mozilla-central/source/dom/console/Console.cpp#2009-2027
- For
null
, https://searchfox.org/mozilla-central/source/js/public/Conversions.h#139 and https://searchfox.org/mozilla-central/source/js/src/jsnum.cpp#2025-2028
if (v.isNull()) {
*out = 0.0;
return true;
}
- for mantissa/prec
https://searchfox.org/mozilla-central/source/xpcom/string/nsTSubstring.cpp#1115 ( prec is -1 here) and https://searchfox.org/mozilla-central/source/mozglue/misc/Printf.cpp#976 , then https://searchfox.org/mozilla-central/source/mozglue/misc/Printf.cpp#351-354
// "If the precision is missing, it shall be taken as 6."
if (prec < 0) {
prec = 6;
}
Comment 3•7 months ago
|
||
Hi, I'm totally new around here and would love to give this bug a shot if that's ok! Could it be assigned to me?
I still see the issue present in the latest Nightly (128.0a1 2024-05-27). In fact, the issue is a bit more broad than what is reported, if we are following the Console Standard here: https://console.spec.whatwg.org/#formatter steps 6.2 and 6.3. (Please let me know if this isn't right/I should be looking at a different version.)
We don't do anything special for Symbol
(6.2.1 and 6.3.1); e.g. console.log("%f", Symbol.match)
or console.log("%d", Symbol.unscopables)
prints nothing in Firefox while both Chrome and Safari agree that NaN
should be printed.
We also don't defer to parseInt
or parseFloat
(per 6.2.2 and 6.3.2) but instead use to my understanding a totally different code path to handle the conversion to string, which causes the disagreements such as printing 0
instead of NaN
noted by Dominic as well as several other issues, for example (everything below checked with Chrome/Safari):
console.log("%d", "xyz")
should printNaN
, but instead prints0
console.log("%f", "3.14 is tasty")
should print3.14
, but instead printsNaN
console.log("%f", ["123", "45"])
should print123
(sinceparseFloat(["123", "45"])
yields123
), but instead it printsNaN
. (Interestingly so does Safari in this case)
Relevant code for this issue: https://searchfox.org/mozilla-central/source/dom/console/Console.cpp#1983-2031
Comment 4•7 months ago
|
||
Hello :gong!
Thank you for your interests in helping this bug. I just assigned this to you.
Comment 5•7 months ago
|
||
Hi Brandon,
if you have any questions about this bug, or general engineering workflow questions, feel free to ask questions and needinfo me, happy to help out. :)
Thank you for taking a look into this bug!
Comment 7•4 months ago
|
||
This good-first-bug hasn't had any activity for 2 months, it is automatically unassigned.
For more information, please visit BugBot documentation.
Comment 8•2 months ago
|
||
Hello,
Am I correct in understanding that this bug is now actively being worked on?
Regardless, I would like to share a few points that I believe could be useful, even if someone is currently addressing the issue.
Based on the previous comment by :gong and referencing the whatwg console specification, when we execute something like console.log("%f", f)
, it should be interpreted as console.log("%f", Number.parseFloat(f))
. This suggests that the following outcomes should occur when calling console.log
:
console.log("%f", "3.14 is tasty")
->console.log("%f", Number.parseFloat("3.14 is tasty"))
->console.log("%f", 3.14)
-> 3.14console.log("%f", ["123", "45"])
->console.log("%f", Number.parseFloat(["123", "45"]))
->console.log("%f", 123)
-> 123
However, what actually happens right now is:
console.log("%f", "3.14 is tasty")
->console.log("%f", ToNumber("3.14 is tasty"))
->console.log("%f", NaN)
-> NaN
WhileJS::ToNumber
functions correctly, it seems we should not be invoking it in this case. Instead, we should be calling aparseFloat
equivalent.
if (NS_WARN_IF(!JS::ToNumber(aCx, value, &v))) {
return false;
}
/* ES6 draft 20141224, 7.1.3. */
MOZ_ALWAYS_INLINE bool ToNumber(JSContext* cx, HandleValue v, double* out) {
detail::AssertArgumentsAreSane(cx, v);
if (v.isNumber()) {
*out = v.toNumber();
return true;
}
return js::ToNumberSlow(cx, v, out);
}
At present, there is no JS::ParseFloat
implementation. From a quick glance, adding JS::ParseFloat
would not be a trivial task. We already have a js::NumberParseInt
implementation, and while adding a JS::ParseInt
function would not be too complicated, simply copying and pasting js::NumberParseInt
to create js::NumberParseFloat
would not be straightforward. However, the existing js::number_parseFloat
function can help in correctly implementing js::NumberParseFloat
, similar to how js::NumberParseInt
is implemented.
// Parse the input string as if Number.parseInt had been called.
extern bool NumberParseInt(JSContext* cx, JS::HandleString str, int32_t radix,
JS::MutableHandleValue result);
I experimented with using js::NumberParseInt
in a debug build, and it works as expected for integer types (we would also need to adjust the logic for %d
or %i
arguments). However, this seems like a complex fix for a "good-first-bug" and might require more significant code changes.
console.log("%d", [23, 45])
prints23
in Google Chrome, in Mozilla0
, in my debug build it also prints23
.
Therefore, my suggestion would be to split this bug into several smaller tasks:
- Add
JS::ParseInt
andJS::ParseFloat
implementations - Replace
JS::ToNumber
withJS::ParseInt
orJS::ParseFloat
where appropriate - (If needed) Further split the task of adding
JS::ParseInt
andJS::ParseFloat
Comment 9•2 months ago
|
||
Hi Nikolai,
thank you for your input! It might make sense to split this up as you proposed. Are you interested in helping out with the implementation here as well?
:gong, are you still actively working on this bug?
Comment 10•2 months ago
|
||
Hi Nikolai and Jan,
I have not worked on the bug in a long while as I have been swept up with other things. I regret not updating you all sooner -- I keep thinking I'll come back to it soon, but now it's been 5 months of silence from me. My apologies for this.
In the time that I did have to look at it, I was not able to make much headway; it's heavily due to my lack of familiarity with the codebase, but looking at Nikolai's investigation it seems that the issue is also more complex than I had imagined.
That being said, I foresee my schedule becoming a bit lighter than it was in the past few months. If you all would be willing, I would still be interested in helping. The task as a whole is probably more difficult than I can handle as a good-first-bug, but I would be happy to take a smaller piece to chew on. Though I very much understand if it would be more productive for someone else to handle this bug!
Thanks all for your time!
Comment 11•2 months ago
|
||
Hello Jan and Brandon,
I’d like to participate in the implementation if possible. As Jan agreed, we should split this bug into at least two parts. One approach could be to file a new bug for implementing JS::ParseInt
and JS::ParseFloat
, and then make the current bug dependent on that new one.
However, after some research, I found that the JS namespace falls under the JavaScript Engine (SpiderMonkey) component. This suggests that adding new methods to this namespace won’t be a quick task, as it will involve other developers from a different component. We’ll also need to provide a clear explanation for why we are modifying code in their component.
That said, I’m not in a rush. I’m happy to take on the task of adding JS::ParseInt
and JS::ParseFloat
and leave the current bug for future work. This way, Brandon (:gong) or other developers can find time to contribute when they're able.
Regarding filing a new bug, I’m not familiar with the procedure but I’m willing to give it a try if possible.
Comment 12•2 months ago
•
|
||
Hey folks, thanks for coming to help out! Feel free to open bugs against the JS engine. If you run into any issues feel free to ping.
Reading through this issue, you are almost there. The core problem is we are missing a C++ facing interface. You don't need to reimplement parseFloat, you can split it. It is currently setting the value of something called rval, but that is actually just a HandleValue
under the hood (same type as v
in ToNumber). You can share quite a bit from there, split the arg handling from the value setting. The c++ interface doesn't need to bother with CallArgs args = CallArgsFromVp(argc, vp);
or checking the length of args, because it can pass the HandleValue
directly.
Some context: the engine can be called in different ways. From JS, from the JITs or from C++ that lives in the browser engine. num_parseFloat
is called by JS, same for num_parseInt
. js::NumberParseInt
is called by both JS (via num_parseInt
) and the JITs (see the callvms in there) but both of these are engine internal. It isn't called by the browser, though it could be. I can't remember if it needs to be a JS_PUBLIC_API
or not, but it seems like you already got it working so maybe not. It might need a public api wrapper or to be a public api itself in order to be called by external C++.
This is a bit of refactoring in order to make it work, but we are happy for the help :). Feel free to stop by the SpiderMonkey channel on matrix for help also.
Comment 13•1 month ago
|
||
(removing my ni? because I could see that :gaporf is active on Matrix discussing the matter with folks that are far more involved than me. Feel free to ping me again if you need anything. Thank you all for working on this and for supporting!)
Comment 14•20 days ago
|
||
Working on the blocking bug 1928966 I discovered an interesting edge case:
In both Mozilla Firefox and Google Chrome, the following commands behave consistently as expected:
> Number.parseInt(-0)
< 0
> Number.parseInt("-0")
< -0
However, the behavior of console.log
differs in Google Chrome:
> console.log("%d", -0)
0
< undefined
> console.log("%d", "-0") // We expect -0 due to Number.parseInt("-0") = -0
0
< undefined
Safari behaves similarly to Google Chrome:
> console.log("%d", -0)
[Log] 0
< undefined
> console.log("%d", "-0")
[Log] 0
< undefined
> Number.parseInt("-0")
< -0
> Number.parseInt(-0)
< 0
I suppose we should add test cases for the following scenarios:
console.log("%d", -0)
, we should expect0
output;console.log("%d", "-0")
, we should expect-0
output.
These test cases should validate whether the output distinguishes between -0
and 0
in %d
formatting, ensuring consistency with the behavior of Number.parseInt
.
Description
•