source

반 플로트 숫자를 적절하게 반올림하는 방법은 무엇입니까?

manycodes 2023. 5. 26. 21:11
반응형

반 플로트 숫자를 적절하게 반올림하는 방법은 무엇입니까?

나는 이상한 행동에 직면해 있습니다.round()함수:

for i in range(1, 15, 2):
    n = i / 2
    print(n, "=>", round(n))

이 코드는 다음을 인쇄합니다.

0.5 => 0
1.5 => 2
2.5 => 2
3.5 => 4
4.5 => 4
5.5 => 6
6.5 => 6

부동 값은 항상 반올림될 것으로 예상했지만, 대신 가장 가까운 짝수로 반올림됩니다.

왜 그런 행동을 하는지, 그리고 올바른 결과를 얻기 위한 가장 좋은 방법은 무엇입니까?

저는 그것을 사용하려고 했지만 결과는 동일합니다.

숫자 유형 섹션에서는 이 동작을 명시적으로 설명합니다.

round(x[, n])
x 반올림하여 n자리로 반올림하고 반올림하여 짝수로 만듭니다.n을 생략하면 기본값은 0입니다.

반올림하여 짝수로 표시합니다.이를 은행원 반올림이라고도 합니다. 항상 반올림하거나 반올림(반올림 오차를 합함)하는 대신 가장 가까운 짝수로 반올림하여 반올림 오차를 평균화합니다.

반올림 동작을 더 잘 제어해야 하는 경우 모듈을 사용하면 사용할 반올림 전략을 정확하게 지정할 수 있습니다.

예를 들어, 절반에서 반올림하는 방법:

>>> from decimal import localcontext, Decimal, ROUND_HALF_UP
>>> with localcontext() as ctx:
...     ctx.rounding = ROUND_HALF_UP
...     for i in range(1, 15, 2):
...         n = Decimal(i) / 2
...         print(n, '=>', n.to_integral_value())
...
0.5 => 1
1.5 => 2
2.5 => 3
3.5 => 4
4.5 => 5
5.5 => 6
6.5 => 7

예:

from decimal import Decimal, ROUND_HALF_UP

Decimal(1.5).quantize(0, ROUND_HALF_UP)

# This also works for rounding to the integer part:
Decimal(1.5).to_integral_value(rounding=ROUND_HALF_UP)

다음을 사용할 수 있습니다.

import math
def normal_round(n):
    if n - math.floor(n) < 0.5:
        return math.floor(n)
    return math.ceil(n)

숫자를 위 또는 아래로 올바르게 반올림합니다.

round()숫자가 짝수인지 홀수인지에 따라 위 또는 아래로 반올림됩니다.간단한 반올림 방법은 다음과 같습니다.

int(num + 0.5)

음수에 대해 올바르게 작동하려면 다음을 사용합니다.

((num > 0) - (num < 0)) * int(abs(num) + 0.5)

은 참로고, 이은큰숫매나우정숫확대자한과 같은 큰 숫자나 정말 을 엉망으로 수 .5000000000000001.0그리고.0.49999999999999994.

Fedor 2612 답변을 사랑합니다.나는 이 함수를 사용하여 소수점 이하의 수를 반올림하려는 사람들을 위해 선택적인 "소수점" 인수로 확장했습니다(예를 들어, 통화를 26.455달러에서 26.46달러로 반올림하고 싶은 경우).

import math

def normal_round(n, decimals=0):
    expoN = n * 10 ** decimals
    if abs(expoN) - abs(math.floor(expoN)) < 0.5:
        return math.floor(expoN) / 10 ** decimals
    return math.ceil(expoN) / 10 ** decimals

oldRounding = round(26.455,2)
newRounding = normal_round(26.455,2)

print(oldRounding)
print(newRounding)

출력:

26.45

26.46

표시되는 동작은 일반적인 IEEE 754 반올림 동작입니다.입력과 동일하게 다른 두 숫자 중에서 선택해야 하는 경우 항상 짝수를 선택합니다.이 동작의 장점은 평균 반올림 효과가 0이라는 것입니다. 마찬가지로 많은 숫자가 위아래로 반올림됩니다.중간 숫자를 일정한 방향으로 반올림하면 반올림이 기대 값에 영향을 줍니다.

목표가 공정한 반올림이라면 여러분이 보고 있는 행동은 옳지만, 그것이 항상 필요한 것은 아닙니다.

원하는 반올림 유형을 얻는 한 가지 방법은 0.5를 추가한 다음 바닥을 차지하는 것입니다.예를 들어, 0.5에서 2.5를 더하면 바닥이 3이고 바닥이 3입니다.

왜 그렇게 복잡하게 만드나요? (양수에만 해당)

