0

is there a way to round a number 0.005 to down.

For example:

5.425 is to be rounded to 5.42

2.336 is to be rounded to 2.34

0.5556 is to be rounded to 0.56

UPDATE: I don't have always 3 digits after point I can have more than that. like 5.555556, 12.3456789, etc

already tried with parseFloat(number).toFixed(2) but it doesn't really work for some cases, value like 0.555 the output will be 0.56 instead of 0.55

thanks in advance

John Sari
  • 326
  • 2
  • 10
  • Duplicate of [Round to at most 2 decimal places (only if necessary)](https://stackoverflow.com/questions/11832914/round-to-at-most-2-decimal-places-only-if-necessary) – MrUpsidown Apr 07 '20 at 11:38
  • @MrUpsidown - That would round to `5.43` for the first example. The OP wants "round evens down." – T.J. Crowder Apr 07 '20 at 11:38
  • @T.J.Crowder Using what answer from the duplicate link I mentioned? Second answer: `parseFloat("5.425").toFixed(2)`, does that print `5.43`? – MrUpsidown Apr 07 '20 at 11:41
  • @MrUpsidown - `toFixed` produces a **string**, not a number. – T.J. Crowder Apr 07 '20 at 11:43
  • @T.J.Crowder OP didn't mention what he needs. – MrUpsidown Apr 07 '20 at 11:44
  • @MrUpsidown - The OP shows numbers as input and output. *Maybe* they want a string as output, but I read it as a number question. Also note that `toFixed` will round the 5 the other way for other numbers, such as `1.125` (which `toFixed` turns into `"1.13"`, not `1.12` as the question asks). – T.J. Crowder Apr 07 '20 at 11:45
  • John, do you want a number or a string? – T.J. Crowder Apr 07 '20 at 11:47
  • @MrUpsidown i already show the input and output that i want. the first examply you show is to round up the .005, what i want is to round down .005. and the output can be string / number, i can convert them when i use to calculation. – John Sari Apr 07 '20 at 13:23
  • @JohnSari no it is not. `parseFloat("5.425").toFixed(2)` will output `5.42` so that is not rounding up. – MrUpsidown Apr 07 '20 at 13:31
  • 1
    @T.J.Crowder sorry if my comment felt unpleasant and if you felt offended. I have removed my comment. I would even have removed my duplicate close-vote but I can't now that the question *is* closed. I too tend to close-vote questions for being too broad when they are limited to "how do I do *something*" and show no attempt whatsoever at solving the issue. – MrUpsidown Apr 07 '20 at 13:38
  • @MrUpsidown I already test it, and it works. but for some cases that's not working. `parseFloat("0.555").toFixed(2)` the output will be `0.56` instead of `0.55` – John Sari Apr 08 '20 at 02:40
  • @JohnSari what you want is round to even right? is this answer help you? https://stackoverflow.com/a/3109234/7865599 – James Apr 08 '20 at 08:46

2 Answers2

0

The issue is, that JavaScript don't know the numbers like 5.425. The floating point implementation of computers and therefor for the most programming languages is binary based and not decimal based (see IEEE 754). So each scanned number is rounded to the nearest possible value first:

5.425  .toPrecision(18); // result "5.42499999999999982"  => ToFixed(2)="5.42"
2.336  .toPrecision(18); // result "2.33599999999999985"  => ToFixed(2)="2.34"
0.5556 .toPrecision(18); // result "0.555599999999999983" => ToFixed(2)="0.56"

And these scanning results are used for further processing. So the results using toFixed(2) are expected and absolute correct.

And don't believe in other solutions (sometimes called betterToFixed()) to fix this issue. They have issues with other numbers, because many authors don't understand the mathematics behind the scenes.

Wiimm
  • 2,971
  • 1
  • 15
  • 25
-1

You've said you want 5.425 to round to 5.42 but that you want 5.555556 to round to 5.556. That means you're doing a round-ties-down (or perhaps round-ties-toward-zero) operation rather than a "normal" round where ties go up (or away from zero, depending).

The only way I can think of to do that is to subtract one from the final digit of the number at each stage of rounding, like this:

// Round the given number to the given number of places, but rounding ties down
// (0.5 rounds to 0 instead of 1, 0.55 rounds to 0.5 instead of 0.6, etc.)
function roundTiesDown(n, places) {
    if (places < 1) {
        throw new Error(`Received places=${places}, but must be >=1`);
    }
    let currentPlaces = significantFractionalPlaces(n)
    // Round ties down at each level, e.g. (if places = 2):
    // 0.55556 => 0.5556
    // 0.5556 => 0.556
    // 0.556 => 0.56
    // and
    // 0.55555 => 0.5555 (would be 0.5556 with "normal" rounding)
    // 0.5555 => 0.555
    // 0.555 => 0.55
    while (currentPlaces > places) {
        const subtrahend = 1 / Math.pow(10, currentPlaces);
        --currentPlaces;
        const multiplier = Math.pow(10, currentPlaces);
        n = Math.round((n - subtrahend) * multiplier) / multiplier;
    }
    return n;
}

Live Example:

// Get a function to convert numbers to string without going to scientific notation
let numberToString;
if (typeof Intl === "object" && typeof Intl.NumberFormat === "function") {
    // Intl.NumberFormat lets us do this properly
    const format = new Intl.NumberFormat(undefined, {
        style: "decimal",
        useGrouping: false,
        maximumFractionDigits: 20
    });
    numberToString = n => format.format(n);
} else {
    // Fall back to toString on platforms without Intl.NumberFormat
    // (all major browsers have it, including IE11 -
    // https://caniuse.com/#feat=mdn-javascript_builtins_intl_numberformat)
    const rexContainsE = /e/i;
    numberToString = n => {
        const str = n.toString();
        if (rexContainsE.test(str)) {
            // Went to scientific notation
            throw new Error("Can't handle numbers this big on this platform");
        }
        return str;
    };
}

// Get the currentPlaces number of significant places in the given number
function significantFractionalPlaces(n) {
    const str = numberToString(n);
    const idx = str.indexOf(".");
    return idx === -1 ? 0 : str.length - idx - 1;
}

// Round the given number to the given number of places, but rounding ties down
// (0.5 rounds to 0 instead of 1, 0.55 rounds to 0.5 instead of 0.6, etc.)
function roundTiesDown(n, places) {
    if (places < 1) {
        throw new Error(`Received places=${places}, but must be >=1`);
    }
    let currentPlaces = significantFractionalPlaces(n)
    // Round ties down at each level, e.g. (if places = 2):
    // 0.55556 => 0.5556
    // 0.5556 => 0.556
    // 0.556 => 0.56
    // and
    // 0.55555 => 0.5555 (would be 0.5556 with "normal" rounding)
    // 0.5555 => 0.555
    // 0.555 => 0.55
    while (currentPlaces > places) {
        const subtrahend = 1 / Math.pow(10, currentPlaces);
        --currentPlaces;
        const multiplier = Math.pow(10, currentPlaces);
        /* For your real function, use this:
        n = Math.round((n - subtrahend) * multiplier) / multiplier;
        instead of the following lines using `rounded`
        */
        const rounded = Math.round((n - subtrahend) * multiplier) / multiplier;
        if (verbose) {
            log("detail", `Rounded ${n} to ${rounded}`);
        }
        n = rounded;
    }
    return n;
}

// ===== Testing

const cbVerbose = document.querySelector("input[type=checkbox]");
const btnRun = document.querySelector("input[type=button]");
const output = document.querySelector(".output");
const errors = document.querySelector(".errors");

function log(cls, msg) {
    /*
    output.insertAdjacentText("beforeend", "\r\n" + msgs.join(" "));
    */
    const div = document.createElement("div");
    div.className = cls;
    div.textContent = msg;
    output.appendChild(div);
}

let verbose = cbVerbose.checked;

function test(n, expected) {
    const rounded = roundTiesDown(n, 2);
    const good = rounded === expected;
    log(
        good ? "good" : "error",
        `${n} => ${rounded} ${good ? "OK" : `<== ERROR, expected ${expected}`}`
    );
    return good ? 0 : 1;
}

function runTests() {
    verbose = cbVerbose.checked;
    output.textContent = "";
    const errorcount =
        test(5.425, 5.42) +
        test(5.555556, 5.56) +
        test(12.3456789, 12.35) +
        test(1.125, 1.12) +
        test(2.336, 2.34) +
        test(2, 2) +
        test(-5.425, -5.43);
    errors.textContent = errorcount === 0 ? "All passed" : `Errors: ${errorcount}`;
    errors.className = errorcount === 0 ? "good" : "error";
}

btnRun.addEventListener("click", runTests);
runTests();
html {
    box-sizing: border-box;
    font-family: sans-serif;
}
*, *:before, *:after {
    box-sizing: inherit;
}
html, body {
    height: 100%;
    overflow: hidden;
    padding: 0;
    margin: 0;
}
body {
    padding: 4px;
    display: flex;
    flex-direction: column;
    font-size: 14px;
}

.panel {
    order: 1;
    border-bottom: 1px solid black;
    padding-bottom: 2px;
}
.output {
    order: 2;
    flex-grow: 1;
    white-space: pre;
    font-family: monospace;
    overflow: auto;
}

.good {
    color: #060;
}
.error {
    color: #C00;
}
.detail {
    color: #aaa;
}
<div class="panel">
    <label style="user-select: none">
        <input type="checkbox">
        Verbose output
    </label>
    <input type="button" value="Run Tests">
    <span class="errors"></span>
</div>
<div class="output"></div>

Note that last result for the negative number. If you want to round toward zero rather than "down," you change this line:

n = Math.round((n - subtrahend) * multiplier) / multiplier;

to this, which allows for the sign of n:

n = Math.round((n + (n < 0 ? subtrahend : -subtrahend)) * multiplier) / multiplier;

and change the name of the function, since it no longer rounds down (perhaps roundTiesToZero).

Live Example:

// Get a function to convert numbers to string without going to scientific notation
let numberToString;
if (typeof Intl === "object" && typeof Intl.NumberFormat === "function") {
    // Intl.NumberFormat lets us do this properly
    const format = new Intl.NumberFormat(undefined, {
        style: "decimal",
        useGrouping: false,
        maximumFractionDigits: 20
    });
    numberToString = n => format.format(n);
} else {
    // Fall back to toString on platforms without Intl.NumberFormat
    // (all major browsers have it, including IE11 -
    // https://caniuse.com/#feat=mdn-javascript_builtins_intl_numberformat)
    const rexContainsE = /e/i;
    numberToString = n => {
        const str = n.toString();
        if (rexContainsE.test(str)) {
            // Went to scientific notation
            throw new Error("Can't handle numbers this big on this platform");
        }
        return str;
    };
}

// Get the currentPlaces number of significant places in the given number
function significantFractionalPlaces(n) {
    const str = numberToString(n);
    const idx = str.indexOf(".");
    return idx === -1 ? 0 : str.length - idx - 1;
}

// Round the given number to the given number of places, but rounding ties down
// (0.5 rounds to 0 instead of 1, 0.55 rounds to 0.5 instead of 0.6, etc.)
function roundTiesToZero(n, places) {
    if (places < 1) {
        throw new Error(`Received places=${places}, but must be >=1`);
    }
    let currentPlaces = significantFractionalPlaces(n)
    // Round ties down at each level, e.g. (if places = 2):
    // 0.55556 => 0.5556
    // 0.5556 => 0.556
    // 0.556 => 0.56
    // and
    // 0.55555 => 0.5555 (would be 0.5556 with "normal" rounding)
    // 0.5555 => 0.555
    // 0.555 => 0.55
    while (currentPlaces > places) {
        const subtrahend = 1 / Math.pow(10, currentPlaces);
        --currentPlaces;
        const multiplier = Math.pow(10, currentPlaces);
        /* For your real function, use this:
        n = Math.round((n + (n < 0 ? subtrahend : -subtrahend)) * multiplier) / multiplier;
        instead of the following lines using `rounded`
        */
        const rounded = Math.round((n + (n < 0 ? subtrahend : -subtrahend)) * multiplier) / multiplier;
        if (verbose) {
            log("detail", `Rounded ${n} to ${rounded}`);
        }
        n = rounded;
    }
    return n;
}

// ===== Testing

const cbVerbose = document.querySelector("input[type=checkbox]");
const btnRun = document.querySelector("input[type=button]");
const output = document.querySelector(".output");
const errors = document.querySelector(".errors");

function log(cls, msg) {
    /*
    output.insertAdjacentText("beforeend", "\r\n" + msgs.join(" "));
    */
    const div = document.createElement("div");
    div.className = cls;
    div.textContent = msg;
    output.appendChild(div);
}

let verbose = cbVerbose.checked;

function test(n, expected) {
    const rounded = roundTiesToZero(n, 2);
    const good = rounded === expected;
    log(
        good ? "good" : "error",
        `${n} => ${rounded} ${good ? "OK" : `<== ERROR, expected ${expected}`}`
    );
    return good ? 0 : 1;
}

function runTests() {
    verbose = cbVerbose.checked;
    output.textContent = "";
    const errorcount =
        test(5.425, 5.42) +
        test(5.555556, 5.56) +
        test(12.3456789, 12.35) +
        test(1.125, 1.12) +
        test(2.336, 2.34) +
        test(2, 2) +
        test(-5.425, -5.42);
    errors.textContent = errorcount === 0 ? "All passed" : `Errors: ${errorcount}`;
    errors.className = errorcount === 0 ? "good" : "error";
}

btnRun.addEventListener("click", runTests);
runTests();
html {
    box-sizing: border-box;
    font-family: sans-serif;
}
*, *:before, *:after {
    box-sizing: inherit;
}
html, body {
    height: 100%;
    overflow: hidden;
    padding: 0;
    margin: 0;
}
body {
    padding: 4px;
    display: flex;
    flex-direction: column;
    font-size: 14px;
}

.panel {
    order: 1;
    border-bottom: 1px solid black;
    padding-bottom: 2px;
}
.output {
    order: 2;
    flex-grow: 1;
    white-space: pre;
    font-family: monospace;
    overflow: auto;
}

.good {
    color: #080;
}
.error {
    color: #C00;
}
.detail {
    color: #aaa;
}
<div class="panel">
    <label style="user-select: none">
        <input type="checkbox">
        Verbose output
    </label>
    <input type="button" value="Run Tests">
    <span class="errors"></span>
</div>
<div class="output"></div>
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    it doesn't work for `0.5556` the output will be `0.55` instead of `0.56`. I already add more details in my question, sorry for not clear answer. – John Sari Apr 08 '20 at 03:41
  • @JohnSari - Ah, okay. So you're doing a round-ties-down (or round-ties-to-zero) operation at each level. I've updated the answer. – T.J. Crowder Apr 08 '20 at 09:50