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

merging itrunc into trunc, etc. #8728

Closed
StefanKarpinski opened this issue Oct 18, 2014 · 19 comments
Closed

merging itrunc into trunc, etc. #8728

StefanKarpinski opened this issue Oct 18, 2014 · 19 comments
Assignees
Milestone

Comments

@StefanKarpinski
Copy link
Sponsor Member

@simonbyrne observed that we could replace itrunc(x) with trunc(Integer, x) and similarly for iround, iceil and ifloor, making for a four-function reduction to the list of exports from Base. So far so good, but this brings up the issue of what these function generically mean, which is why I'm filing this issue for discussion.

@StefanKarpinski
Copy link
Sponsor Member Author

Having opened the issue, let me take a stab.

  • trunc(T, x) returns the integer-valued element of type T with maximal absolute value such that the absolute value is not larger than that of x.
  • round(T, x) returns the integer-valued element of type T closest to the value of x.
  • ceil(T, x) returns the smallest integer-valued element of type T not less than x.
  • floor(T, x) returns the largest integer-valued element of type T not greater than x.

The single-argument versions are trunc(x) = trunc(typeof(x), x), etc. I think that all works ok.

Open question: how to make this dovetail with the two-argument form of round: round(pi, 3).

@StefanKarpinski StefanKarpinski self-assigned this Oct 18, 2014
@JeffBezanson
Copy link
Sponsor Member

👍

I don't mind so much keeping round(pi, 3) along with this change, but that should arguably be renamed. I think it's actually a very rarely-used operation.

@JeffBezanson JeffBezanson added this to the 0.4 milestone Oct 18, 2014
@simonbyrne
Copy link
Contributor

This makes sense. The typical signature would be:

round([T,]x[, digits[, base]])

which should handle cases like round(pi,3) correctly.

As far as definitions go: trunc,ceil and floor are fairly well-defined. We currently follow the C convention of having round round ties away from zero. As mentioned in #5983, we should probably also have a function that rounds ties to even (rounde?): this has the benefit of having a native CPU instruction; as well as a function that uses the current rounding mode (rint in C).

@StefanKarpinski
Copy link
Sponsor Member Author

So what does the general signature round(T, x, digits, base) mean?

@simonbyrne
Copy link
Contributor

Same as round(x, digits, base), except it returns a value of type T.

Admittedly, we probably don't need to define the general form for T<:Integer (though I guess there could be demand for round(Int,123,-2) == 100)

@StefanKarpinski
Copy link
Sponsor Member Author

Same as round(x, digits, base), except it returns a value of type T.

That's not really an answer. I mean something like

round(T, x) returns the integer-valued element of type T closest to the value of x.

What do you replace "integer-valued" with when digits is non-zero? What is the role of base?

@simonbyrne
Copy link
Contributor

How's this:

  • round(T,x,d) returns the nearest number of type T with d digits after the decimal place (or before if d is negative). If T is an integer type, then d > 0 will cause an InexactError() to be thrown.
  • round(T,x,d,b) rounds in base b. For example, round(Uint8,0x73,-1,16) == 0x70.

@StefanKarpinski
Copy link
Sponsor Member Author

round(T,x,d) returns the nearest number of type T with d digits after the decimal place

I'm not sure that makes sense – e.g. which Float64 values have 3 decimal digits after the point?

@simonbyrne
Copy link
Contributor

I guess we want to say something like "the value of type T nearest to the value of x rounded to d decimal places". I'm not sure that what we're doing is actually correctly rounded though.

Also, I guess DomainError would be probably more appropriate than InexactError, as we would probably always want to throw it.

Something like this should work for integers:

function round{T<:Integer}(::Type{T},x::Integer,d::Integer,b::Integer)
    d <= 0 || throw(DomainError())
    s = copysign(b>>1,x)
    q = -d*b
    convert(T,div(x+s,q)*q)
end

@StefanKarpinski
Copy link
Sponsor Member Author

Ok, now we're getting somewhere. I have also suspected that what we're doing may not be correctly rounded. It's actually kind of a strange operation to round a binary floating-point value to a number of decimal digits – number of decimal digits is a printing issue, not about values, really.

@simonbyrne
Copy link
Contributor

I agree. We should probably add a big warning to the docs, and refer them to @printf and friends.

@StefanKarpinski
Copy link
Sponsor Member Author

I wonder if we shouldn't deprecate rounding to various decimal places.

@ArchRobison
Copy link
Contributor

I found using round(x,n) handy for quickly hacking displayed accuracy in string interpolation. As far as I can tell from semi-exhaustive search (for Float32 over the interval [0.1,1], string(round(x,n)) really does limit the number of decimal places displayed to n or fewer. I think the property is inherent in the way that the printing of decimal numerals never uses more digits than necessary to distinguish from the next closest floating-point value.

But I concur that it is a bit weird, and the documentation should have a big pointer to using @printf. etc.

@StefanKarpinski
Copy link
Sponsor Member Author

You may be right – the hard work done by printing to ensure that the formatted output is minimal probably does guarantee that property. I'd like to convince myself slightly more thoroughly. Also, Float32 is the best thing ever for testing stuff like this since you can do exhaustive testing.

@simonbyrne
Copy link
Contributor

The value returned should always print to the correct number of decimal places, provided the quantity 10^d is representable in the floating point type: the final division operation involves two exactly representable integers, and will return the closest floating point value to i // 10^d, which should always print to at most d decimal places.

The problem is that we don't strictly round in the correct direction:

julia> @printf "%.60f" 8.315
8.314999999999999502620084967929869890213012695312500000000000
julia> round(8.315,2)
8.32
julia> @printf "%.2f" 8.315
8.31

Strictly speaking, 8.315 (the Float64 value) should be rounded down, as it's closer to 8.31 than 8.32.

@simonbyrne
Copy link
Contributor

Interestingly, python gets it right (both 2 and 3, even though they changed rounding behaviour): here is an interesting StackOverflow post.

@JeffBezanson
Copy link
Sponsor Member

Reminds me of #3497

@JeffBezanson
Copy link
Sponsor Member

This issue needs a new title, or else can be closed.

@JeffBezanson
Copy link
Sponsor Member

Closed by #9133.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants