From b2ee1756ef9abe704b9fb6a9ffee53efed46dea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Thu, 2 Feb 2017 12:11:10 +0100 Subject: [PATCH] Add ceil and floor as uom rounding methods issue6222 review27041002 --- CHANGELOG | 2 ++ tests/test_product.py | 31 +++++++++++++++++-------------- uom.py | 35 +++++++++++++++++++++++++++++------ 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 320db089ab..8b3d7d5a48 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Add ceil and floor as uom rounding + Version 4.2.0 - 2016-11-28 * Bug fixes (see mercurial logs for details) * Add relate from Template to Products diff --git a/tests/test_product.py b/tests/test_product.py index 99fc51926c..88d7089f43 100644 --- a/tests/test_product.py +++ b/tests/test_product.py @@ -313,24 +313,27 @@ class ProductTestCase(ModuleTestCase): ], count=True), 2) @with_transaction() - def test_uom_round(self): - 'Test uom round function' + def test_uom_rounding(self): + 'Test uom rounding functions' pool = Pool() Uom = pool.get('product.uom') tests = [ - (2.53, .1, 2.5), - (3.8, .1, 3.8), - (3.7, .1, 3.7), - (1.3, .5, 1.5), - (1.1, .3, 1.2), - (17, 10, 20), - (7, 10, 10), - (4, 10, 0), - (17, 15, 15), - (2.5, 1.4, 2.8), + (2.53, .1, 2.5, 2.6, 2.5), + (3.8, .1, 3.8, 3.8, 3.8), + (3.7, .1, 3.7, 3.7, 3.7), + (1.3, .5, 1.5, 1.5, 1.0), + (1.1, .3, 1.2, 1.2, 0.9), + (17, 10, 20, 20, 10), + (7, 10, 10, 10, 0), + (4, 10, 0, 10, 0), + (17, 15, 15, 30, 15), + (2.5, 1.4, 2.8, 2.8, 1.4), ] - for number, precision, result in tests: - self.assertEqual(Uom(rounding=precision).round(number), result) + for number, precision, round, ceil, floor in tests: + uom = Uom(rounding=precision) + self.assertEqual(uom.round(number), round) + self.assertEqual(uom.ceil(number), ceil) + self.assertEqual(uom.floor(number), floor) @with_transaction() def test_product_order(self): diff --git a/uom.py b/uom.py index df6ad87bcf..338b7817f1 100644 --- a/uom.py +++ b/uom.py @@ -3,6 +3,7 @@ from __future__ import division from decimal import Decimal from sql import Table +from math import ceil, floor, log10 from trytond.model import ModelView, ModelSQL, fields, Check from trytond.pyson import Eval @@ -135,12 +136,13 @@ class Uom(ModelSQL, ModelView): ] def round(self, number): - precision = self.rounding - i, d = divmod(precision, 1) - base = round(number / precision) - # Instead of multiply by the decimal part, we must divide by the - # integer to avoid precision lost due to float - return (base * i) + ((base / (1 / d)) if d != 0 else 0) + return _round(self, number, func=round) + + def ceil(self, number): + return _round(self, number, func=ceil) + + def floor(self, number): + return _round(self, number, func=floor) @classmethod def validate(cls, uoms): @@ -265,3 +267,24 @@ class Uom(ModelSQL, ModelView): new_price = new_price / Decimal(rate_format % to_uom.rate) return new_price + + +def _round(uom, number, func=round): + precision = uom.rounding + # Convert precision into an integer greater than 1 to avoid precision lost. + # This works for most cases because rounding is often: n * 10**i + if precision < 1: + exp = -floor(log10(precision)) + factor = 10 ** exp + number *= factor + precision *= factor + else: + factor = 1 + # Divide by factor which is an integer to avoid precision lost due to + # multiplication by float < 1. + # example: + # >>> 3 * 0.1 + # 0.30000000000000004 + # >>> 3 / 10. + # 0.3 + return func(number / precision) * precision / factor