def HalfRoundUp(value):
    return int(value + 0.5)

물론 다음과 같은 람다로 만들 수 있습니다.

HalfRoundUp = lambda value: int(value + 0.5)

불행하게도, 이 간단한 답은 음수에서는 작동하지 않지만, 수학의 바닥 함수에서는 고정될 수 있습니다. (양수와 음수 모두에 대해서도 작동합니다.)

from math import floor
def HalfRoundUp(value):
    floor(value + 0.5)

짧은 버전: 십진수 모듈을 사용합니다.2.675가 실제로 2.674999999999982236416599749535322189331056875(정확히)인 파이썬 플로트와는 달리 2.675와 같은 숫자를 정확하게 나타낼 수 있습니다.그리고 원하는 반올림을 지정할 수 있습니다. ROUUND_CEILING, ROUUND_DOWN, ROUUND_HALF_DOWN, ROUUND_HALF_EVEN, ROUUND_HALF_UP, ROUUND_05UP가 모두 옵션입니다.

질문에서 이것은 기본적으로 양의 정수를 2로 나눌 때 문제가 됩니다.은 가장쉬방은법입니다.int(n + 0.5)개개의 번호로

그러나 우리는 이것을 시리즈에 적용할 수 없기 때문에, 예를 들어 판다 데이터 프레임에 대해 루프에 들어가지 않고 다음과 같이 할 수 있는 것은 다음과 같습니다.

import numpy as np
df['rounded_division'] = np.where(df['some_integer'] % 2 == 0, round(df['some_integer']/2,0), round((df['some_integer']+1)/2,0))

일부 솔루션을 반올림하여 작은 값을 추가하는 것은 경우에 따라 예상대로 작동하지 않을 수 있습니다.

예를 들어 위의 기능을 사용하면 다음과 같습니다.

from decimal import Decimal, ROUND_HALF_UP
def round_half_up(x: float, num_decimals: int) -> float:
    if num_decimals < 0:
        raise ValueError("Num decimals needs to be at least 0.")
    target_precision = "1." + "0" * num_decimals
    rounded_x = float(Decimal(x).quantize(Decimal(target_precision), ROUND_HALF_UP))
    return rounded_x
round_half_up(1.35, 1)
1.4
round_half_up(4.35, 1)
4.3

내가 기대했던 곳은4.4 ㅠㅠㅠㅠㅠㅠㅠㅠㅠx먼저 줄로

from decimal import Decimal, ROUND_HALF_UP
def round_half_up(x: float, num_decimals: int) -> float:
    if num_decimals < 0:
        raise ValueError("Num decimals needs to be at least 0.")
    target_precision = "1." + "0" * num_decimals
    rounded_x = float(Decimal(str(x)).quantize(Decimal(target_precision), ROUND_HALF_UP))
    return rounded_x

round_half_up(4.35, 1)
4.4

가장 가까운 짝수로 반올림하는 것은 수치 분야에서 일반적인 관행이 되었습니다."반올림"은 더 큰 결과에 대한 약간의 편향을 생성합니다.

에서는 그서래관, 계의서보면점에과학,보▁so면ific과서에관,▁establish▁the,학계ment점.round올바른 동작을 합니다.

여기 또 다른 해결책이 있습니다.엑셀에서 정상적인 라운딩으로 작동할 것입니다.

from decimal import Decimal, getcontext, ROUND_HALF_UP

round_context = getcontext()
round_context.rounding = ROUND_HALF_UP

def c_round(x, digits, precision=5):
    tmp = round(Decimal(x), precision)
    return float(tmp.__round__(digits))

c_round(0.15, 1) -> 0.2, c_round(0.5, 0) -> 1

은 "학교 라운딩"을 " 패션 라운딩"을달성했습니다.decimal모듈(느린 것으로 판명됨).

def school_round(a_in,n_in):
''' python uses "banking round; while this round 0.05 up" '''
    if (a_in * 10 ** (n_in + 1)) % 10 == 5:
        return round(a_in + 1 / 10 ** (n_in + 1), n_in)
    else:
        return round(a_in, n_in)

예.

print(round(0.005,2)) # 0
print(school_round(0.005,2)) #0.01

그래서 여기에 명확한 작업 사례가 있는지 확인하기 위해 작은 편의 기능을 작성했습니다.

def round_half_up(x: float, num_decimals: int) -> float:
    """Use explicit ROUND HALF UP. See references, for an explanation.

    This is the proper way to round, as taught in school.

    Args:
        x:
        num_decimals:

    Returns:
            https://stackoverflow.com/questions/33019698/how-to-properly-round-up-half-float-numbers-in-python

    """

    if num_decimals < 0:
        raise ValueError("Num decimals needs to be at least 0.")
    target_precision = "1." + "0" * num_decimals
    rounded_x = float(Decimal(x).quantize(Decimal(target_precision), ROUND_HALF_UP))
    return rounded_x

