I am trying to solve a simple problem - rounding a number to a nearest multiple of a given float. As an example, I would like roundmult(3811.47123123, 0.01) = 3811.47. However, the simplest implementation of roundmult(val, prec) = round(val / prec) * prec backfires by outputting roundmult(3811.47123123, 0.01) = 3811.4700000000003.
Is there any simple way of achieving this using some built-in functionality, or some magic of rounding the above result once more to the precision given by prec? Something like roundmult(val, prec) = round(round(val / prec) * prec, digits=ceil(Integer, -log10(prec)+1)) would probably work, but it is so ugly.
Cheeky I do not like the idea of changing the whole application due to this small issue
Also, the precision is not fixed upfront, it changes based on the current data. So your suggestion would not really work.
E.g. roundmult(0.3, 0.1) == 0.30000000000000004. This is correct: 3*0.1 == 0.30000000000000004. 0.3 is not an integer multiple of 0.1 in Float64.
Julia float literals are base-2 floating point. This means that 1//10 cannot be expressed (has an infinite number of nonzero digits), just like you would encounter rounding for 1//3 if you used decimal floats.
@Tamas_Papp Thanks for the suggestion! It is definitely a cleaner approach than the one I pointed out in the original post, but I would still expect something cleaner to be around.
@foobar_lv2 I understand your point. If I would use this value just in my Julia application, it makes no sense to consider this question at all. However, I am sending the result to an external API, which will only accept numbers rounded to these given floats. If I send the result 3811.4700000000003, my request will be simply declined. I really need to send 3811.47, therefore my question.
If it is only about rounding to a certain number of digits after the decimal, then your problem is simply about printing floats, which is much simpler than the on you describe (since prec is of the form 10^n).
Not really. I am sending a JSON, where the field is actually a float. Maybe it would make more sense if it would actually accept printed floats.
The issue is that the prec variable does not have to be of the form 10^{-n}. As in my original post, it could easily be 0.005. The simplest way to figure out the number of decimals is probably using the logarithm, which I also suggested in the original post.
A clean solution was already provided by bennedich - it works like charm.
The question is not how the JSON.jl library accepts it, which is an implementation detail, but how you can print it the way you like (since JSON is trivially easy to generate). See eg Base.Grisu.grisu for low-level float digits printing, which would allow you to generate either of the strings above.
Good point, I agree that rounding during the serialization would feel more natural than rounding the float. How would you round to an arbitrary step with grisu? I didn’t find any documentation about this. I.e. to accomplish this:
I’d advice against implementing one’s own JSON serializer, it can be hard to get all escaping, separators, etc, correct. I’ve seen bugs introduced this way before. The way to do it IMO would be to use a custom JSON serializer. Unless I’m missing something, the support for that doesn’t seem great in JSON.jl – I don’t think we can use JSON.lower for this. I guess we can do something like the below, although arguably the rounding should belong to the serializer not each individual float:
using JSON
struct MyRoundedFloat
x::Float64
step::Float64
end
function JSON.show_json(io::JSON.Writer.StructuralContext, s::JSON.Serializations.CommonSerialization, x::MyRoundedFloat)
print(io, "formatting code goes here")
end
This means that whatever endpoint you are talking to does crazy stuff you need to figure out (if you are lucky, then by reading code; if unlucky, by looking at a disassembly; if very unlucky, then by sending a bazillion requests and black-box reversing whatever they are doing).
If I would send 0.1 * 3 to the endpoint, something could go wrong, but it might work out, depending on the processing on the other side. But if I send actual 0.3, I should be safe.
As far as I understand, the problem isn’t that Julia cannot represent 0.3. The problem is that 0.1 * 3 does not give 0.3, due to the representation and arithmetic on these two numbers. Am I wrong?
Wasn’t there recently a discussion about the appropriateness of recommending internal functions? At least it should come with a warning, especially to new users.