JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.54k stars 5.47k forks source link

Round to Nearest Fraction #53154

Open uniment opened 8 months ago

uniment commented 8 months ago

We often round numbers to the nearest fraction—for example, rounding to the nearest half or nearest quarter.

Normally we do something like round(val*4)//4, but maybe it would be good to incorporate this functionality into round itself? For my own use, I found these to be convenient:

Base.round(to::Rational, val::Real) = round(Int, val*to.den/to.num)*to
Base.floor(to::Rational, val::Real) = floor(Int, val*to.den/to.num)*to
Base.ceil(to::Rational,  val::Real) = ceil(Int,  val*to.den/to.num)*to

Example use:

julia> floor.(1//3, 0:1/6:1)
7-element Vector{Rational{Int64}}:
 0//1
 0//1
 1//3
 1//3
 2//3
 2//3
 1//1

Does this look like an appealing feature to implement in the language?

adienes commented 8 months ago

this got me thinking it might be equivalently useful and more general to instead implement round(::T, ::StepRangeLen), e.g.

round(0.2, 0:1//4:1) to return 1//4

nhz2 commented 8 months ago

Something close to your example, apart from floating point rounding is:

julia> floor.(0:1/6:1; digits=1, base=3)
7-element Vector{Float64}:
 0.0
 0.0
 0.3333333333333333
 0.3333333333333333
 0.6666666666666666
 0.6666666666666666
 1.0
uniment commented 8 months ago

equivalently useful and more general to instead implement round(::T, ::StepRangeLen)

I prefer the argument order reversed tbh, but that could be useful too. I wouldn't say it's more general, since the range is bounded whereas for the OP the range is unbounded, but it could be useful in its own right for performing a composite round and clamp operation while also permitting an offset from zero for the points on the lattice that the rounding operation will snap to.

For example:

round(1//4,  1.3) == 5//4            # this proposal: round to nearest 1//4, unbounded
round(0:1//4:1,  1.3) == 1//1        # StepRangeLen: round to nearest 1//4, clamped to the range 0:1
round(1//4:1//2:9//4,  1.3) == 5//4  # round to nearest 1//2 on a grid offset by 1//4

Something close to your example, apart from floating point rounding

Clever!