1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
|
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from math import log10
from odoo.tests.common import TransactionCase
from odoo.tools import float_compare, float_is_zero, float_repr, float_round, float_split_str, pycompat
class TestFloatPrecision(TransactionCase):
""" Tests on float precision. """
def test_rounding_02(self):
""" Test rounding methods with 2 digits. """
currency = self.env.ref('base.EUR')
def try_round(amount, expected):
digits = max(0, -int(log10(currency.rounding)))
result = float_repr(currency.round(amount), precision_digits=digits)
self.assertEqual(result, expected, 'Rounding error: got %s, expected %s' % (result, expected))
try_round(2.674,'2.67')
try_round(2.675,'2.68') # in Python 2.7.2, round(2.675,2) gives 2.67
try_round(-2.675,'-2.68') # in Python 2.7.2, round(2.675,2) gives 2.67
try_round(0.001,'0.00')
try_round(-0.001,'-0.00')
try_round(0.0049,'0.00') # 0.0049 is closer to 0 than to 0.01, so should round down
try_round(0.005,'0.01') # the rule is to round half away from zero
try_round(-0.005,'-0.01') # the rule is to round half away from zero
def try_zero(amount, expected):
self.assertEqual(currency.is_zero(amount), expected,
"Rounding error: %s should be zero!" % amount)
try_zero(0.01, False)
try_zero(-0.01, False)
try_zero(0.001, True)
try_zero(-0.001, True)
try_zero(0.0046, True)
try_zero(-0.0046, True)
try_zero(2.68-2.675, False) # 2.68 - 2.675 = 0.005 -> rounds to 0.01
try_zero(2.68-2.676, True) # 2.68 - 2.675 = 0.004 -> rounds to 0.0
try_zero(2.676-2.68, True) # 2.675 - 2.68 = -0.004 -> rounds to -0.0
try_zero(2.675-2.68, False) # 2.675 - 2.68 = -0.005 -> rounds to -0.01
def try_compare(amount1, amount2, expected):
self.assertEqual(currency.compare_amounts(amount1, amount2), expected,
"Rounding error, compare_amounts(%s,%s) should be %s" % (amount1, amount2, expected))
try_compare(0.001, 0.001, 0)
try_compare(-0.001, -0.001, 0)
try_compare(0.001, 0.002, 0)
try_compare(-0.001, -0.002, 0)
try_compare(2.675, 2.68, 0)
try_compare(2.676, 2.68, 0)
try_compare(-2.676, -2.68, 0)
try_compare(2.674, 2.68, -1)
try_compare(-2.674, -2.68, 1)
try_compare(3, 2.68, 1)
try_compare(-3, -2.68, -1)
try_compare(0.01, 0, 1)
try_compare(-0.01, 0, -1)
def test_rounding_03(self):
""" Test rounding methods with 3 digits. """
def try_round(amount, expected, digits=3, method='HALF-UP'):
value = float_round(amount, precision_digits=digits, rounding_method=method)
result = float_repr(value, precision_digits=digits)
self.assertEqual(result, expected, 'Rounding error: got %s, expected %s' % (result, expected))
try_round(2.6745, '2.675')
try_round(-2.6745, '-2.675')
try_round(2.6744, '2.674')
try_round(-2.6744, '-2.674')
try_round(0.0004, '0.000')
try_round(-0.0004, '-0.000')
try_round(357.4555, '357.456')
try_round(-357.4555, '-357.456')
try_round(457.4554, '457.455')
try_round(-457.4554, '-457.455')
# Try some rounding value with rounding method UP instead of HALF-UP
# We use 8.175 because when normalizing 8.175 with precision_digits=3 it gives
# us 8175,0000000001234 as value, and if not handle correctly the rounding UP
# value will be incorrect (should be 8,175 and not 8,176)
try_round(8.175, '8.175', method='UP')
try_round(8.1751, '8.176', method='UP')
try_round(-8.175, '-8.175', method='UP')
try_round(-8.1751, '-8.176', method='UP')
try_round(-6.000, '-6.000', method='UP')
try_round(1.8, '2', 0, method='UP')
try_round(-1.8, '-2', 0, method='UP')
# Try some rounding value with rounding method DOWN instead of HALF-UP
# We use 2.425 because when normalizing 2.425 with precision_digits=3 it gives
# us 2424.9999999999995 as value, and if not handle correctly the rounding DOWN
# value will be incorrect (should be 2.425 and not 2.424)
try_round(2.425, '2.425', method='DOWN')
try_round(2.4249, '2.424', method='DOWN')
try_round(-2.425, '-2.425', method='DOWN')
try_round(-2.4249, '-2.424', method='DOWN')
try_round(-2.500, '-2.500', method='DOWN')
try_round(1.8, '1', 0, method='DOWN')
try_round(-1.8, '-1', 0, method='DOWN')
# Extended float range test, inspired by Cloves Almeida's test on bug #882036.
fractions = [.0, .015, .01499, .675, .67499, .4555, .4555, .45555]
expecteds = ['.00', '.02', '.01', '.68', '.67', '.46', '.456', '.4556']
precisions = [2, 2, 2, 2, 2, 2, 3, 4]
# Note: max precision for double floats is 53 bits of precision or
# 17 significant decimal digits
for magnitude in range(7):
for frac, exp, prec in pycompat.izip(fractions, expecteds, precisions):
for sign in [-1,1]:
for x in range(0, 10000, 97):
n = x * 10 ** magnitude
f = sign * (n + frac)
f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp
try_round(f, f_exp, digits=prec)
def try_zero(amount, expected):
self.assertEqual(float_is_zero(amount, precision_digits=3), expected,
"Rounding error: %s should be zero!" % amount)
try_zero(0.0002, True)
try_zero(-0.0002, True)
try_zero(0.00034, True)
try_zero(0.0005, False)
try_zero(-0.0005, False)
try_zero(0.0008, False)
try_zero(-0.0008, False)
def try_compare(amount1, amount2, expected):
self.assertEqual(float_compare(amount1, amount2, precision_digits=3), expected,
"Rounding error, compare_amounts(%s,%s) should be %s" % (amount1, amount2, expected))
try_compare(0.0003, 0.0004, 0)
try_compare(-0.0003, -0.0004, 0)
try_compare(0.0002, 0.0005, -1)
try_compare(-0.0002, -0.0005, 1)
try_compare(0.0009, 0.0004, 1)
try_compare(-0.0009, -0.0004, -1)
try_compare(557.4555, 557.4556, 0)
try_compare(-557.4555, -557.4556, 0)
try_compare(657.4444, 657.445, -1)
try_compare(-657.4444, -657.445, 1)
# Rounding to unusual rounding units (e.g. coin values)
def try_round(amount, expected, precision_rounding=None, method='HALF-UP'):
value = float_round(amount, precision_rounding=precision_rounding, rounding_method=method)
result = float_repr(value, precision_digits=2)
self.assertEqual(result, expected, 'Rounding error: got %s, expected %s' % (result, expected))
try_round(-457.4554, '-457.45', precision_rounding=0.05)
try_round(457.444, '457.50', precision_rounding=0.5)
try_round(457.3, '455.00', precision_rounding=5)
try_round(457.5, '460.00', precision_rounding=5)
try_round(457.1, '456.00', precision_rounding=3)
try_round(2.5, '2.50', precision_rounding=0.05, method='DOWN')
try_round(-2.5, '-2.50', precision_rounding=0.05, method='DOWN')
def test_rounding_04(self):
""" check that proper rounding is performed for float persistence """
currency = self.env.ref('base.EUR')
currency_rate = self.env['res.currency.rate']
def try_roundtrip(value, expected, date):
rate = currency_rate.create({'name': date,
'rate': value,
'currency_id': currency.id})
self.assertEqual(rate.rate, expected,
'Roundtrip error: got %s back from db, expected %s' % (rate, expected))
# res.currency.rate uses 6 digits of precision by default
try_roundtrip(2.6748955, 2.674896, '2000-01-01')
try_roundtrip(-2.6748955, -2.674896, '2000-01-02')
try_roundtrip(10000.999999, 10000.999999, '2000-01-03')
try_roundtrip(-10000.999999, -10000.999999, '2000-01-04')
def test_float_split_05(self):
""" Test split method with 2 digits. """
currency = self.env.ref('base.EUR')
def try_split(value, expected):
digits = max(0, -int(log10(currency.rounding)))
result = float_split_str(value, precision_digits=digits)
self.assertEqual(result, expected, 'Split error: got %s, expected %s' % (result, expected))
try_split(2.674, ('2', '67'))
try_split(2.675, ('2', '68')) # in Python 2.7.2, round(2.675,2) gives 2.67
try_split(-2.675, ('-2', '68')) # in Python 2.7.2, round(2.675,2) gives 2.67
try_split(0.001, ('0', '00'))
try_split(-0.001, ('-0', '00'))
def test_rounding_invalid(self):
""" verify that invalid parameters are forbidden """
with self.assertRaises(AssertionError):
float_is_zero(0.01, precision_digits=3, precision_rounding=0.01)
with self.assertRaises(AssertionError):
float_compare(0.01, 0.02, precision_digits=3, precision_rounding=0.01)
with self.assertRaises(AssertionError):
float_round(0.01, precision_digits=3, precision_rounding=0.01)
def test_amount_to_text_10(self):
""" verify that amount_to_text works as expected """
currency = self.env.ref('base.EUR')
amount_target = currency.amount_to_text(0.29)
amount_test = currency.amount_to_text(0.28)
self.assertNotEqual(amount_test, amount_target,
"Amount in text should not depend on float representation")
|