그리고 적절한 테스트 사례 세트가

def test_round_half_up():
    x = 1.5
    y = round_half_up(x, 0)
    assert y == 2.0

    y = round_half_up(x, 1)
    assert y == 1.5

    x = 1.25
    y = round_half_up(x, 1)
    assert y == 1.3

    y = round_half_up(x, 2)
    assert y == 1.25

소수 자릿수를 인수로 사용하는 함수입니다.소수점 이하 반올림하기도 합니다.

import math
def normal_round(n, decimal_places):
    if int((str(n)[-1])) < 5:
        return round(n, decimal_places)
    return round(n + 10**(-1 * (decimal_places+1)), decimal_places)

테스트 사례:

>>> normal_round(5.12465, 4)
5.1247
>>> normal_round(5.12464, 4)
5.1246
>>> normal_round(5.12467, 4)
5.1247
>>> normal_round(5.12463, 4)
5.1246
>>> normal_round(5.1241, 4)
5.1241
>>> normal_round(5.1248, 4)
5.1248
>>> normal_round(5.1248, 3)
5.125
>>> normal_round(5.1242, 3)
5.124

사용할 수 있는 항목:

from decimal import Decimal, ROUND_HALF_UP

for i in range(1, 15, 2):
    n = i / 2
    print(n, "=>", Decimal(str(n)).quantize(Decimal("1"), rounding=ROUND_HALF_UP))

라이브러리가 없는 고전적인 수학적 반올림

def rd(x,y=0):
''' A classical mathematical rounding by Voznica '''
m = int('1'+'0'*y) # multiplier - how many positions to the right
q = x*m # shift to the right by multiplier
c = int(q) # new number
i = int( (q-c)*10 ) # indicator number on the right
if i >= 5:
    c += 1
return c/m

Compare:

print( round(0.49), round(0.51), round(0.5), round(1.5), round(2.5), round(0.15,1))  # 0  1  0  2  2  0.1

print( rd(0.49), rd(0.51), rd(0.5), rd(1.5), rd(2.5), rd(0.15,1))  # 0  1  1  2  3  0.2

은는은▁that것▁knowing.round(9.99,0)로의 라운드.int=10그리고.int(9.99)로의 라운드.int=9성공을 가져옵니다.

목에 더 와 더 합니다.value

    def get_half_round_numers(self, value):
        """
        Returns dict with upper_half_rn and lower_half_rn
        :param value:
        :return:
        """
        hrns = {}
        if not isinstance(value, float):
            print("Error>Input is not a float. None return.")
            return None

        value = round(value,2)
        whole = int(value) # Rounds 9.99 to 9
        remainder = (value - whole) * 100

        if remainder >= 51:
            hrns['upper_half_rn'] = round(round(value,0),2)  # Rounds 9.99 to 10
            hrns['lower_half_rn'] = round(round(value,0) - 0.5,2)
        else:
            hrns['lower_half_rn'] = round(int(value),2)
            hrns['upper_half_rn'] = round(int(value) + 0.5,2)

        return hrns

일부 테스트:

여기에 이미지 설명 입력

야!

import math
# round tossing n digits from the end
def my_round(n, toss=1):

    def normal_round(n):
        if isinstance(n, int):
            return n
        intn, dec = str(n).split(".")
        if int(dec[-1]) >= 5:
            if len(dec) == 1:
                return math.ceil(n)
            else:
                return float(intn + "." + str(int(dec[:-1]) + 1))
        else:
            return float(intn + "." + dec[:-1])

    while toss >= 1:
        n = normal_round(n)
        toss -= 1
    return n


for n in [1.25, 7.3576, 30.56]:
    print(my_round(n, 2))

1.0
7.36
31
import math
def round_half_up(x: float) -> int:
    if x < 0:
        return math.trunc(x) if -x % 1 < 0.5 else math.floor(x)
    else:
        return math.trunc(x) if  x % 1 < 0.5 else math.ceil(x)

이것은 심지어 다음과 같은 코너 케이스에도 효과가 있습니다.0.49999999999999994그리고.5000000000000001.0.

해보세요.

def round(num):
    return round(num + 10**(-9))

이후로 작동할 것입니다.num = x.5항상 그럴 것입니다.x.5 + 0.00...01보다 가까운 과정에서x+1따라서 라운드 함수는 제대로 작동하고 라운드할 것입니다.x.5로.x+1

언급URL : https://stackoverflow.com/questions/33019698/how-to-properly-round-up-half-float-numbers

반응형