Rounding to a multiple of float

You’re right… OP: It is generally discouraged to use internal functions, since they might disappear or change behavior in future versions of Julia, and they’re also undocumented which makes your code harder to read. If the problem can be solved by using the publicly exported API that would be preferred.

In this case, there is indeed no reason to use _round_invstep, since that method simply boils down to:

_round_invstep(x, invstep, r::RoundingMode) = round(x * invstep, r) / invstep

This is almost identical to your initial suggestion, but with the step inverted. So you could modify your roundmult like this:

julia> roundmult(val, prec) = (inv_prec = 1 / prec; round(val * inv_prec) / inv_prec);

julia> roundmult(3811.47123123, 0.01)
3811.47
2 Likes

Unfortunately, it appears like OP needs to deal with exactly such an absurd third-party interface.

So the real question is not “round to multiple of float” but instead “how to implement a rounding mode that successfully interfaces with this absurd third-party interface”.

I would not be entirely surprised if this third-party interface was emergent instead of intended: Maybe there are multiple rounds of “printf” and parsing with different kind of rounding modes / format-strings involved, maybe crossing Float32 or decimal or integer representations on some intermediate database servers that all subtly misbehave and combine to create such a nightmare scenario.

In order to interface with such an endpoint, one first needs to understand what it is actually doing, and then work around its bugs. If lucky by finding a source-line that is responsible for the problem, if unlucky by asking support or hammering their webserver. @bennedich implements a clean and principled function; but it is unlikely for a principled and clean solution to exist for this kind of suck.

something like this?

function roundmult(num,mul)
    if mul == 1
        return  round(num)
    elseif 0 < mul < 1
        return round(num/mul)/round(1/mul)
    else
        return round(round(num/mul)*mul)
    end
end 

output:

julia> roundmult(38112.47123123, 1000)
38000.0
julia> roundmult(38112.47123123, 0.01)
38112.47
julia> roundmult(1.06, 0.05)
1.05
julia> roundmult(1.09, 0.05)
1.1

the part for numbers greater than one can be improved. for a digits alternative:

function roundmult(num,digits)
mul = 10^-digits    
    if mul == 1
        return  round(num)
    elseif mul < 1 #the exponenciation takes care of negative numbers in mul
        return round(num/mul)/round(1/mul)
    else
        return round(round(num/mul)*mul)
    end
end 

I think this is an accurate description, with the understanding that 0.3 here is, as Tamas points out above, not exactly 0.3, but the closest Float64 representation of 0.3, which is 5404319552844595/2^54 = 0.299999999999999988897769753748434595763683319091796875. Since Julia knows that this is the closest float to 0.3, it will display and parse it as “0.3”.

Meanwhile, 0.1 is represented as 3602879701896397/2^55, so 0.1 * 3 becomes 10808639105689191/2^55, which rounds to 5404319552844596/2^54, which is not the same as above, i.e. not the closest number to 0.3, and thus it will display in full: 0.30000000000000004.

3 Likes

Thanks for all the inputs, I have learned a lot APIs, floats & related stuff.

As always, floating point arithmetic brings magic. I will rewrite my code to use this instead of the internal function.

I understand that the original question was ill-posed and that the external API is possibly not very well designed, but I appreciate that so many replies have been posted with many important observations. Thanks to everybody!

You might find this useful: PSA: floating-point arithmetic

3 Likes