【问题标题】:Round Number 0.005 to down in Javascript在 Javascript 中将数字 0.005 向下舍入
【发布时间】:2020-04-07 11:33:32
【问题描述】:

有没有办法将数字 0.005 向下舍入。

例如:

5.425 将四舍五入为5.42

2.336 要四舍五入为2.34

0.5556 将四舍五入为0.56

更新:在点之后我并不总是有 3 位数字,我可以拥有更多。比如5.55555612.3456789

已经尝试使用parseFloat(number).toFixed(2),但在某些情况下它并不能真正起作用,像0.555 这样的值输出将是0.56 而不是0.55

提前致谢

【问题讨论】:

  • @MrUpsidown - 第一个示例将舍入到5.43。 OP 想要“四舍五入”。
  • @T.J.Crowder 使用我提到的重复链接中的什么答案?第二个答案:parseFloat("5.425").toFixed(2),是否打印5.43
  • @MrUpsidown - toFixed 产生一个字符串,而不是一个数字。
  • @T.J.Crowder 抱歉,如果我的评论让您感到不愉快并且您感到被冒犯了。我已删除我的评论。我什至会删除我重复的近距离投票,但现在问题 is 已关闭,我不能。当问题仅限于“我如何做某事”时,我也倾向于对过于宽泛的问题进行近距离投票,并且没有表现出任何解决问题的尝试。

标签: javascript


【解决方案1】:

问题是,JavaScript 不知道像 5.425 这样的数字。计算机的浮点实现以及大多数编程语言的浮点实现都是基于二进制的,而不是基于十进制的(参见 IEEE 754)。因此,每个扫描的数字首先四舍五入到最接近的可能值:

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"

并且这些扫描结果用于进一步处理。所以使用toFixed(2) 的结果是预期的并且绝对正确。

不要相信其他解决方案(有时称为betterToFixed())可以解决此问题。他们对其他数字有疑问,因为许多作者不了解幕后的数学。

【讨论】:

    【解决方案2】:

    您曾说过您希望5.425 舍入为5.42,但您希望5.555556 舍入为5.556。这意味着您正在执行向下舍入(或者可能是向零舍入)操作,而不是进行向上(或远离零,具体取决于)的“正常”轮次。

    我能想到的唯一方法是在四舍五入的每个阶段从数字的最后一位中减去一个,如下所示:

    // 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;
    }
    

    现场示例:

    // 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>

    注意最后一个结果为负数。如果您想向零舍入而不是“向下”,请更改此行:

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

    到此,允许n 的符号:

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

    并更改函数的名称,因为它不再向下取整(可能是roundTiesToZero)。

    现场示例:

    // 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>

    【讨论】:

    • 它不适用于0.5556,输出将是0.55 而不是0.56。我已经在我的问题中添加了更多细节,抱歉没有明确的答案。
    • @JohnSari - 啊,好吧。因此,您在每个级别都进行了向下舍入(或舍入归零)操作。我已经更新了答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-25
    • 1970-01-01
    • 2022-07-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多