Decimal string not showing trailing zeros
Added by Anonymous about 16 years ago
Legacy ID: #5357983 Legacy Poster: Thais (thaisf)
I have created an XSLT 2 UDF to set the precision of prices and line costs. It either pads or rounds the decimal part, if necessary. The padding mechanism is the one occasionally failing, yet it is using XSLT's concat() to add the trailing zeros, which should be returning a string. This never fails in Stylus Studio, which is set to use the Saxon 9.0.0.2 processor. But it fails in random situations on the web server, which is using Saxon-B 9.1.0.1. This leads me to think there might be a bug in version 9.1 B or maybe something changed and I need to explicitly do some type casting. These are the UDFs I'm talking about: <!-- precision --> <xsl:function name="format:precision"> <xsl:param name="value"/> <xsl:param name="decimalPrecision"/> <xsl:variable name="decimalPart" select="substring-after(string($value), '.')"/> <xsl:variable name="decimalPartLength" select="string-length($decimalPart)"/> <xsl:choose> <xsl:when test="$decimalPartLength = 0"> <xsl:value-of select="concat($value, '.', format:padding('0', $decimalPrecision))"/> </xsl:when> <xsl:when test="$decimalPartLength lt $decimalPrecision"> <xsl:value-of select="concat($value, format:padding('0', $decimalPrecision - $decimalPartLength))"/> </xsl:when> <xsl:when test="$decimalPartLength = $decimalPrecision"> <xsl:value-of select="$value"/> </xsl:when> <xsl:otherwise> <xsl:variable name="factor" select="math:naturalExponentiation(10, $decimalPrecision)"/> <xsl:value-of select="round($value * $factor) div $factor"/> </xsl:otherwise> </xsl:choose> </xsl:function> <!-- padding --> <xsl:function name="format:padding"> <xsl:param name="character"/> <xsl:param name="times"/> <xsl:choose> <xsl:when test="$times lt 0"/> <xsl:when test="$times = 0"> <xsl:value-of select="''"/> </xsl:when> <xsl:when test="$times = 1"> <xsl:value-of select="$character"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="concat($character, format:padding($character, $times - 1))"/> </xsl:otherwise> </xsl:choose> </xsl:function> Any help would be appreciated! Thais
Replies (10)
Please register to reply
RE: Decimal string not showing trailing zeros - Added by Anonymous about 16 years ago
Legacy ID: #5361863 Legacy Poster: Michael Kay (mhkay)
At the very least I will need to know what you mean by "it fails": what precisely are the symptoms of failure? If the failure looks like a Saxon problem (e.g. an exception thrown within the Saxon code) then I will also need a repro test so that I can investigate it. I would suggest very strongly that when you write user-defined functions, you should declare the types of the parameters and the type of the result. Apart from anything else, it makes the code far easier for a reader (like me) to understand. I would also suggest that you use xsl:sequence rather than xsl:value-of to return function results. Using xsl:value-of constructs a text node, which the caller of the function in all probability atomizes to create an untypedAtomic value, which will in turn be converted back to a number or string. Your format:precision function is particularly confusing because it sometimes returns a string wrapped in a text node, and sometimes a number wrapped in a text node. Finally, your format:padding function seems to be doing nothing more than string-join(for $i in 1 to $times return $character, '')
RE: Decimal string not showing trailing zeros - Added by Anonymous about 16 years ago
Legacy ID: #5364084 Legacy Poster: Thais (thaisf)
The precise symptom of failure is the thread subject: a decimal number, of type string (generated by the concat function), being output without its decimal trailing zeros. It is not a thrown exception. Alright, as this is not a discussion about the benefits of static vs. dynamic typing, I have explicitly typed my parameters, variables and functions for you. Thanks for the suggestion, I didn't know xsl:sequence was more efficient than xsl:value-of. My format:precision function either returns the value itself, or the value padded with zeros, or the rounded value with the desired precision. I thought XSLT was a declarative functional language, I didn't know it supported simple looping over a given number of iterations, that's why I implemented it using recursion. I also made my math:naturalExponentiation function recursive. I have not seen this "for..in..to" expression that you suggested described anywhere in the W3C recommendation for XPath 2 (http://www.w3.org/TR/xpath20/#id-for-expressions). As you're one of the editors (really cool), why is it not there? Is it something special that Saxon does or is it just missing from the specification? Alright, after all of these suggestions (updated code below), it is now failing to display the decimal trailing zeros in Stylus Studio as well, which was always working fine before, as I said in my previous post. It seems to only happen with certain numbers, e.g.: - decimal value: 210 * 30.1590 - desired precision 4 which outputs 6333.39, but if I change the 2nd factor to 30.1580, I get the trailing zeros: 6333.1800 <!-- precision Sets the precision of the given value to the given number of decimal places. --> <xsl:function name="format:precision" as="xs:string"> <xsl:param name="value" as="xs:double"/> <xsl:param name="decimalPrecision" as="xs:integer"/> <xsl:variable name="decimalPart" as="xs:string" select="substring-after(string($value), '.')"/> <xsl:variable name="decimalPartLength" as="xs:integer" select="string-length($decimalPart)"/> <xsl:choose> <xsl:when test="$decimalPartLength = 0"> <xsl:sequence select="concat($value, '.', format:padding('0', $decimalPrecision))"/> </xsl:when> <xsl:when test="$decimalPartLength lt $decimalPrecision"> <xsl:sequence select="concat($value, format:padding('0', $decimalPrecision - $decimalPartLength))"/> </xsl:when> <xsl:when test="$decimalPartLength = $decimalPrecision"> <xsl:sequence select="string($value)"/> </xsl:when> <xsl:otherwise> <xsl:variable name="factor" select="math:naturalExponentiation(10, $decimalPrecision)"/> <xsl:sequence select="string(round($value * $factor) div $factor)"/> </xsl:otherwise> </xsl:choose> </xsl:function> <!-- padding Returns a string with repeated occurrences of the given character. The number of times must be a natural number. --> <xsl:function name="format:padding" as="xs:string"> <xsl:param name="character" as="xs:string"/> <xsl:param name="times" as="xs:integer"/> <xsl:sequence select="string-join(for $i in 1 to $times return $character, '')"/> </xsl:function>
RE: Decimal string not showing trailing zeros - Added by Anonymous about 16 years ago
Legacy ID: #5364241 Legacy Poster: Michael Kay (mhkay)
Sorry if you didn't find my comments on your code helpful, but you're asking me to debug your code, so I thought I might suggest a few ways in which you could make my job easier. the construct string-join(for $i in 1 to $times return $character, '') doesn't involve any Saxon extensions. It's just a coding idiom that uses a combination of standard expressions/operators. (1 to $n) returns a sequence of integers between 1 and $n, (for $i in X return $c) returns one instance of $c for each item in X, and string-join() takes a sequence of strings and joins them into one big string. There's nothing wrong with your recursive code, I just thought you would appreciate a suggestion on how to make it more concise. Now that you've clarified that the expected input is a double, rather than a decimal, it's clear what's going on. The literal 30.1590 is a decimal, and it's exactly the same as the decimal 30.159. Multiplying it by 210 gives another decimal. Passing this decimal to a function that expects a double gives the nearest double to this decimal value. In general a decimal fraction is not representable exactly by a double, so this may involve a rounding error. Now you convert the double to a string, and you're interested in how many decimal places there are after the decimal point. This is going to depend entirely on the rounding that was applied. Also of course there might be no decimal point. And if the number is outside the range 1e-6 to 1e+6, the stuff after the decimal point will include an exponent such as e-8 or e10. So, for starters, I think you would be much better off using decimal arithmetic rather than double: that is, declare value "as xs:decimal". But even then, I think you would be much better off using the format-number() function rather than trying to do this yourself. When you weren't declaring the type of the parameter, the actual value would be either a decimal or a double depending on the precise nature of the expression that was used to supply the argument. This might explain why the results were so unpredictable. It's clear now that this is an XSLT coding problem rather than something specific to Saxon, so I think you might be best off asking the questions on the xsl-list at mulberrytech.com. This forum isn't intended as a place to get programming advice.
RE: Decimal string not showing trailing zeros - Added by Anonymous about 16 years ago
Legacy ID: #5383034 Legacy Poster: Thais (thaisf)
What do you mean I didn't find your comments helpful? Yes I did, and I implemented them and posted the updated code. The only thing I can think of that you could have not liked from what I said is that I didn't want to discuss static vs. dynamic typing, which I don't see what's wrong with that. It is a valid discussion. I am sorry if I sounded ungrateful. I did thank you for the suggestion. I am not asking you to debug my code or for programming advice, although that's always welcome, I pasted it because that's obviously standard forum procedure so people can understand what's going on. Thanks for clarifying about the "1 to $n" construct used with the for expression, that's what I hadn't seen before. That almost means that XSLT is not purely functional - but that's another issue. I have found the problem. Multiplying 30.159 by 210 was not returning 6333.39 as expected, it was returning several decimal places, even with all of the involved numeric types being xs:decimal. So the padding part of my code was not getting executed at all, but the rounding part, which was then returning the appropriate value 6333.39, without the padding. So all I had to do was invoke my precision function again before returning from the rounding procedure. I had tried format-number() before writing this, but it was not padding the decimal numbers with trailing zeros. I might have done something wrong. I'll take a look at it again. No matter what, thank you for all the hints.
RE: Decimal string not showing trailing zeros - Added by Anonymous about 16 years ago
Legacy ID: #5383078 Legacy Poster: Michael Kay (mhkay)
Academic debating point, I know, but I don't agree with you on: >That almost means that XSLT is not purely functional - but that's another issue. Clearly there is surface syntax in XSLT/XPath that sometimes disguises the functional nature of the language, but I think you'll find it's superficial, and that the underlying semantics are purely functional. * The expression (A to B) could be written as a pure function range(A, B) that returns a sequence of integers between A to B inclusive. * The expression (for $a in X return $a3) is just alternative syntax for a higher-order apply() or map() function: apply(X, {f($a): $a3}) So the combination (for $i in 1 to 10 return '.') is just syntactic sugar for something like apply(range(1,10), {f($x): '.'}) Glad you sorted your numeric precision issues. If you have trouble with format-number(), ask on xsl-list. Michael Kay http://www.saxonica.com/
RE: Decimal string not showing trailing zeros - Added by Anonymous about 16 years ago
Legacy ID: #5383123 Legacy Poster: Thais (thaisf)
True. I just asked myself, then why even try to disguise, when the functional paradigm is so beautiful and this might be a chance to finally make it popular, through XML and the Web standards? But on the other hand, it might backfire and actually scare people away. So this might be a way to start getting programmers' mentality somewhat used to functional languages.
RE: Decimal string not showing trailing zeros - Added by Anonymous about 16 years ago
Legacy ID: #5383207 Legacy Poster: Michael Kay (mhkay)
>I just asked myself, then why even try to disguise Part of the answer to that is DSSSL - a precursor to XSLT that used syntax based on Scheme. It never took off, and one of the conventional explanations of the reason was that the syntax was impenetrable to the target audience. Though the "for" expression probably owes more to the XQuery influence with its background in SQL and other query languages whose syntax was based more on predicate calculus.
RE: Decimal string not showing trailing zeros - Added by Anonymous about 16 years ago
Legacy ID: #5383516 Legacy Poster: Thais (thaisf)
I have been very excited about XML transformations with XSLT, XQuery and XPath because they have allowed me to restore some, although remote, contact with the lambda calculus paradigm I fell in love with during college, and which I sadly thought I would never even see again once I entered the business world and got stuck with Turing machine derived thinking. However, I confess that I fell even more in love with Prolog than Haskell, but I have not yet identified any vestige of predicate logic among these languages. Do you happen to know of any XML related language that falls more into the logic programming paradigm? Sorry if I am abusing the extent of this thread.
RE: Decimal string not showing trailing zeros - Added by Anonymous about 16 years ago
Legacy ID: #5383578 Legacy Poster: Michael Kay (mhkay)
This isn't a good place for that kind of discussion, because not many people monitor this forum. Open a thread on xsl-list at mulberrytech.com
RE: Decimal string not showing trailing zeros - Added by Anonymous about 16 years ago
Legacy ID: #5383831 Legacy Poster: Thais (thaisf)
Ok, I'll probably do that later from home. Thank you.
Please register to reply