Try

   round
[ * [: <. 0.5 + %~

which I found in j602.

--Kip Murray

On Thu, Jan 14, 2016 at 1:56 PM, Marshall Lochbaum <[email protected]>
wrote:

> Here's something I spent far too long on, and consequently thought was
> worth sharing. I can turn it into an essay on the J wiki if people want
> that.
>
> Recently I ran into the problem of rounding a J floating point number to
> an integer, and forcing the result to have integer type. This seems like
> a simple task: using a standard rounding function, we have
>    0.5 <.@:+ 2.3 5.1 7.6 3.9
> 2 5 8 4
> But with numbers that are too large, the result still contains
> floating-point numbers, and has type 8 (floating point) rather than 4
> (integer).
>    0.5 <.@:+ _1e50 2e30 _
> _1e50 2e30 _
>    3!:0 ]0.5 <.@:+ _1e50 2e30 _
> 8
> When applied to a float, (<.) applies the C floor function, which yields
> another float, and than casts the results to integers if all of them are
> exactly representable as integers. They're not here, so they are left as
> floating-point numbers.
>
> To give a more accurate problem statement, I want the 64-bit signed
> integer which is closest to the function input. Thus numbers above the
> maximum representable integer should round to that integer, and likewise
> for numbers below the minimum representable integer. We define these two
> bounds now.
>    MAX =: ->: MIN =: _2 <.@^ 63
> Note that since MAX is one less than 2^63, trying to take (2<.@^63)
> would give us a float, and subtracting one would still leave us with a
> floating point number, which is not actually equal to MAX since (>:MAX)
> is representable as a float, while nearby integers are not. MIN on the
> other hand is safely computed as an exponent. Note the negative base,
> which works because 63 is odd.
>
> With these bounds, our problem should be easy: clamp to the integer
> range, then use (<.).
>    ([: <. MIN>.MAX<.]) 0.5 + __ _1e30 _1e10 _100.3
> _9223372036854775808 _9223372036854775808 _10000000000 _100
> So far, so good...
>    ([: <. MIN>.MAX<.]) 0.5 + 50.4 2e10 1e30 _
> 50 2e10 9.22337e18 9.22337e18
> Oops. What happened?
>    MAX <. _
> 9.22337e18
> Since one of the arguments is a float, (<.) casts both to floats, and
> takes the minimum. But the closest floating-point number to MAX is
> (MAX+1), and that number's floor (MAX+1) isn't representable as an
> integer--it's one too big. We didn't have this problem with MIN, since
> it is exactly a negative power of two.
>    <. MAX+1
> 9.22337e18
>
> We'll make a test case that contains numbers close to both bounds. I've
> included the addition of 0.5 in t so I can focus on the floor function
> from now on. The type of our result is a float, so we failed.
>    t =. 0.5 + __,_,~ (MIN,0,MAX) +/(,@:) i:1e5
>    3!:0 ([: <. MIN>.MAX<.]) t
> 8
>
> We can fix the problem by using exact integers, but it's extremely slow.
> However, it serves as a good answer key. The ("0) is there for a
> reason--otherwise the big array of exact integers tends to flood RAM.
>    fl_e =: (MIN>.MAX<.<.)&.:x:"0  NB. exact floor
>    3!:0 key =. fl_e t
> 4
>    10 (6!:2) 'fl_e t'
> 3.62018
>
> If we use a number small enough that its floating-point representation
> is equal to a 64-bit integer, then we can force our answer to be a
> float, but it's not correct since the results are sometimes too small.
> If that doesn't matter and speed is critical, this is the right method
> to use.
>    MAX1 =. MAX - 512
>    fl_f =: [: <. MIN>.MAX1<.]  NB. fast floor
>    3!:0 fl_f t
> 4
>    key -: fl_f t
> 0
>    10 (6!:2) 'fl_f t'
> 0.0179205
>
> Finally, my solution. It's not particularly elegant, but it is correct
> and has good performance. We reduce all the values larger than MAX to
> zero, then clamp on the minimum side and take the floor. For the values
> that we removed, we add MAX back in. The comparison (<:&MAX) is only
> computed once to save a little time.
>    fl =: ((MAX*-.@]) + [: <. MIN>.*) <:&MAX
>    key -: fl t
> 1
>    10 (6!:2) 'fl t'
> 0.0426181
> It's critical to use (<:) rather than (<) to test whether numbers are
> acceptable even though it fails MAX, which wouldn't break (<.). That's
> because comparisons cast their arguments to floats before comparing, so
>    MAX < MAX+1
> 0
>
> Maybe there's a quicker solution to be found. The following rounds
> towards zero quickly by negating all the positive numbers, and restoring
> their signs later. However, adding in the cases to make it equal to (<.)
> on small numbers removes its advantage.
>    fl_o =: (] * MIN <.@:>. *) -@:*  NB. floor towards zero
>    fl_o _4.6 _3 _2.8 _1.2 3.4 5.8 9
> _5 _3 _3 _2 4 6 9
>    10 (6!:2) 'fl_o t'
> 0.0324293
>
> Any takers?
>
> Marshall
> ----------------------------------------------------------------------
> For information about J forums see http://www.jsoftware.com/forums.htm
----------------------------------------------------------------------
For information about J forums see http://www.jsoftware.com/forums.htm

Reply via email to