Modification-proposal for %% (modulo) when supplied with double

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Modification-proposal for %% (modulo) when supplied with double

Emil
Hi all,



Could we modify the "%%" (modulo)-operator to include some tolerance for rounding-errors when supplied with doubles?

It's not much work (patch supplied on the bottom), and I don't think it would break anything, only if you were really interested in analysing rounding differences.

Any ideas about implementing this and overwriting base::`%%`, or would we want another method (as I've done for the moment)?



Background

I was writing some code where something has to happen at a certain interval, with progress indicated, something like this:

interval <- .001

progress <- .1

for(i in 1:1000*interval) {myFun(i); Sys.sleep(interval); if(i %% progress, 0))) cat(i, '\n')}

without interval and progress being known in advance. I could work around it and make i integer, or do something like

isTRUE(all.equal(i %% progress,0)) || isTRUE(all.equal(i %% progress, progress),

but I think my code is clearer as it is. And I like the idea behind all.equal: we want double to approximately identical.



So my patch (with roxygen2-markup):

#' Modulo-operator with near-equality

#'

#' The \code{\link[base:Arithmetic]{`\%\%`}} operator calculates the modulo, but sometimes has rounding errors, e.g. "\code{(9.1/.1) \%\% 1}" gives ~ 1, instead of 0.\cr

#' Comparable to what all.equal does, this operator has some tolerance for small rounding errors.\cr

#' If the answer would be equal to the divisor within a small tolerance, 0 is returned instead.

#'

#' For integer x and y, the normal \%\%-operator is used

#'

#' @usage `\%mod\%`(x, y, tolerance = sqrt(.Machine$double.eps))

#' x \%mod\% y

#' @param x,y numeric vectors, similar to those passed on to \%\%

#' @param tolerance numeric, maximum difference, see \code{\link[base]{all.equal}}. The default is ~ \code{1.5e-8}

#' @return identical to the result for \%\%, unless the answer would be really close to y, in which case 0 is returned

#' @note To specify tolerance, use the call \code{`\%mod\%`(x,y,tolerance)}

#' @note The precedence for \code{\%mod\%} is the same as that for \code{\%\%}

#'

#' @name mod

#' @rdname mod

#'

#' @export

`%mod%` <- function(x,y, tolerance = sqrt(.Machine$double.eps)) {

  stopifnot(is.numeric(x), is.numeric(y), is.numeric(tolerance),

            !is.na(tolerance), length(tolerance)==1, tolerance>=0)

  if(is.integer(x) && is.integer(y)) {

    return(x %% y)

  } else {

    ans <- x %% y

    return(ifelse(abs(ans-y)<tolerance | abs(ans)<tolerance, 0, ans))

  }

}



Best regards,

Emil Bode

        [[alternative HTML version deleted]]

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Reply | Threaded
Open this post in threaded view
|

Re: Modification-proposal for %% (modulo) when supplied with double

Duncan Murdoch-2
On 11/09/2018 11:23 AM, Emil Bode wrote:
> Hi all,
>
>
>
> Could we modify the "%%" (modulo)-operator to include some tolerance for rounding-errors when supplied with doubles?
>
> It's not much work (patch supplied on the bottom), and I don't think it would break anything, only if you were really interested in analysing rounding differences.
>
> Any ideas about implementing this and overwriting base::`%%`, or would we want another method (as I've done for the moment)?

I think this is a bad idea.  Your comments say "The
\code{\link[base:Arithmetic]{`\%\%`}} operator calculates the modulo,
but sometimes has rounding errors, e.g. "\code{(9.1/.1) \%\% 1}" gives ~
1, instead of 0."

This is false.  The %% calculation is exactly correct.  The rounding
error happened in your input:  9.1/0.1 is not equal to 91, it is a
little bit less:

 > options(digits=20)
 > 9.1/.1
[1] 90.999999999999985789

And %% did not return 1, it returned the correct value:

 > (9.1/.1) %% 1
[1] 0.99999999999998578915

So it makes no sense to change %%.

You might argue that the division 9.1/.1 is giving the wrong answer, but
in fact that answer is correct too.  The real problem is that in double
precision floating point the numbers 9.1 and .1 can't be represented
exactly.  This is well known, it's in the FAQ (question 7.31).

Duncan Murdoch

>
>
>
> Background
>
> I was writing some code where something has to happen at a certain interval, with progress indicated, something like this:
>
> interval <- .001
>
> progress <- .1
>
> for(i in 1:1000*interval) {myFun(i); Sys.sleep(interval); if(i %% progress, 0))) cat(i, '\n')}
>
> without interval and progress being known in advance. I could work around it and make i integer, or do something like
>
> isTRUE(all.equal(i %% progress,0)) || isTRUE(all.equal(i %% progress, progress),
>
> but I think my code is clearer as it is. And I like the idea behind all.equal: we want double to approximately identical.
>
>
>
> So my patch (with roxygen2-markup):
>
> #' Modulo-operator with near-equality
>
> #'
>
> #' The \code{\link[base:Arithmetic]{`\%\%`}} operator calculates the modulo, but sometimes has rounding errors, e.g. "\code{(9.1/.1) \%\% 1}" gives ~ 1, instead of 0.\cr
>
> #' Comparable to what all.equal does, this operator has some tolerance for small rounding errors.\cr
>
> #' If the answer would be equal to the divisor within a small tolerance, 0 is returned instead.
>
> #'
>
> #' For integer x and y, the normal \%\%-operator is used
>
> #'
>
> #' @usage `\%mod\%`(x, y, tolerance = sqrt(.Machine$double.eps))
>
> #' x \%mod\% y
>
> #' @param x,y numeric vectors, similar to those passed on to \%\%
>
> #' @param tolerance numeric, maximum difference, see \code{\link[base]{all.equal}}. The default is ~ \code{1.5e-8}
>
> #' @return identical to the result for \%\%, unless the answer would be really close to y, in which case 0 is returned
>
> #' @note To specify tolerance, use the call \code{`\%mod\%`(x,y,tolerance)}
>
> #' @note The precedence for \code{\%mod\%} is the same as that for \code{\%\%}
>
> #'
>
> #' @name mod
>
> #' @rdname mod
>
> #'
>
> #' @export
>
> `%mod%` <- function(x,y, tolerance = sqrt(.Machine$double.eps)) {
>
>    stopifnot(is.numeric(x), is.numeric(y), is.numeric(tolerance),
>
>              !is.na(tolerance), length(tolerance)==1, tolerance>=0)
>
>    if(is.integer(x) && is.integer(y)) {
>
>      return(x %% y)
>
>    } else {
>
>      ans <- x %% y
>
>      return(ifelse(abs(ans-y)<tolerance | abs(ans)<tolerance, 0, ans))
>
>    }
>
> }
>
>
>
> Best regards,
>
> Emil Bode
>
> [[alternative HTML version deleted]]
>
> ______________________________________________
> [hidden email] mailing list
> https://stat.ethz.ch/mailman/listinfo/r-devel
>

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Reply | Threaded
Open this post in threaded view
|

Re: Modification-proposal for %% (modulo) when supplied with double

frederik
Duncan, I think Emil realizes that the floating point format isn't
able to represent certain numbers, that's why he is suggesting this
change rather than complaining about our arithmetic being broken.

However, I agree with you that we should not adopt his proposal. It
would not make things more "user friendly" for people. Everyone has a
different application and a different use of %% and they just need to
keep in mind that they are talking to a computer and not a blackboard.
Here is an example of a feature that was meant to help users get more
intuitive results with floating point numbers, but which actually
caused headaches instead:
https://github.com/Rdatatable/data.table/issues/1642 It is a slightly
different scenario to this one, but I think it is still a good example
of how we can end up creating unforeseen problems for people if we
change core functionality to do unsolicited rounding behind the
scenes.

Best wishes,

Frederick

On Tue, Sep 11, 2018 at 12:11:29PM -0400, Duncan Murdoch wrote:

> On 11/09/2018 11:23 AM, Emil Bode wrote:
> > Hi all,
> >
> >
> >
> > Could we modify the "%%" (modulo)-operator to include some tolerance for rounding-errors when supplied with doubles?
> >
> > It's not much work (patch supplied on the bottom), and I don't think it would break anything, only if you were really interested in analysing rounding differences.
> >
> > Any ideas about implementing this and overwriting base::`%%`, or would we want another method (as I've done for the moment)?
>
> I think this is a bad idea.  Your comments say "The
> \code{\link[base:Arithmetic]{`\%\%`}} operator calculates the modulo, but
> sometimes has rounding errors, e.g. "\code{(9.1/.1) \%\% 1}" gives ~ 1,
> instead of 0."
>
> This is false.  The %% calculation is exactly correct.  The rounding error
> happened in your input:  9.1/0.1 is not equal to 91, it is a little bit
> less:
>
> > options(digits=20)
> > 9.1/.1
> [1] 90.999999999999985789
>
> And %% did not return 1, it returned the correct value:
>
> > (9.1/.1) %% 1
> [1] 0.99999999999998578915
>
> So it makes no sense to change %%.
>
> You might argue that the division 9.1/.1 is giving the wrong answer, but in
> fact that answer is correct too.  The real problem is that in double
> precision floating point the numbers 9.1 and .1 can't be represented
> exactly.  This is well known, it's in the FAQ (question 7.31).
>
> Duncan Murdoch
>
> >
> >
> >
> > Background
> >
> > I was writing some code where something has to happen at a certain interval, with progress indicated, something like this:
> >
> > interval <- .001
> >
> > progress <- .1
> >
> > for(i in 1:1000*interval) {myFun(i); Sys.sleep(interval); if(i %% progress, 0))) cat(i, '\n')}
> >
> > without interval and progress being known in advance. I could work around it and make i integer, or do something like
> >
> > isTRUE(all.equal(i %% progress,0)) || isTRUE(all.equal(i %% progress, progress),
> >
> > but I think my code is clearer as it is. And I like the idea behind all.equal: we want double to approximately identical.
> >
> >
> >
> > So my patch (with roxygen2-markup):
> >
> > #' Modulo-operator with near-equality
> >
> > #'
> >
> > #' The \code{\link[base:Arithmetic]{`\%\%`}} operator calculates the modulo, but sometimes has rounding errors, e.g. "\code{(9.1/.1) \%\% 1}" gives ~ 1, instead of 0.\cr
> >
> > #' Comparable to what all.equal does, this operator has some tolerance for small rounding errors.\cr
> >
> > #' If the answer would be equal to the divisor within a small tolerance, 0 is returned instead.
> >
> > #'
> >
> > #' For integer x and y, the normal \%\%-operator is used
> >
> > #'
> >
> > #' @usage `\%mod\%`(x, y, tolerance = sqrt(.Machine$double.eps))
> >
> > #' x \%mod\% y
> >
> > #' @param x,y numeric vectors, similar to those passed on to \%\%
> >
> > #' @param tolerance numeric, maximum difference, see \code{\link[base]{all.equal}}. The default is ~ \code{1.5e-8}
> >
> > #' @return identical to the result for \%\%, unless the answer would be really close to y, in which case 0 is returned
> >
> > #' @note To specify tolerance, use the call \code{`\%mod\%`(x,y,tolerance)}
> >
> > #' @note The precedence for \code{\%mod\%} is the same as that for \code{\%\%}
> >
> > #'
> >
> > #' @name mod
> >
> > #' @rdname mod
> >
> > #'
> >
> > #' @export
> >
> > `%mod%` <- function(x,y, tolerance = sqrt(.Machine$double.eps)) {
> >
> >    stopifnot(is.numeric(x), is.numeric(y), is.numeric(tolerance),
> >
> >              !is.na(tolerance), length(tolerance)==1, tolerance>=0)
> >
> >    if(is.integer(x) && is.integer(y)) {
> >
> >      return(x %% y)
> >
> >    } else {
> >
> >      ans <- x %% y
> >
> >      return(ifelse(abs(ans-y)<tolerance | abs(ans)<tolerance, 0, ans))
> >
> >    }
> >
> > }
> >
> >
> >
> > Best regards,
> >
> > Emil Bode
> >
> > [[alternative HTML version deleted]]
> >
> > ______________________________________________
> > [hidden email] mailing list
> > https://stat.ethz.ch/mailman/listinfo/r-devel
> >
>
> ______________________________________________
> [hidden email] mailing list
> https://stat.ethz.ch/mailman/listinfo/r-devel
>

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Reply | Threaded
Open this post in threaded view
|

Re: Modification-proposal for %% (modulo) when supplied with double

Emil
Okay, thanks for your reactions.
I realized it's not something being "broken", just that it's a situation where my intuition messes things up, and that in this situation it's not as easy as using all.equal, which is my usual approach when working with floats.
But maybe I underestimated the impact a change would have, thanks for the example Frederick.

Best,
Emil

´╗┐On 11/09/2018, 21:10, "[hidden email]" <[hidden email]> wrote:

    Duncan, I think Emil realizes that the floating point format isn't
    able to represent certain numbers, that's why he is suggesting this
    change rather than complaining about our arithmetic being broken.
   
    However, I agree with you that we should not adopt his proposal. It
    would not make things more "user friendly" for people. Everyone has a
    different application and a different use of %% and they just need to
    keep in mind that they are talking to a computer and not a blackboard.
    Here is an example of a feature that was meant to help users get more
    intuitive results with floating point numbers, but which actually
    caused headaches instead:
    https://github.com/Rdatatable/data.table/issues/1642 It is a slightly
    different scenario to this one, but I think it is still a good example
    of how we can end up creating unforeseen problems for people if we
    change core functionality to do unsolicited rounding behind the
    scenes.
   
    Best wishes,
   
    Frederick
   
    On Tue, Sep 11, 2018 at 12:11:29PM -0400, Duncan Murdoch wrote:
    > On 11/09/2018 11:23 AM, Emil Bode wrote:
    > > Hi all,
    > >
    > >
    > >
    > > Could we modify the "%%" (modulo)-operator to include some tolerance for rounding-errors when supplied with doubles?
    > >
    > > It's not much work (patch supplied on the bottom), and I don't think it would break anything, only if you were really interested in analysing rounding differences.
    > >
    > > Any ideas about implementing this and overwriting base::`%%`, or would we want another method (as I've done for the moment)?
    >
    > I think this is a bad idea.  Your comments say "The
    > \code{\link[base:Arithmetic]{`\%\%`}} operator calculates the modulo, but
    > sometimes has rounding errors, e.g. "\code{(9.1/.1) \%\% 1}" gives ~ 1,
    > instead of 0."
    >
    > This is false.  The %% calculation is exactly correct.  The rounding error
    > happened in your input:  9.1/0.1 is not equal to 91, it is a little bit
    > less:
    >
    > > options(digits=20)
    > > 9.1/.1
    > [1] 90.999999999999985789
    >
    > And %% did not return 1, it returned the correct value:
    >
    > > (9.1/.1) %% 1
    > [1] 0.99999999999998578915
    >
    > So it makes no sense to change %%.
    >
    > You might argue that the division 9.1/.1 is giving the wrong answer, but in
    > fact that answer is correct too.  The real problem is that in double
    > precision floating point the numbers 9.1 and .1 can't be represented
    > exactly.  This is well known, it's in the FAQ (question 7.31).
    >
    > Duncan Murdoch
    >
    > >
    > >
    > >
    > > Background
    > >
    > > I was writing some code where something has to happen at a certain interval, with progress indicated, something like this:
    > >
    > > interval <- .001
    > >
    > > progress <- .1
    > >
    > > for(i in 1:1000*interval) {myFun(i); Sys.sleep(interval); if(i %% progress, 0))) cat(i, '\n')}
    > >
    > > without interval and progress being known in advance. I could work around it and make i integer, or do something like
    > >
    > > isTRUE(all.equal(i %% progress,0)) || isTRUE(all.equal(i %% progress, progress),
    > >
    > > but I think my code is clearer as it is. And I like the idea behind all.equal: we want double to approximately identical.
    > >
    > >
    > >
    > > So my patch (with roxygen2-markup):
    > >
    > > #' Modulo-operator with near-equality
    > >
    > > #'
    > >
    > > #' The \code{\link[base:Arithmetic]{`\%\%`}} operator calculates the modulo, but sometimes has rounding errors, e.g. "\code{(9.1/.1) \%\% 1}" gives ~ 1, instead of 0.\cr
    > >
    > > #' Comparable to what all.equal does, this operator has some tolerance for small rounding errors.\cr
    > >
    > > #' If the answer would be equal to the divisor within a small tolerance, 0 is returned instead.
    > >
    > > #'
    > >
    > > #' For integer x and y, the normal \%\%-operator is used
    > >
    > > #'
    > >
    > > #' @usage `\%mod\%`(x, y, tolerance = sqrt(.Machine$double.eps))
    > >
    > > #' x \%mod\% y
    > >
    > > #' @param x,y numeric vectors, similar to those passed on to \%\%
    > >
    > > #' @param tolerance numeric, maximum difference, see \code{\link[base]{all.equal}}. The default is ~ \code{1.5e-8}
    > >
    > > #' @return identical to the result for \%\%, unless the answer would be really close to y, in which case 0 is returned
    > >
    > > #' @note To specify tolerance, use the call \code{`\%mod\%`(x,y,tolerance)}
    > >
    > > #' @note The precedence for \code{\%mod\%} is the same as that for \code{\%\%}
    > >
    > > #'
    > >
    > > #' @name mod
    > >
    > > #' @rdname mod
    > >
    > > #'
    > >
    > > #' @export
    > >
    > > `%mod%` <- function(x,y, tolerance = sqrt(.Machine$double.eps)) {
    > >
    > >    stopifnot(is.numeric(x), is.numeric(y), is.numeric(tolerance),
    > >
    > >              !is.na(tolerance), length(tolerance)==1, tolerance>=0)
    > >
    > >    if(is.integer(x) && is.integer(y)) {
    > >
    > >      return(x %% y)
    > >
    > >    } else {
    > >
    > >      ans <- x %% y
    > >
    > >      return(ifelse(abs(ans-y)<tolerance | abs(ans)<tolerance, 0, ans))
    > >
    > >    }
    > >
    > > }
    > >
    > >
    > >
    > > Best regards,
    > >
    > > Emil Bode
    > >
    > > [[alternative HTML version deleted]]
    > >
    > > ______________________________________________
    > > [hidden email] mailing list
    > > https://stat.ethz.ch/mailman/listinfo/r-devel
    > >
    >
    > ______________________________________________
    > [hidden email] mailing list
    > https://stat.ethz.ch/mailman/listinfo/r-devel
    >
   

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel