[MERGE] sync with latest trunk
bzr revid: odo@openerp.com-20111222202356-6u22um5kc9yuusxc
This commit is contained in:
@@ -257,6 +257,9 @@
|
||||
</group>
|
||||
</page>
|
||||
<page string="Header/Footer" groups="base.group_extended">
|
||||
<group colspan="2" col="4">
|
||||
<field name="paper_format" on_change="onchange_paper_format(paper_format)"/>
|
||||
</group>
|
||||
<field colspan="4" name="rml_header" nolabel="1"/>
|
||||
</page>
|
||||
<page string="Internal Header/Footer" groups="base.group_extended">
|
||||
|
||||
+6093
-1205
File diff suppressed because it is too large
Load Diff
@@ -144,6 +144,7 @@ class res_company(osv.osv):
|
||||
'website': fields.related('partner_id', 'website', string="Website", type="char", size=64),
|
||||
'vat': fields.related('partner_id', 'vat', string="Tax ID", type="char", size=32),
|
||||
'company_registry': fields.char('Company Registry', size=64),
|
||||
'paper_format': fields.selection([('a4', 'A4'), ('us_letter', 'US Letter')], "Paper Format", required=True),
|
||||
}
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', 'The company name must be unique !')
|
||||
@@ -277,29 +278,31 @@ class res_company(osv.osv):
|
||||
finally:
|
||||
header_file.close()
|
||||
except:
|
||||
return """
|
||||
return self._header_a4
|
||||
|
||||
_header_main = """
|
||||
<header>
|
||||
<pageTemplate>
|
||||
<frame id="first" x1="1.3cm" y1="2.5cm" height="23.0cm" width="19cm"/>
|
||||
<frame id="first" x1="1.3cm" y1="2.5cm" height="%s" width="19.0cm"/>
|
||||
<pageGraphics>
|
||||
<!-- You Logo - Change X,Y,Width and Height -->
|
||||
<image x="1.3cm" y="27.6cm" height="40.0" >[[ company.logo or removeParentNode('image') ]]</image>
|
||||
<image x="1.3cm" y="%s" height="40.0" >[[ company.logo or removeParentNode('image') ]]</image>
|
||||
<setFont name="DejaVu Sans" size="8"/>
|
||||
<fill color="black"/>
|
||||
<stroke color="black"/>
|
||||
<lines>1.3cm 27.7cm 20cm 27.7cm</lines>
|
||||
<lines>1.3cm %s 20cm %s</lines>
|
||||
|
||||
<drawRightString x="20cm" y="27.8cm">[[ company.rml_header1 ]]</drawRightString>
|
||||
<drawRightString x="20cm" y="%s">[[ company.rml_header1 ]]</drawRightString>
|
||||
|
||||
|
||||
<drawString x="1.3cm" y="27.2cm">[[ company.partner_id.name ]]</drawString>
|
||||
<drawString x="1.3cm" y="26.8cm">[[ company.partner_id.address and company.partner_id.address[0].street or '' ]]</drawString>
|
||||
<drawString x="1.3cm" y="26.4cm">[[ company.partner_id.address and company.partner_id.address[0].zip or '' ]] [[ company.partner_id.address and company.partner_id.address[0].city or '' ]] - [[ company.partner_id.address and company.partner_id.address[0].country_id and company.partner_id.address[0].country_id.name or '']]</drawString>
|
||||
<drawString x="1.3cm" y="26.0cm">Phone:</drawString>
|
||||
<drawRightString x="7cm" y="26.0cm">[[ company.partner_id.address and company.partner_id.address[0].phone or '' ]]</drawRightString>
|
||||
<drawString x="1.3cm" y="25.6cm">Mail:</drawString>
|
||||
<drawRightString x="7cm" y="25.6cm">[[ company.partner_id.address and company.partner_id.address[0].email or '' ]]</drawRightString>
|
||||
<lines>1.3cm 25.5cm 7cm 25.5cm</lines>
|
||||
<drawString x="1.3cm" y="%s">[[ company.partner_id.name ]]</drawString>
|
||||
<drawString x="1.3cm" y="%s">[[ company.partner_id.address and company.partner_id.address[0].street or '' ]]</drawString>
|
||||
<drawString x="1.3cm" y="%s">[[ company.partner_id.address and company.partner_id.address[0].zip or '' ]] [[ company.partner_id.address and company.partner_id.address[0].city or '' ]] - [[ company.partner_id.address and company.partner_id.address[0].country_id and company.partner_id.address[0].country_id.name or '']]</drawString>
|
||||
<drawString x="1.3cm" y="%s">Phone:</drawString>
|
||||
<drawRightString x="7cm" y="%s">[[ company.partner_id.address and company.partner_id.address[0].phone or '' ]]</drawRightString>
|
||||
<drawString x="1.3cm" y="%s">Mail:</drawString>
|
||||
<drawRightString x="7cm" y="%s">[[ company.partner_id.address and company.partner_id.address[0].email or '' ]]</drawRightString>
|
||||
<lines>1.3cm %s 7cm %s</lines>
|
||||
|
||||
<!--page bottom-->
|
||||
|
||||
@@ -311,8 +314,18 @@ class res_company(osv.osv):
|
||||
</pageGraphics>
|
||||
</pageTemplate>
|
||||
</header>"""
|
||||
|
||||
_header_a4 = _header_main % ('23.0cm', '27.6cm', '27.7cm', '27.7cm', '27.8cm', '27.2cm', '26.8cm', '26.4cm', '26.0cm', '26.0cm', '25.6cm', '25.6cm', '25.5cm', '25.5cm')
|
||||
_header_letter = _header_main % ('21.3cm', '25.9cm', '26.0cm', '26.0cm', '26.1cm', '25.5cm', '25.1cm', '24.7cm', '24.3cm', '24.3cm', '23.9cm', '23.9cm', '23.8cm', '23.8cm')
|
||||
|
||||
def onchange_paper_format(self, cr, uid, ids, paper_format, context=None):
|
||||
if paper_format == 'us_letter':
|
||||
return {'value': {'rml_header': self._header_letter}}
|
||||
return {'value': {'rml_header': self._header_a4}}
|
||||
|
||||
_defaults = {
|
||||
'currency_id': _get_euro,
|
||||
'paper_format': 'a4',
|
||||
'rml_header':_get_header,
|
||||
'rml_header2': _header2,
|
||||
'rml_header3': _header3,
|
||||
|
||||
@@ -24,7 +24,7 @@ import netsvc
|
||||
from osv import fields, osv
|
||||
import tools
|
||||
|
||||
from tools.misc import currency
|
||||
from tools import float_round, float_is_zero, float_compare
|
||||
from tools.translate import _
|
||||
|
||||
CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?')
|
||||
@@ -127,15 +127,49 @@ class res_currency(osv.osv):
|
||||
return [(x['id'], tools.ustr(x['name']) + (x['symbol'] and (' (' + tools.ustr(x['symbol']) + ')') or '')) for x in reads]
|
||||
|
||||
def round(self, cr, uid, currency, amount):
|
||||
if currency.rounding == 0:
|
||||
return 0.0
|
||||
else:
|
||||
# /!\ First member below must be rounded to full unit!
|
||||
# Do not pass a rounding digits value to round()
|
||||
return round(amount / currency.rounding) * currency.rounding
|
||||
"""Return ``amount`` rounded according to ``currency``'s
|
||||
rounding rules.
|
||||
|
||||
:param browse_record currency: currency for which we are rounding
|
||||
:param float amount: the amount to round
|
||||
:return: rounded float
|
||||
"""
|
||||
return float_round(amount, precision_rounding=currency.rounding)
|
||||
|
||||
def compare_amounts(self, cr, uid, currency, amount1, amount2):
|
||||
"""Compare ``amount1`` and ``amount2`` after rounding them according to the
|
||||
given currency's precision..
|
||||
An amount is considered lower/greater than another amount if their rounded
|
||||
value is different. This is not the same as having a non-zero difference!
|
||||
|
||||
For example 1.432 and 1.431 are equal at 2 digits precision,
|
||||
so this method would return 0.
|
||||
However 0.006 and 0.002 are considered different (returns 1) because
|
||||
they respectively round to 0.01 and 0.0, even though
|
||||
0.006-0.002 = 0.004 which would be considered zero at 2 digits precision.
|
||||
|
||||
:param browse_record currency: currency for which we are rounding
|
||||
:param float amount1: first amount to compare
|
||||
:param float amount2: second amount to compare
|
||||
:return: (resp.) -1, 0 or 1, if ``amount1`` is (resp.) lower than,
|
||||
equal to, or greater than ``amount2``, according to
|
||||
``currency``'s rounding.
|
||||
"""
|
||||
return float_compare(amount1, amount2, precision_rounding=currency.rounding)
|
||||
|
||||
def is_zero(self, cr, uid, currency, amount):
|
||||
return abs(self.round(cr, uid, currency, amount)) < currency.rounding
|
||||
"""Returns true if ``amount`` is small enough to be treated as
|
||||
zero according to ``currency``'s rounding rules.
|
||||
|
||||
Warning: ``is_zero(amount1-amount2)`` is not always equivalent to
|
||||
``compare_amounts(amount1,amount2) == 0``, as the former will round after
|
||||
computing the difference, while the latter will round before, giving
|
||||
different results for e.g. 0.006 and 0.002 at 2 digits precision.
|
||||
|
||||
:param browse_record currency: currency for which we are rounding
|
||||
:param float amount: amount to compare with currency's zero
|
||||
"""
|
||||
return float_is_zero(amount, precision_rounding=currency.rounding)
|
||||
|
||||
def _get_conversion_rate(self, cr, uid, from_currency, to_currency, context=None):
|
||||
if context is None:
|
||||
|
||||
@@ -144,3 +144,163 @@
|
||||
!python {model: res.partner.category}: |
|
||||
self.pool._init = True
|
||||
|
||||
-
|
||||
"Float precision tests: verify that float rounding methods are working correctly via res.currency"
|
||||
-
|
||||
!python {model: res.currency}: |
|
||||
from tools import float_repr
|
||||
from math import log10
|
||||
currency = self.browse(cr, uid, ref('base.EUR'))
|
||||
def try_round(amount, expected, self=self, cr=cr, currency=currency, float_repr=float_repr,
|
||||
log10=log10):
|
||||
digits = max(0,-int(log10(currency.rounding)))
|
||||
result = float_repr(self.round(cr, 1, currency, amount), precision_digits=digits)
|
||||
assert 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=self, cr=cr, currency=currency):
|
||||
assert self.is_zero(cr, 1, currency, 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=self, cr=cr, currency=currency):
|
||||
assert self.compare_amounts(cr, 1, currency, 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)
|
||||
|
||||
-
|
||||
"Float precision tests: verify that float rounding methods are working correctly via tools"
|
||||
-
|
||||
!python {model: res.currency}: |
|
||||
from tools import float_compare, float_is_zero, float_round, float_repr
|
||||
def try_round(amount, expected, precision_digits=3, float_round=float_round, float_repr=float_repr):
|
||||
result = float_repr(float_round(amount, precision_digits=precision_digits),
|
||||
precision_digits=precision_digits)
|
||||
assert 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')
|
||||
|
||||
# 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 i in xrange(len(fractions)):
|
||||
frac, exp, prec = fractions[i], expecteds[i], precisions[i]
|
||||
for sign in [-1,1]:
|
||||
for x in xrange(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, precision_digits=prec)
|
||||
|
||||
|
||||
def try_zero(amount, expected, float_is_zero=float_is_zero):
|
||||
assert 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, float_compare=float_compare):
|
||||
assert 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, float_round=float_round, float_repr=float_repr):
|
||||
result = float_repr(float_round(amount, precision_rounding=precision_rounding),
|
||||
precision_digits=2)
|
||||
assert 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)
|
||||
|
||||
-
|
||||
"Float precision tests: check that proper rounding is performed for float persistence"
|
||||
-
|
||||
!python {model: res.currency}: |
|
||||
currency = self.browse(cr, uid, ref('base.EUR'))
|
||||
res_currency_rate = self.pool.get('res.currency.rate')
|
||||
from tools import float_compare, float_is_zero, float_round, float_repr
|
||||
def try_roundtrip(value, expected, self=self, cr=cr, currency=currency,
|
||||
res_currency_rate=res_currency_rate):
|
||||
rate_id = res_currency_rate.create(cr, 1, {'name':'2000-01-01',
|
||||
'rate': value,
|
||||
'currency_id': currency.id})
|
||||
rate = res_currency_rate.read(cr, 1, rate_id, ['rate'])['rate']
|
||||
assert 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)
|
||||
try_roundtrip(-2.6748955, -2.674896)
|
||||
try_roundtrip(10000.999999, 10000.999999)
|
||||
try_roundtrip(-10000.999999, -10000.999999)
|
||||
|
||||
-
|
||||
"Float precision tests: verify that invalid parameters are forbidden"
|
||||
-
|
||||
!python {model: res.currency}: |
|
||||
from tools import float_compare, float_is_zero, float_round
|
||||
try:
|
||||
float_is_zero(0.01, precision_digits=3, precision_rounding=0.01)
|
||||
except AssertionError:
|
||||
pass
|
||||
try:
|
||||
float_compare(0.01, 0.02, precision_digits=3, precision_rounding=0.01)
|
||||
except AssertionError:
|
||||
pass
|
||||
try:
|
||||
float_round(0.01, precision_digits=3, precision_rounding=0.01)
|
||||
except AssertionError:
|
||||
pass
|
||||
|
||||
@@ -37,6 +37,8 @@ from openerp.tools.safe_eval import safe_eval as eval
|
||||
from reportlab.lib.units import inch,cm,mm
|
||||
from openerp.tools.misc import file_open
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.lib.pagesizes import A4, letter
|
||||
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
@@ -910,9 +912,13 @@ class _rml_template(object):
|
||||
self.path = path
|
||||
self.title = title
|
||||
|
||||
if not node.get('pageSize'):
|
||||
pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
|
||||
else:
|
||||
pagesize_map = {'a4': A4,
|
||||
'us_letter': letter
|
||||
}
|
||||
pageSize = A4
|
||||
if self.localcontext.get('company'):
|
||||
pageSize = pagesize_map.get(self.localcontext.get('company').paper_format, A4)
|
||||
if node.get('pageSize'):
|
||||
ps = map(lambda x:x.strip(), node.get('pageSize').replace(')', '').replace('(', '').split(','))
|
||||
pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ import socket
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
import openerp
|
||||
import openerp.netsvc as netsvc
|
||||
import openerp.tiny_socket as tiny_socket
|
||||
import openerp.tools as tools
|
||||
@@ -66,11 +66,11 @@ class TinySocketClientThread(threading.Thread):
|
||||
break
|
||||
except Exception, e:
|
||||
try:
|
||||
new_e = Exception(tools.exception_to_unicode(e)) # avoid problems of pickeling
|
||||
tb = getattr(e, 'traceback', sys.exc_info())
|
||||
tb_s = "".join(traceback.format_exception(*tb))
|
||||
valid_exception = Exception(netrpc_handle_exception_legacy(e))
|
||||
valid_traceback = getattr(e, 'traceback', sys.exc_info())
|
||||
formatted_traceback = "".join(traceback.format_exception(*valid_traceback))
|
||||
logging.getLogger('web-services').debug("netrpc: communication-level exception", exc_info=True)
|
||||
ts.mysend(new_e, exception=True, traceback=tb_s)
|
||||
ts.mysend(valid_exception, exception=True, traceback=formatted_traceback)
|
||||
break
|
||||
except Exception, ex:
|
||||
#terminate this channel if we can't properly send back the error
|
||||
@@ -85,7 +85,17 @@ class TinySocketClientThread(threading.Thread):
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
|
||||
def netrpc_handle_exception_legacy(e):
|
||||
if isinstance(e, openerp.osv.osv.except_osv):
|
||||
return 'warning -- ' + e.name + '\n\n' + e.value
|
||||
if isinstance(e, openerp.exceptions.Warning):
|
||||
return 'warning -- Warning\n\n' + str(e)
|
||||
if isinstance(e, openerp.exceptions.AccessError):
|
||||
return 'warning -- AccessError\n\n' + str(e)
|
||||
if isinstance(e, openerp.exceptions.AccessDenied):
|
||||
return 'AccessDenied ' + str(e)
|
||||
return openerp.tools.exception_to_unicode(e)
|
||||
|
||||
class TinySocketServerThread(threading.Thread,netsvc.Server):
|
||||
def __init__(self, interface, port, secure=False):
|
||||
|
||||
@@ -31,6 +31,7 @@ from amount_to_text_en import *
|
||||
from pdf_utils import *
|
||||
from yaml_import import *
|
||||
from sql import *
|
||||
from float_utils import *
|
||||
|
||||
#.apidoc title: Tools
|
||||
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2011 OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import math
|
||||
|
||||
def _float_check_precision(precision_digits=None, precision_rounding=None):
|
||||
assert (precision_digits is not None or precision_rounding is not None) and \
|
||||
not (precision_digits and precision_rounding),\
|
||||
"exactly one of precision_digits and precision_rounding must be specified"
|
||||
if precision_digits is not None:
|
||||
return 10 ** -precision_digits
|
||||
return precision_rounding
|
||||
|
||||
def float_round(value, precision_digits=None, precision_rounding=None):
|
||||
"""Return ``value`` rounded to ``precision_digits``
|
||||
decimal digits, minimizing IEEE-754 floating point representation
|
||||
errors, and applying HALF-UP (away from zero) tie-breaking rule.
|
||||
Precision must be given by ``precision_digits`` or ``precision_rounding``,
|
||||
not both!
|
||||
|
||||
:param float value: the value to round
|
||||
:param int precision_digits: number of fractional digits to round to.
|
||||
:param float precision_rounding: decimal number representing the minimum
|
||||
non-zero value at the desired precision (for example, 0.01 for a
|
||||
2-digit precision).
|
||||
:return: rounded float
|
||||
"""
|
||||
rounding_factor = _float_check_precision(precision_digits=precision_digits,
|
||||
precision_rounding=precision_rounding)
|
||||
if rounding_factor == 0 or value == 0: return 0.0
|
||||
|
||||
# NORMALIZE - ROUND - DENORMALIZE
|
||||
# In order to easily support rounding to arbitrary 'steps' (e.g. coin values),
|
||||
# we normalize the value before rounding it as an integer, and de-normalize
|
||||
# after rounding: e.g. float_round(1.3, precision_rounding=.5) == 1.5
|
||||
|
||||
# TIE-BREAKING: HALF-UP
|
||||
# We want to apply HALF-UP tie-breaking rules, i.e. 0.5 rounds away from 0.
|
||||
# Due to IEE754 float/double representation limits, the approximation of the
|
||||
# real value may be slightly below the tie limit, resulting in an error of
|
||||
# 1 unit in the last place (ulp) after rounding.
|
||||
# For example 2.675 == 2.6749999999999998.
|
||||
# To correct this, we add a very small epsilon value, scaled to the
|
||||
# the order of magnitude of the value, to tip the tie-break in the right
|
||||
# direction.
|
||||
# Credit: discussion with OpenERP community members on bug 882036
|
||||
|
||||
normalized_value = value / rounding_factor # normalize
|
||||
epsilon_magnitude = math.log(abs(normalized_value), 2)
|
||||
epsilon = 2**(epsilon_magnitude-53)
|
||||
normalized_value += cmp(normalized_value,0) * epsilon
|
||||
rounded_value = round(normalized_value) # round to integer
|
||||
result = rounded_value * rounding_factor # de-normalize
|
||||
return result
|
||||
|
||||
def float_is_zero(value, precision_digits=None, precision_rounding=None):
|
||||
"""Returns true if ``value`` is small enough to be treated as
|
||||
zero at the given precision (smaller than the corresponding *epsilon*).
|
||||
The precision (``10**-precision_digits`` or ``precision_rounding``)
|
||||
is used as the zero *epsilon*: values less than that are considered
|
||||
to be zero.
|
||||
Precision must be given by ``precision_digits`` or ``precision_rounding``,
|
||||
not both!
|
||||
|
||||
Warning: ``float_is_zero(value1-value2)`` is not equivalent to
|
||||
``float_compare(value1,value2) == 0``, as the former will round after
|
||||
computing the difference, while the latter will round before, giving
|
||||
different results for e.g. 0.006 and 0.002 at 2 digits precision.
|
||||
|
||||
:param int precision_digits: number of fractional digits to round to.
|
||||
:param float precision_rounding: decimal number representing the minimum
|
||||
non-zero value at the desired precision (for example, 0.01 for a
|
||||
2-digit precision).
|
||||
:param float value: value to compare with the precision's zero
|
||||
:return: True if ``value`` is considered zero
|
||||
"""
|
||||
epsilon = _float_check_precision(precision_digits=precision_digits,
|
||||
precision_rounding=precision_rounding)
|
||||
return abs(float_round(value, precision_rounding=epsilon)) < epsilon
|
||||
|
||||
def float_compare(value1, value2, precision_digits=None, precision_rounding=None):
|
||||
"""Compare ``value1`` and ``value2`` after rounding them according to the
|
||||
given precision. A value is considered lower/greater than another value
|
||||
if their rounded value is different. This is not the same as having a
|
||||
non-zero difference!
|
||||
Precision must be given by ``precision_digits`` or ``precision_rounding``,
|
||||
not both!
|
||||
|
||||
Example: 1.432 and 1.431 are equal at 2 digits precision,
|
||||
so this method would return 0
|
||||
However 0.006 and 0.002 are considered different (this method returns 1)
|
||||
because they respectively round to 0.01 and 0.0, even though
|
||||
0.006-0.002 = 0.004 which would be considered zero at 2 digits precision.
|
||||
|
||||
Warning: ``float_is_zero(value1-value2)`` is not equivalent to
|
||||
``float_compare(value1,value2) == 0``, as the former will round after
|
||||
computing the difference, while the latter will round before, giving
|
||||
different results for e.g. 0.006 and 0.002 at 2 digits precision.
|
||||
|
||||
:param int precision_digits: number of fractional digits to round to.
|
||||
:param float precision_rounding: decimal number representing the minimum
|
||||
non-zero value at the desired precision (for example, 0.01 for a
|
||||
2-digit precision).
|
||||
:param float value1: first value to compare
|
||||
:param float value2: second value to compare
|
||||
:return: (resp.) -1, 0 or 1, if ``value1`` is (resp.) lower than,
|
||||
equal to, or greater than ``value2``, at the given precision.
|
||||
"""
|
||||
rounding_factor = _float_check_precision(precision_digits=precision_digits,
|
||||
precision_rounding=precision_rounding)
|
||||
value1 = float_round(value1, precision_rounding=rounding_factor)
|
||||
value2 = float_round(value2, precision_rounding=rounding_factor)
|
||||
delta = value1 - value2
|
||||
if float_is_zero(delta, precision_rounding=rounding_factor): return 0
|
||||
return -1 if delta < 0.0 else 1
|
||||
|
||||
def float_repr(value, precision_digits):
|
||||
"""Returns a string representation of a float with the
|
||||
the given number of fractional digits. This should not be
|
||||
used to perform a rounding operation (this is done via
|
||||
:meth:`~.float_round`), but only to produce a suitable
|
||||
string representation for a float.
|
||||
|
||||
:param int precision_digits: number of fractional digits to
|
||||
include in the output
|
||||
"""
|
||||
# Can't use str() here because it seems to have an intrisic
|
||||
# rounding to 12 significant digits, which causes a loss of
|
||||
# precision. e.g. str(123456789.1234) == str(123456789.123)!!
|
||||
return ("%%.%sf" % precision_digits) % value
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
import time
|
||||
start = time.time()
|
||||
count = 0
|
||||
errors = 0
|
||||
|
||||
def try_round(amount, expected, precision_digits=3):
|
||||
global count, errors; count += 1
|
||||
result = float_repr(float_round(amount, precision_digits=precision_digits),
|
||||
precision_digits=precision_digits)
|
||||
if result != expected:
|
||||
errors += 1
|
||||
print '###!!! Rounding error: got %s , expected %s' % (result, expected)
|
||||
|
||||
# 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]
|
||||
for magnitude in range(7):
|
||||
for i in xrange(len(fractions)):
|
||||
frac, exp, prec = fractions[i], expecteds[i], precisions[i]
|
||||
for sign in [-1,1]:
|
||||
for x in xrange(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, precision_digits=prec)
|
||||
|
||||
stop = time.time()
|
||||
|
||||
# Micro-bench results:
|
||||
# 47130 round calls in 0.422306060791 secs, with Python 2.6.7 on Core i3 x64
|
||||
# with decimal:
|
||||
# 47130 round calls in 6.612248100021 secs, with Python 2.6.7 on Core i3 x64
|
||||
print count, " round calls, ", errors, "errors, done in ", (stop-start), 'secs'
|
||||
@@ -1200,4 +1200,4 @@ class UnquoteEvalContext(defaultdict):
|
||||
def __missing__(self, key):
|
||||
return unquote(key)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
@@ -177,20 +177,12 @@ Section OpenERP_Server SectionOpenERP_Server
|
||||
!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
|
||||
;Create shortcuts
|
||||
CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER"
|
||||
; CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\OpenERP Server.lnk" "$INSTDIR\openerp-server.exe"
|
||||
; CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Start service.lnk" "$INSTDIR\service\start.bat"
|
||||
; CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Stop service.lnk" "$INSTDIR\service\stop.bat"
|
||||
; CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Edit config.lnk" "notepad.exe" "$INSTDIR\openerp-server.conf"
|
||||
; CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\View log.lnk" "notepad.exe" "$INSTDIR\openerp-server.log"
|
||||
; CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\OpenERP.lnk" "start" "http://127.0.0.1:8069/"
|
||||
; CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\uninstall.exe"
|
||||
; !insertmacro CreateInternetShortcut "$SMPROGRAMS\$STARTMENU_FOLDER\Forum" "http://www.openerp.com/forum"
|
||||
; !insertmacro CreateInternetShortcut "$SMPROGRAMS\$STARTMENU_FOLDER\Translation" "https://translations.launchpad.net/openobject"
|
||||
!insertmacro CreateInternetShortcut "$SMPROGRAMS\$STARTMENU_FOLDER\OpenERP" "http://localhost:8069/"
|
||||
!insertmacro MUI_STARTMENU_WRITE_END
|
||||
|
||||
|
||||
FileOpen $9 '$INSTDIR\install.log' w
|
||||
FileWrite $9 "INSTDIR $INSTDIR$\r$\n"
|
||||
FileWrite $9 "Hostname $TextPostgreSQLHostname$\r$\n"
|
||||
FileWrite $9 "Port $TextPostgreSQLPort$\r$\n"
|
||||
FileWrite $9 "Username $TextPostgreSQLUsername$\r$\n"
|
||||
@@ -214,7 +206,7 @@ Section -RestartServer
|
||||
SectionEnd
|
||||
|
||||
Section -Post
|
||||
WriteRegExpandStr HKLM "${UNINSTALL_REGISTRY_KEY}" "UninstallString" "$INSTDIR\Uninstall.exe"
|
||||
WriteRegExpandStr HKLM "${UNINSTALL_REGISTRY_KEY}" "UninstallString" "$INSTDIR\server\Uninstall.exe"
|
||||
WriteRegExpandStr HKLM "${UNINSTALL_REGISTRY_KEY}" "InstallLocation" "$INSTDIR"
|
||||
WriteRegStr HKLM "${UNINSTALL_REGISTRY_KEY}" "DisplayName" "${DISPLAY_NAME}"
|
||||
WriteRegStr HKLM "${UNINSTALL_REGISTRY_KEY}" "DisplayVersion" "${VERSION}"
|
||||
@@ -228,7 +220,7 @@ Section -Post
|
||||
WriteRegStr HKLM "${UNINSTALL_REGISTRY_KEY}" "Contact" "sales@openerp.com"
|
||||
WriteRegDWORD HKLM "${UNINSTALL_REGISTRY_KEY}" "NoModify" "1"
|
||||
WriteRegDWORD HKLM "${UNINSTALL_REGISTRY_KEY}" "NoRepair" "1"
|
||||
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
||||
WriteUninstaller "$INSTDIR\server\Uninstall.exe"
|
||||
SectionEnd
|
||||
|
||||
Section "Uninstall"
|
||||
@@ -240,19 +232,12 @@ Section "Uninstall"
|
||||
nsExec::Exec '"$INSTDIR\service\OpenERPServerService.exe" -remove'
|
||||
sleep 2
|
||||
|
||||
Rmdir /r "$INSTDIR"
|
||||
Rmdir /r "$INSTDIR\service"
|
||||
Rmdir /r "$INSTDIR\server"
|
||||
|
||||
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
|
||||
|
||||
Delete "$SMPROGRAMS\$MUI_TEMP\Forum.url"
|
||||
Delete "$SMPROGRAMS\$MUI_TEMP\Translation.url"
|
||||
Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
|
||||
Delete "$SMPROGRAMS\$MUI_TEMP\OpenERP Server.lnk"
|
||||
Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
|
||||
Delete "$SMPROGRAMS\$MUI_TEMP\Start service.lnk"
|
||||
Delete "$SMPROGRAMS\$MUI_TEMP\Stop service.lnk"
|
||||
Delete "$SMPROGRAMS\$MUI_TEMP\Edit config.lnk"
|
||||
Delete "$SMPROGRAMS\$MUI_TEMP\View log.lnk"
|
||||
Delete "$SMPROGRAMS\$MUI_TEMP\OpenERP.url"
|
||||
;
|
||||
;Delete empty start menu parent diretories
|
||||
StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP"
|
||||
|
||||
Reference in New Issue
Block a user