feat: configurable rounding methods
This commit is contained in:
parent
640a543dae
commit
48f63f53ab
4 changed files with 82 additions and 17 deletions
|
|
@ -17,10 +17,11 @@
|
|||
"date_format",
|
||||
"time_format",
|
||||
"number_format",
|
||||
"first_day_of_the_week",
|
||||
"column_break_7",
|
||||
"float_precision",
|
||||
"currency_precision",
|
||||
"first_day_of_the_week",
|
||||
"rounding_method",
|
||||
"sec_backup_limit",
|
||||
"backup_limit",
|
||||
"encrypt_backup",
|
||||
|
|
@ -520,12 +521,19 @@
|
|||
"fieldname": "login_with_email_link_expiry",
|
||||
"fieldtype": "Int",
|
||||
"label": "Login with email link expiry (in minutes)"
|
||||
},
|
||||
{
|
||||
"default": "Bankers Rounding",
|
||||
"fieldname": "rounding_method",
|
||||
"fieldtype": "Select",
|
||||
"label": "Rounding Method",
|
||||
"options": "Bankers Rounding\nRounding half away from zero"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-12-20 21:45:37.651668",
|
||||
"modified": "2023-03-06 11:31:19.144956",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "System Settings",
|
||||
|
|
|
|||
|
|
@ -6,25 +6,28 @@ import json
|
|||
import os
|
||||
import sys
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from decimal import Decimal
|
||||
from decimal import ROUND_HALF_UP, Decimal, localcontext
|
||||
from enum import Enum
|
||||
from io import StringIO
|
||||
from mimetypes import guess_type
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytz
|
||||
from hypothesis import given
|
||||
from hypothesis import strategies as st
|
||||
from PIL import Image
|
||||
|
||||
import frappe
|
||||
from frappe.installer import parse_app_name
|
||||
from frappe.model.document import Document
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import (
|
||||
ceil,
|
||||
dict_to_str,
|
||||
evaluate_filters,
|
||||
execute_in_shell,
|
||||
floor,
|
||||
flt,
|
||||
format_timedelta,
|
||||
get_bench_path,
|
||||
get_file_timestamp,
|
||||
|
|
@ -1001,3 +1004,45 @@ class TestTBSanitization(FrappeTestCase):
|
|||
self.assertIn("********", traceback)
|
||||
self.assertIn("password =", traceback)
|
||||
self.assertIn("safe_value", traceback)
|
||||
|
||||
|
||||
class TestRounding(FrappeTestCase):
|
||||
@change_settings("System Settings", {"rounding_method": "Rounding half away from zero"})
|
||||
def test_normal_rounding(self):
|
||||
self.assertEqual(flt("what"), 0)
|
||||
|
||||
self.assertEqual(flt("0.5", 0), 1)
|
||||
self.assertEqual(flt("0.3"), 0.3)
|
||||
|
||||
self.assertEqual(flt("1.5", 0), 2)
|
||||
|
||||
# positive rounding to integers
|
||||
self.assertEqual(flt(0.4, 0), 0)
|
||||
self.assertEqual(flt(0.5, 0), 1)
|
||||
self.assertEqual(flt(1.455, 0), 1)
|
||||
self.assertEqual(flt(1.5, 0), 2)
|
||||
|
||||
# negative rounding to integers
|
||||
self.assertEqual(flt(-0.5, 0), -1)
|
||||
self.assertEqual(flt(-1.5, 0), -2)
|
||||
|
||||
# negative precision i.e. round to nearest 10th
|
||||
self.assertEqual(flt(123, -1), 120)
|
||||
self.assertEqual(flt(125, -1), 130)
|
||||
self.assertEqual(flt(134.45, -1), 130)
|
||||
self.assertEqual(flt(135, -1), 140)
|
||||
|
||||
# # positive multiple digit rounding
|
||||
self.assertEqual(flt(1.25, 1), 1.3)
|
||||
self.assertEqual(flt(0.15, 1), 0.2)
|
||||
|
||||
# # negative multiple digit rounding
|
||||
self.assertEqual(flt(-1.25, 1), -1.3)
|
||||
self.assertEqual(flt(-0.15, 1), -0.2)
|
||||
|
||||
@change_settings("System Settings", {"rounding_method": "Rounding half away from zero"})
|
||||
@given(st.decimals(min_value=-1e8, max_value=1e8), st.integers(min_value=-2, max_value=4))
|
||||
def test_normal_rounding_property(self, number, precision):
|
||||
with localcontext() as ctx:
|
||||
ctx.rounding = ROUND_HALF_UP
|
||||
self.assertEqual(Decimal(str(flt(float(number), precision))), round(number, precision))
|
||||
|
|
|
|||
|
|
@ -1047,25 +1047,36 @@ def sbool(x: str) -> bool | Any:
|
|||
|
||||
|
||||
def rounded(num, precision=0):
|
||||
"""round method for round halfs to nearest even algorithm aka banker's rounding - compatible with python3"""
|
||||
"""Round according to method set in system setting, defaults to banker's rounding"""
|
||||
precision = cint(precision)
|
||||
multiplier = 10**precision
|
||||
|
||||
# avoid rounding errors
|
||||
num = round(num * multiplier if precision else num, 8)
|
||||
rounding_method = frappe.get_system_settings("rounding_method") or "Bankers Rounding"
|
||||
|
||||
floor_num = math.floor(num)
|
||||
decimal_part = num - floor_num
|
||||
if rounding_method == "Bankers Rounding":
|
||||
# avoid rounding errors
|
||||
multiplier = 10**precision
|
||||
num = round(num * multiplier if precision else num, 8)
|
||||
|
||||
if not precision and decimal_part == 0.5:
|
||||
num = floor_num if (floor_num % 2 == 0) else floor_num + 1
|
||||
else:
|
||||
if decimal_part == 0.5:
|
||||
num = floor_num + 1
|
||||
floor_num = math.floor(num)
|
||||
decimal_part = num - floor_num
|
||||
|
||||
if not precision and decimal_part == 0.5:
|
||||
num = floor_num if (floor_num % 2 == 0) else floor_num + 1
|
||||
else:
|
||||
num = round(num)
|
||||
if decimal_part == 0.5:
|
||||
num = floor_num + 1
|
||||
else:
|
||||
num = round(num)
|
||||
|
||||
return (num / multiplier) if precision else num
|
||||
return (num / multiplier) if precision else num
|
||||
|
||||
elif rounding_method == "Rounding half away from zero":
|
||||
if num == 0:
|
||||
return 0.0
|
||||
# Epsilon is small correctional value added to correctly round numbers which can't be
|
||||
# represented in IEEE 754 representation.
|
||||
epsilon = 2.0 ** (math.log(abs(num), 2) - 52.0)
|
||||
return round(num + math.copysign(epsilon, num), precision)
|
||||
|
||||
|
||||
def remainder(numerator: NumericType, denominator: NumericType, precision: int = 2) -> NumericType:
|
||||
|
|
|
|||
|
|
@ -102,3 +102,4 @@ Faker = "~=13.12.1"
|
|||
pyngrok = "~=5.0.5"
|
||||
unittest-xml-reporting = "~=3.0.4"
|
||||
watchdog = "~=2.1.9"
|
||||
hypothesis = "~=6.68.2"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue