Compatibility
Published
Short answer: no. We ran a forty-line number-formatting sample through five protection configurations and diffed the output against the unprotected original. Every line matched, including the rounding results people find surprising. The only configuration that broke anything was one we deliberately misconfigured, and it broke loudly rather than quietly.
What was actually measured
Formatting bugs are expensive because they are invisible in testing and obvious on an invoice. If protection changed the way a number renders, you would find out from a customer, not from a test suite. So rather than reason about it, we measured it.
The sample exercises toFixed, toPrecision, toExponential, toString with radixes 2, 8, 16 and 36, parseInt with and without an explicit radix, parseFloat, the Number() constructor across empty strings and null and undefined, and the Number statics isInteger, isSafeInteger, isNaN and isFinite. It runs in Node, prints one labelled line per case, and the protected copy is executed the same way.
The five configurations were the ES5 target, the modern target, the two identifier-renaming presets our emit-validation gate uses, and the string-table preset that moves and encodes every string literal. All five produced output identical to the original, line for line.
The rounding results that look like bugs are not bugs
The cases most likely to be reported as an obfuscation defect are the ones that were already surprising. (1.005).toFixed(2) returns 1.00, not 1.01. (2.675).toFixed(2) returns 2.67, not 2.68. Both of those are true before protection and true after it, on every configuration we ran.
The reason is that 1.005 and 2.675 are not exactly representable in binary floating point. The nearest double to 1.005 is slightly below it, so rounding to two places goes down. That is a property of the number, not of your build pipeline. If you protect a file and a finance report shifts by a cent, the protection did not do it, and the same output would appear from the unprotected source.
Ordinary halfway cases behave the way you expect: (0.5).toFixed(0) gives 1 and (1.5).toFixed(0) gives 2. Again, identical before and after.
Radix conversion and parsing survive intact
Base conversion is common in licence keys, colour handling and short identifiers, so it is worth stating precisely. (255).toString(2) stays 11111111, (255).toString(16) stays ff, (35).toString(36) stays z, and (-255).toString(16) keeps its sign as -ff. Fractional conversion is stable too: (0.5).toString(2) gives 0.1.
Parsing keeps all of its quirks, which is what you want, because code depends on them. parseInt("12abc", 10) still returns 12 by stopping at the first invalid character. parseInt(" 7 ", 10) still skips leading whitespace and returns 7. parseInt("0x1f") still auto-detects hexadecimal and returns 31. parseInt("abc", 10) still returns NaN.
The Number() conversions that differ from parseInt also hold: Number("") is 0, Number(null) is 0, Number(undefined) is NaN, and Number("12abc") is NaN where parseInt would have returned 12. If your code relies on that distinction, it keeps relying on it safely.
The statics behave identically as well, including the pair people mix up: Number.isNaN("x") is false while the global isNaN("x") is true, and Number.isFinite("5") is false while the global isFinite("5") is true. Number.parseInt === parseInt remains true after protection, so the identity check some polyfill guards perform still passes.
The one way to break this, and what it looks like
There is a configuration that does break number formatting, and it is worth showing because it is a mistake we can describe precisely rather than a defect you have to work around.
Member renaming is driven by a regular expression matched against property and method names. It is name-based, not type-aware: the engine has no way to know that toFixed on a number is a built-in rather than one of your own methods. If your pattern matches a built-in name, the call site is rewritten to the new name and the built-in is simply not there any more.
We ran the same sample with a member pattern of ^(toFixed)$. Every toFixed call site was rewritten, so v.toFixed(0) became v._0x1(0), and the script stopped at the first one with TypeError: v._0x1 is not a function. String literals containing the word were untouched, because a string is not a rename site.
That failure mode is the good kind. It throws immediately, on the first call, with the offending name in the message, so it shows up the moment the file is exercised rather than silently producing a wrong invoice. The fix is to scope the pattern to names your own objects define. Anchor it, list the names explicitly, and do not use a catch-all. The Protect Members documentation covers how the pattern is applied.
What this means for a release
If you format numbers for display, convert bases, or parse user input, protection is not a risk area, and you do not need a special test plan for it. The behaviour is identical because the engine is renaming and restructuring identifiers rather than touching arithmetic or the standard library.
The thing worth a checklist entry is the member-renaming pattern, and it is worth one for reasons well beyond number formatting. Review it as you would review a regular expression that runs in production, because that is what it is. A pattern that is too broad is the single most common way to turn a working build into a broken one, and it is entirely within your control.
If you want to confirm this on your own code rather than take our measurement for it, run your formatting unit tests against the protected bundle. That is the general recipe in verifying the obfuscator did not break your code, and number formatting is one of the easiest things to assert on.
Frequently asked questions
Does obfuscation change what toFixed returns?
No. We diffed a number-formatting sample against its protected copy on five configurations, covering the ES5 target, the modern target, two identifier-renaming presets and the string-table preset, and every toFixed result was identical. That includes the surprising cases such as (1.005).toFixed(2) returning 1.00, which is a floating point property of the number and is equally true before protection.
Why did my rounding change after I protected my code?
Almost certainly it did not, and the same result appears from the unprotected source. Values like 1.005 and 2.675 are not exactly representable as binary doubles, so rounding to two places can go the direction you did not expect. Run the identical input through the original file to confirm. If the original and protected outputs really do differ, that is worth reporting, because in our measurements they do not.
Is parseInt affected by identifier renaming?
No. parseInt, parseFloat and Number() are global functions, and renaming identifiers does not touch them. All of the parsing edge cases held: parseInt stopping at the first invalid character, skipping leading whitespace, and auto-detecting a 0x prefix. Number.parseInt === parseInt also remains true after protection.
Does toString with a radix still work for base conversion?
Yes. Base 2, 8, 16 and 36 conversions were identical after protection, negative values kept their sign, and fractional conversion such as (0.5).toString(2) returning 0.1 was unchanged. This matters for licence keys and short identifiers, which commonly use base 36.
Can member renaming break number formatting?
Yes, if the pattern is too broad. Member renaming matches names with a regular expression and is not type-aware, so a pattern matching toFixed rewrites those call sites and the built-in is no longer reachable. We measured this deliberately: the script threw TypeError: v._0x1 is not a function at the very first call. Scope the pattern to names your own objects define.
Do I need special tests for number handling before shipping protected code?
No more than you already have. Because the behaviour is identical, your existing formatting and parsing unit tests are sufficient evidence when run against the protected bundle. The one thing worth reviewing by hand is the member-renaming pattern, which is the only setting in this area that can change behaviour.
Related reading