Project

Profile

Help

Support #6408

closed

decimalDivide uses HALF_DOWN instead of HALF_UP rounding

Added by Svante Schubert 3 months ago. Updated 2 months ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Category:
XPath conformance
Sprint/Milestone:
-
Start date:
2024-04-29
Due date:
% Done:

0%

Estimated time:
Legacy ID:
Applies to branch:
12
Fix Committed on Branch:
Fixed in Maintenance Release:
Platforms:
Java

Description

In class net.sf.saxon.expr.Calculator.java

The function public static BigDecimalValue decimalDivide(NumericValue a, NumericValue b) throws XPathException

is using HALF_DOWN instead of HALF_UP rounding, which would be the default of XPath 3.1 round() see https://www.w3.org/TR/xpath-functions-31/#numeric-value-functions

Seen in 12.4 release


Files

rounding-messages.xsl (5.53 KB) rounding-messages.xsl stand-alone rounding test messages (free to be used as any license you like to) Svante Schubert, 2024-05-02 10:26
Actions #1

Updated by Michael Kay 3 months ago

I don't think there's any non-conformance here: the specification is explicit that this is implementation-defined. See F+O §4.2:

...for division and modulus operations, the returned result should be accurate to at least M digits of precision. The actual precision is ·implementation-defined·. If the number of digits in the mathematical result exceeds the number of digits that the implementation retains for that operation, the result is truncated or rounded in an ·implementation-defined· manner.

So that leaves the question whether we have made the best choice here, and if not, whether it is better to change it or to leave it alone in the interests of stability. I'm inclined towards the stability argument: if the result is implementation-defined then people shouldn't be writing code that depends on the outcome, but if they do write code that depends on the outcome, we shouldn't change it without a strong reason.

Actions #2

Updated by Michael Kay 3 months ago

  • Status changed from New to In Progress
Actions #3

Updated by Svante Schubert 2 months ago

An interesting point is that there seems to be a semantic gap for the term "half-up" rounding that I was not aware of earlier:

When I look into software documentation like Java (or Python), here is JavaDoc: https://docs.oracle.com/javase/8/docs/api/java/math/RoundingMode.html Or in the German Wiki for rounding: https://de.wikipedia.org/wiki/Rundung#Kaufm%C3%A4nnisches_Runden The rounding half-away-from-zero is meant.

But when looking into IEEE 754 (see https://courses.physics.illinois.edu/cs357/sp2020/notes/ref-5-rounding.html) and XPath 3.1 https://www.w3.org/TR/xpath-functions-31/#func-round the rounding half-to-positive-infinity

In Germany, I learned in school the half-away-from-zero rounding as the default. Most of all, it is mandated for VAT calculation by German law. Therefore we used it as a recommendation for the default rounding for electronic invoices within Europe in CEN TC 434 for the EN16931 norm.

Perhaps in different countries there towards positive infinity rounding is learned in school and is required for VAT calculation, but I am puzzled that XPath has chosen it as default - perhaps it was a misunderstanding towards using "half-up" ;-)

One last question. if Saxon 12.4 calculated round(-9552.255, -2) shouldn't be the result -9500? xsl:messageround(-9552.255, -2) should be -9500 and is <xsl:value-of select="round(-9552.255, -2)"/>!</xsl:message>

Actions #4

Updated by Michael Kay 2 months ago

XPath 1.0, despite its brevity, is very careful to say that round() does round-half-towards-positive-infinity, so I suspect James Clark was fully aware that "round up" was ambiguous. Why they made that choice, I don't know. I'm afraid I know very little about accounting standards in different countries.

Of course it's easy to achieve round-half-away-from-zero with a bit of conditional logic testing if the number is negative.

One last question. if Saxon 12.4 calculated round(-9552.255, -2) shouldn't be the result -9500?

-9600 seems right to me.

Actions #5

Updated by Svante Schubert 2 months ago

Thanks, Michael, yes, I realized now that -9600 is correct.

I assume there is no round-half-away-from-zero method in XPatch or Saxon to fulfil the German VAT rounding requirements by law. Added the function and made it the default of round(). In addition, tried to avoid binary floating points as much as possible and be as precise as possible therefore I enhanced the precision from 18 to 34.

Moved all changes (via git interactive) into a single patch to make it easier to review: https://github.com/svanteschubert/Saxon-HE-enhanced-accuracy/commit/64aef1958a2475485ad4229fefebbe3850aef234

I would call it the Saxon extreme-accuracy mod ;-) You might consider enabling all the above by configuration - but I am not aware if there is a market for that. At least I hereby donate every change - the complete above repo - back to Saxonica if this might help you in any kind. :-)

Have a nice weekend, Michael - TGIF

PS: My favourite GitHub feature is, to be able to point at a Code line and provide the URL. It is yet not possible to use with: https://github.com/Saxonica/Saxon-HE/blob/main/12/source/saxon12-4source.zip ;-) I have found the tests neither. ;-)

Actions #6

Updated by Michael Kay 2 months ago

Thanks. I'm afraid this is the kind of area where implementing the core feature - a different rounding mode - is trivial, but designing the APIs or configuration facilities to enable users to exploit the feature is a much bigger project. I will see whether there is any appetite in the qt4cg to add support for rounding modes, either on round() itself, or on decimal divide.

Actions #7

Updated by Michael Kay 2 months ago

  • Tracker changed from Bug to Support
  • Status changed from In Progress to Closed

Closing this; the code is working as designed. I have raised an issue on the QT4 list to see whether we want to improve the specs in this area.

Please register to edit this issue

Also available in: Atom PDF