r语言 - 使用C++中的"round-to-even"将双倍舍入到小数点后 n 位

r - Rounding doubles to n decimal points using "round-to-even" in C++

本文关键字:舍入 小数点 语言 使用 C++ round-to-even 中的      更新时间:2023-10-16

我想在C++中实现一个函数:

double round_to_even(double num, int decimal_places); 
/* rounds the first argument to three decimal places 
using round-to-even (unbiased rounding) */

它的行为与R中的四舍五入的工作方式相同,使用了一种称为四舍五五入、无偏四入或统计学家四舍五进的方法:https://stat.ethz.ch/R-manual/R-devel/library/base/html/Round.html最好的方法是通过R中的几个例子来说明这一点,其中decimal_place=3:

> round(0.1247,3) # rounding the first argument to three decimal places
[1] 0.125
> round(0.1244,3)
[1] 0.124

注意它的行为,当指定decimal_place右侧的数字大于5(此处等于7(时向上取整,当它小于5时截断(此处等于4(。然而,当指定decimal_place右侧的数字等于5(或在"中点"处(时,其行为如下:

> round(0.1255,3) # will round up
[1] 0.126
> round(.1275,3) # will round up
[1] 0.128
> round(0.1265,3) # will truncate
[1] 0.126
> round(0.1245,3) # will truncate
[1] 0.124

decimal_place左边的数字为奇数时取整,偶数时截断。

有没有一种简洁的方法来实现这一点,特别是在不将数字转换为字符的情况下?

编辑:这是我能想到的最好的:

double round_to_even(double number, int decimal_points) 
/* rounds the first argument to three decimal places
using round-to-even (unbiased rounding) */
{   
    double num_left = number, num_right = number;
    int digit_left, digit_right;
    num_left *= pow(10, decimal_points + 1);
    digit_left = fmod(num_left, 10);
    if (digit_left == 5) 
    {   
        num_right *= pow(10, decimal_points);
        digit_right = fmod(num_right, 10);
        if (digit_right % 2 == 0) // if even 
            return floor(number * pow(10, decimal_points)) / pow(10, decimal_points);
        else // otherwise it's odd
            return ceil(number * pow(10, decimal_points)) / pow(10, decimal_points);
    }
    else { // standard round-to-nearest
        return round(number * pow(10, decimal_points)) / pow(10, decimal_points);
    }
}

我已经测试过了:

std::vector<double> test_vector({ 0.1247, 0.1244, 0.1255, 0.1275, 0.1265, 0.1245 });
std::vector<double> expected_values({ 0.125, 0.124, 0.126, 0.128, 0.126, 0.124 });
for (std::vector<double>::size_type i = 0; i < test_vector.size(); i++)
    std::cout << "expected: " << expected_values[i] << "t got: " << round_to_even(test_vector[i], 3) << std::endl;

它给出以下输出:

expected: 0.125  got: 0.125 
expected: 0.124  got: 0.124
expected: 0.126  got: 0.126
expected: 0.128  got: 0.128
expected: 0.126  got: 0.126
expected: 0.124  got: 0.124
double round(double d, int n) {
    double last = d * pow(10, n + 1);
    int last_dig = floor(last) % 10;
    if (last_dig != 5)
        return reg_round(d, n); //round as normal
    double pre_last = d * pow(10, n);
    int pre_last_dig = floor(pre_last) % 10;
    if (pre_last_dig %2 == 0)
        return floor(d,n); //last digit is even, floor.
    else
        return ceil(d,n); //last digit is odd, ceil.
} 

假设CCD_ 1是正规圆。

我最近为了工作不得不研究这个问题,我研究了dtoa.c,但我试图使用的精简版本似乎在windows上有问题,这可能是我的错,但最终我自己实现了round,甚至也就是银行家舍入/统计学家舍入:

#include <math.h>
#include <stdio.h>
void bankerRound(double d, int decimals, char *buffer, int bufferLength) {
    const auto powerTen = pow(10.0, decimals);
    double intPart = 0;
    double fractionPart = fabs(modf(d, &intPart));
    double fractionRaised = fractionPart * powerTen;
    double fractionRaisedAndRounded;
    double fractionRaisedIntComponent = NAN;
    if (modf(fractionRaised, &fractionRaisedIntComponent) == 0.5) {
        if ((long) fractionRaisedIntComponent % 2 == 0) {
            fractionRaisedAndRounded = floor(fractionRaised);
        } else {
            fractionRaisedAndRounded = ceil(fractionRaised);
        }
    } else {
        fractionRaisedAndRounded = round(fractionRaised);
    }
    if (fractionRaisedAndRounded >= powerTen) {
        fractionRaisedAndRounded -= powerTen;
        intPart = intPart < 0 ? intPart - 1 : intPart + 1;
    }
    snprintf(buffer, static_cast<size_t>(bufferLength), "%.0lf.%0*.0lf", intPart, decimals, fractionRaisedAndRounded);
}

modf分离二重的整数分量和分数分量,这意味着我们不需要将二重提高到大幂,可能会溢出。然而,乘以分数可能会导致不准确,因为值不能准确地以2为基数表示,所以我只会在你只使用小数点后6位的情况下使用这个函数。否则,最好尝试使用dtoa.c或更新的Ryú算法。