Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

l3fp: How to get a zero filling rounding result? #1226

Open
muzimuzhi opened this issue Jun 8, 2023 · 32 comments
Open

l3fp: How to get a zero filling rounding result? #1226

muzimuzhi opened this issue Jun 8, 2023 · 32 comments
Labels
enhancement New feature or request l3fp

Comments

@muzimuzhi
Copy link
Contributor

muzimuzhi commented Jun 8, 2023

Currently trailing zeros are always trimmed. Functions round, floor, ceil, and trunc have the same behavior.

(#1186 was caused by this)

\documentclass{article}
\begin{document}
\ExplSyntaxOn
\fp_eval:n {round(1.001, 0)}, % 1,      expected
\fp_eval:n {round(1.001, 1)}, % 1,      how to get 1.0
\fp_eval:n {round(1.001, 2)}, % 1,      how to get 1.00
\fp_eval:n {round(1.001, 3)}, % 1.001,  expected
\fp_eval:n {round(1.001, 4)}, % 1.001,  how to get 1.0010
\ExplSyntaxOff
\end{document}
@Skillmon
Copy link
Contributor

Skillmon commented Jun 8, 2023

\ExplSyntaxOn
\cs_new:Npn \my_fp_round:nn #1#2
  {
    \int_compare:nNnTF {#2} = \c_zero_int
      { \fp_eval:n { round(#1, 0) } }
      { \exp_args:Ne \__my_fp_round:nn { \fp_eval:n { round(#1, #2) } } {#2} }
  }
\cs_new:Npn \__my_fp_round_decimal:w #1 . #2 \q_stop #3 { #3 {#2} }
\cs_new:Npn \__my_fp_round:nn #1
  {
    #1
    \__my_fp_round_decimal:w #1 \q_stop \__my_fp_round_has_decimal:w
      . \q_stop \__my_fp_round_no_decimal:nn
  }
\cs_new:Npn \__my_fp_round_has_decimal:w
    #1 .\q_stop \__my_fp_round_no_decimal:nn #2
  { \prg_replicate:nn { #2 - \str_count_ignore_spaces:n {#1} } { 0 } }
\cs_new:Npn \__my_fp_round_no_decimal:nn #1#2
  { . \prg_replicate:nn {#2} { 0 } }

\tl_show:x { \my_fp_round:nn { 1.001 } { 0 } }
\tl_show:x { \my_fp_round:nn { 1.001 } { 1 } }
\tl_show:x { \my_fp_round:nn { 1.001 } { 2 } }
\tl_show:x { \my_fp_round:nn { 1.001 } { 3 } }
\tl_show:x { \my_fp_round:nn { 1.001 } { 4 } }
\ExplSyntaxOff

@muzimuzhi
Copy link
Contributor Author

The trailing zeros are trimmed by \__fp_trim_zeros:w at the final step.

Doc for \fp_eval:n reads

Non-significant trailing zeros are trimmed, and integers are expressed without a decimal separator.

What's the meaning of "non-significant trailing zeros" here?

@josephwright
Copy link
Member

What's the meaning of "non-significant trailing zeros" here?

Zeros coming after the last non-zero digit, so 1.010 becomes 1.01 for example.

@josephwright
Copy link
Member

Do we need any action here? Zero filling is a 'display' thing, I suspect.

@josephwright
Copy link
Member

Alternatively, @muzimuzhi fancy writing a PR? ;)

@davidcarlisle
Copy link
Member

Do we need any action here? Zero filling is a 'display' thing, I suspect.

might be nice to add. While it is mostly about display, given \fp_eval:n/\fpeval is in the format, loading a full number formatter such as siunitx is a lot of overhead if you want half of £1 to be £0.50 not £0.5

@josephwright
Copy link
Member

Do we need any action here? Zero filling is a 'display' thing, I suspect.

might be nice to add. While it is mostly about display, given \fp_eval:n/\fpeval is in the format, loading a full number formatter such as siunitx is a lot of overhead if you want half of £1 to be £0.50 not £0.5

Sure: I guess in many ways the question is 'what function names do we want inside the expression' (and here I really do mean function!). I guess zerofill(<val>,<n>) would work?

@josephwright
Copy link
Member

Or pad()?

@u-fischer
Copy link
Member

pad sounds good. It could then even allow to pad with something else then zero, and it could work on both sides.

@car222222
Copy link
Contributor

What exactly would <val>,<n> be?
More generally: What would be the arguments of pad (both ends) be?

@josephwright
Copy link
Member

What exactly would <val>,<n> be?

<val> is 'a value to be padded': as we are dealing with real functions, that is of course 'an expression which produces a value'. <n> is 'the number of decimal places which should be produced if filling is required', i.e. no rounding/trimming.

@muzimuzhi
Copy link
Contributor Author

It is possible (too late?) to change the behavior of all four fp rounding functions so that if one of them is used as the outer-most function in an fp expression, \fp_eval:n and similar latex3 functions would express the result in a possibly zero filling decimal? If so, then is introducing four new fp rounding functions acceptable?

For the 'display' purpose, there're full bunch of format string syntax conventions [1] that can be implemented as for example (in an ideal world) \fp_format:nn {<fp expr>} {<fp format>} and even \tl_format:nn {<tokens containing format specifiers>} {<var1>, <var2>, ...}.

[1] In C language https://en.cppreference.com/w/c/io/fprintf and in Java https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/Formatter.html#syntax. Java also has a java.text.DecimalFormat class.

@PhelypeOleinik
Copy link
Member

I have an almost finished expl3 implementation of C's printf, which includes the regular printf interface, plus formatting functions for individual numbers like \printf_format:nn { 06.3f } { pi }. I was planning to publish it as a package before the year 3019, but if it would be useful, it could go into the kernel.

@josephwright
Copy link
Member

@PhelypeOleinik Sounds good but I think padding will still be requested 'directly' in l3fp.

@josephwright
Copy link
Member

@muzimuzhi I don't like that at all: rounding and filling zeros are entirely separate concepts (I split them in siunitx v3 for exactly that reason).

@Skillmon
Copy link
Contributor

Skillmon commented Jun 9, 2023

How about extending the round-functions to take an optional third argument which if it evaluates to true means don't drop non-significant trailing zeros?

@muzimuzhi
Copy link
Contributor Author

@muzimuzhi I don't like that at all: rounding and filling zeros are entirely separate concepts (I split them in siunitx v3 for exactly that reason).

@josephwright You're right. When typing my previous comment I had semantic of python's built-in round(number, ndigits=None) in mind. Turns out python's round is a combination of rounding and displaying.

@car222222
Copy link
Contributor

“rounding and filling zeros are entirely separate” 
This is not entirely true! But nor are they entirely related . . .
both have (mathematical) connections to “fixed precision”:
but these connections concern only whether zero-padding
is “correct”, i.e., when should it be done in order to display
important information concerning the nature and derivation
of a numerical result.

Thus, for example, the use of zero-padding is not related only to the case of “no rounding/trimming”.

However, this issue has by now been reduced to the basic idea of “how to specify that padding should be applied in the typeset out”, rather than any such considerations of “mathematical or computational appropriateness/correctness”.

@car222222
Copy link
Contributor

The idea of zero-padding and when it is advisable/necessary are not, in all cases, "only a display thing”, but this current
issue of whether/how to provide it is so restricted.

Thus, if it is easy to provide this type of display then simply go ahead and do it!

@josephwright
Copy link
Member

“rounding and filling zeros are entirely separate”  This is not entirely true! But nor are they entirely related . . . both have (mathematical) connections to “fixed precision”: but these connections concern only whether zero-padding is “correct”, i.e., when should it be done in order to display important information concerning the nature and derivation of a numerical result.

Thus, for example, the use of zero-padding is not related only to the case of “no rounding/trimming”.

However, this issue has by now been reduced to the basic idea of “how to specify that padding should be applied in the typeset out”, rather than any such considerations of “mathematical or computational appropriateness/correctness”.

I mean that one shouldn't use rounding to force zero-filling. In siunitx I have a spec. for the minium number of places after round, which can then retain zeros, but that's different from padding a number. I am not sure how easily that can be expressed here.

@car222222
Copy link
Contributor

I mean that one shouldn't use rounding to force zero-filling.

Agreed.

In siunitx I have a spec. for the minium number of places after round, which can then retain zeros, but that's different from padding a number.

Probably. It is certainly different from @davidcarlisle's example of conventions such as presentation of (many) currencies using two digital places.

I am not sure how easily that can be expressed here.

My main point is: does this even need to be expressed here?
This issue is by now reduced to providing a command to do any required padding (for display only).

@blefloch
Copy link
Member

The internal representation of floating point numbers in l3fp does not include any notion of trailing zeros. So this padding can only occur at display time.

@josephwright
Copy link
Member

OK, based on @blefloch's point I'm minded to close here as I don't think we want a specific display function for this, and it therefore falls more in the area of a generic number formatter or passing to dedicated routines elsewhere - thoughts?

@car222222
Copy link
Contributor

car222222 commented Oct 18, 2023

I thought that @davidcarlisle was suggesting that we do in fact need to support padding.

What is not so clear is whether this should be provided for the display only.

Thus doing nothing in l3fp may be sensible, but there may still be a need to provide this simple formatting convention somewhere.

@davidcarlisle
Copy link
Member

@car222222 well yes but I don't think anyone doubts there is a requirement, just a case of where to put it. A light weight fp number formatter that sits in the format alongside fpeval would be a good thing, although it doesn't have to be in the fp module at the expl3 level (although that wouldn't be a bad place, I think)

@josephwright
Copy link
Member

OK, so perhaps a version of \fp_eval:n that takes a second argument specifying the minimum number of decimal places to print?

@josephwright
Copy link
Member

Based on @Skillmon's suggestion, that would perhaps be

\ExplSyntaxOn
\cs_new:Npn \fp_eval:nn #1#2
  {
    \int_compare:nNnTF {#2} = \c_zero_int
      { \fp_eval:n {#1} }
      { \exp_args:Ne \__fp_eval:nn { \fp_eval:n {#1} } {#2} }
  }
\cs_new:Npn \__fp_eval:nn #1
  {
    #1
    \__fp_eval_decimal:w #1 \q_stop \__fp_eval_has_decimal:w
      . \q_stop \__fp_eval_no_decimal:nn
  }
\cs_new:Npn \__fp_eval_decimal:w #1 . #2 \q_stop #3 { #3 {#2} }

\cs_new:Npn \__fp_eval_has_decimal:w
    #1 .\q_stop \__fp_eval_no_decimal:nn #2
  { \prg_replicate:nn { \int_max:nn {#2 - \str_count_ignore_spaces:n {#1} } { 0 } } { 0 } }
\cs_new:Npn \__fp_eval_no_decimal:nn #1#2
  { . \prg_replicate:nn {#2} { 0 } }

\tl_show:e { \fp_eval:nn { 1.001 } { 0 } }
\tl_show:e { \fp_eval:nn { 1.001 } { 1 } }
\tl_show:e { \fp_eval:nn { 1.001 } { 2 } }
\tl_show:e { \fp_eval:nn { 1.001 } { 3 } }
\tl_show:e { \fp_eval:nn { 1.001 } { 4 } }
\tl_show:e { \fp_eval:nn { 1.001 } { 5 } }
\ExplSyntaxOff

@davidcarlisle
Copy link
Member

davidcarlisle commented Oct 18, 2023

@josephwright possibly. it depends how light weight we want light weight formatting to be, rather than an integer another possibility would be to to take a token list similar to (or the same as) a subset of C printf %f or %g format string possibilities

https://www.tutorialspoint.com/c_standard_library/c_function_printf.htm

so f7.2 or e7.2 or ...

@josephwright
Copy link
Member

(I wonder if this is \fp_to_deicmal:nn rather than \fp_eval:nn)

@josephwright
Copy link
Member

@josephwright possibly. it depends how light weight we want light weight formatting to be, rather than an integer another possibility would be to to take a token list similar to (or the same as) a subset of C printf %f or %g format string possibilities

https://www.tutorialspoint.com/c_standard_library/c_function_printf.htm

so f7.2 or e7.2 or ...

Yes, but I suspect that really then is more of a generic printf module - I was aiming for something clearly in-scope for fp, which is really a 'calculation' module.

@FrankMittelbach
Copy link
Member


\cs_new:Npn \fp_eval:nn #1#2

Personally, I would find the code more readable if this is not just denoted by an extra argument, but by a command name change, e.g., \fp_eval_formatted:nn but I guess this is used in a similar way in other places, in which case it is probably not a good idea.

@josephwright
Copy link
Member

@FrankMittelbach Sure, name is to be settled. The question is whether we want something very lightweight in the fp module, or if it belongs elsewhere, or if we need something more complex.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request l3fp
Projects
None yet
Development

No branches or pull requests

9 participants