3

I had a value, 0.2435, that I wanted to format in D3 as a percent, rounded to the tenths place. I was expecting "24.4%" but got "24.3%".

I know there are different rounding methods, but couldn't find mention of the one D3 uses. See below for several examples to illustrate the issue.

var fmt = d3.format(",.1%");

console.log(fmt(.2405));
console.log(fmt(.2415));
console.log(fmt(.2425));
console.log(fmt(.2435));
console.log(fmt(.2445));
console.log(fmt(.2455));
console.log(fmt(.2465));
console.log(fmt(.2475));
console.log(fmt(.2485));
console.log(fmt(.2495));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.4.1/d3.min.js"></script>

  • 2
    Found this for anyone reaching this question: github.com/d3/d3-format/issues/27. – Al R. Mar 11 at 22:43
  • 2
    The GitHub issue you linked pretty much explains the matter: the rounding "problem" you see is by design. Therefore, your best choice is implementing the double rounding described in the issue itself. – Gerardo Furtado Mar 11 at 23:09
1

Since this question is explicitly asking about the algorithm used internally by D3, not for a solution to the problem OP is facing (which is complicated, see the comments above), here is the description.

When you do this...

var fmt = d3.format(",.1%");

... you are setting a precision of 1. After checking for the passed value...

precision = precision == null ? 6
    : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision))
    : Math.max(0, Math.min(20, precision));

...this precision will be used here:

value = formatType(Math.abs(value), precision);

If we have a look at the source code for formatType with % as the type, we'll see:

"%": function(x, p) { return (x * 100).toFixed(p); },

Where p is the precision. Therefore, in your case, this is equivalent to:

function(x){
    return (x * 100).toFixed(1);
};

And that's the algorithm you're asking.

As a proof, let's see your examples with that algorithm:

var d3fmt = d3.format(",.1%");

function jsfmt(x) {
  return (x * 100).toFixed(1) + "%";
}

console.log("d3: " + d3fmt(.2405) + " --- pure JS: " + jsfmt(.2405));
console.log("d3: " + d3fmt(.2415) + " --- pure JS: " + jsfmt(.2415));
console.log("d3: " + d3fmt(.2425) + " --- pure JS: " + jsfmt(.2425));
console.log("d3: " + d3fmt(.2435) + " --- pure JS: " + jsfmt(.2435));
console.log("d3: " + d3fmt(.2445) + " --- pure JS: " + jsfmt(.2445));
console.log("d3: " + d3fmt(.2455) + " --- pure JS: " + jsfmt(.2455));
console.log("d3: " + d3fmt(.2465) + " --- pure JS: " + jsfmt(.2465));
console.log("d3: " + d3fmt(.2475) + " --- pure JS: " + jsfmt(.2475));
console.log("d3: " + d3fmt(.2485) + " --- pure JS: " + jsfmt(.2485));
console.log("d3: " + d3fmt(.2495) + " --- pure JS: " + jsfmt(.2495));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

You can see that they are the same values that you have in your snippet.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.