15
\$\begingroup\$

The round function in Python 3 will round values of the form \$k+\frac{1}{2}\$ (with \$k\$ being an integer) to the nearest even integer, which is apparently better than the rounding method taught in school

Your task is to recreate this functionality: write a program of function that given a (real) number as input, outputs that number rounded to the nearest number, using the same tie-breaking rule as Python's round function

Examples/Test-cases

-3.0   ->    -3
-2.7   ->    -3
-2.5   ->    -2
-2.2   ->    -2
-1.7   ->    -2
-1.5   ->    -2
-0.7   ->    -1
-0.5   ->    0
0.0    ->    0
0.4    ->    0
0.5    ->    0
0.51   ->    1
1.0    ->    1
1.2    ->    1
1.4999 ->    1
1.5    ->    2
1.7    ->    2
2.0    ->    2
2.3    ->    2
2.5    ->    2
2.6    ->    3

Rules

  • Please add built-in solutions to the community wiki
  • This is the shortest solution wins
\$\endgroup\$
4
  • \$\begingroup\$ What do you mean "better than the usual rounding method"? Nearest-even is the usual method and has been for decades. As the linked answer says, it's the default rounding mode for IEEE-754 FP math. (Including for rounding to the nearest representable float after operations like * and +, as well as for rounding to integer-valued float with C functions like nearbyint, or as part of conversion to integer with C functions like lrint. If you're talking about C's round() function which rounds away from 0 as a a tie-break, that's not "standard".) \$\endgroup\$ Commented Aug 29, 2023 at 20:32
  • \$\begingroup\$ @PeterCordes Usual in the sense that the rounding mode taught in school (and used by the round method in C/Java/JavaScript) is to always round up (for positive numbers) if the last digit is 5. The first time I heard about nearest-even rounding being commonly used was when I searched why Pythons round was not doing what I expected it to do (round(0.5) not evaluating to 1). \$\endgroup\$
    – bsoelch
    Commented Aug 30, 2023 at 8:37
  • \$\begingroup\$ Ok, fair point that it's not just C where a function called round is the simpler strategy of rounding away from zero as a tie-break; I guess this strategy was at least formerly considered "usual" by high-level language designers. I'd previously assumed C's round function was just a bad ancient design choice, and I've always thought of lrint and nearbyint as the normal rounding functions after investigating to find out they were the ones supported by hardware features on CPUs like x86, but I guess I'm weird :P \$\endgroup\$ Commented Aug 30, 2023 at 9:11
  • 1
    \$\begingroup\$ Exceedingly similar to codegolf.stackexchange.com/questions/256353/…. I'd probably say its a dupe. \$\endgroup\$
    – Wheat Wizard
    Commented Sep 3, 2023 at 19:00

13 Answers 13

12
\$\begingroup\$

JavaScript (ES6), 30 bytes

Uses the Math.round() built-in, then clears the least significant bit of the result if the squared fractional part of the original input is \$1/4\$.

v=>Math.round(v)&~(v*v%1==.25)

Try it online!

Explanation

Math.round does rounding half up.

We have 4 cases. Given \$k\ge 0\$:

  • \$2k+1/2\$ is rounded to \$2k+1\$. Clearing the LSB turns it into \$2k\$.

    Example: \$22.5 \rightarrow 23 \rightarrow 22\$

  • \$2k+1+1/2\$ is rounded to \$2k+2\$. Clearing the LSB leaves it unchanged.

    Example: \$23.5 \rightarrow 24 \rightarrow 24\$

  • \$-(2k+1/2)\$ is rounded to \$-2k\$. Clearing the LSB leaves it unchanged.

    Example: \$-22.5 \rightarrow -22 \rightarrow -22\$

  • \$-(2k+1+1/2)\$ is rounded to \$-(2k+1)\$. Clearing the LSB turns it into \$-(2k+2)\$.

    Example: \$-23.5 \rightarrow -23 \rightarrow -24\$

\$\endgroup\$
5
  • \$\begingroup\$ What rounding mode does JS's Math.Round use? Is it like C/C++'s round() function that rounds away from 0 as a tie-break? (en.wikipedia.org/wiki/Rounding#Rounding_half_away_from_zero / round() for float in C++) . Unlike C nearbyint and rint / lrint which use the current FP rounding mode, thus usually the one the OP wants. I guess JS doesn't have equivalents for those, unless you're avoiding using a buitin. \$\endgroup\$ Commented Aug 29, 2023 at 20:49
  • 1
    \$\begingroup\$ @PeterCordes Math.round() does rounding half up (23.5 -> 24, -23.5 -> -23). Here is the official spec. \$\endgroup\$
    – Arnauld
    Commented Aug 29, 2023 at 21:22
  • \$\begingroup\$ Thanks. So Math.rint() isn't part of the standard, since that official spec doesn't include it. But still, if it's available in some implementations maybe someone should add it to the community-wiki answer. \$\endgroup\$ Commented Aug 30, 2023 at 0:41
  • \$\begingroup\$ @PeterCordes This is very late but, Java and JavaScript are not the same language, nor are they really at all similar besides some syntax (confusing, I know), and therefore any methods that exist in Java may have no connection to JavaScript. There is no Math.rint() in any common JS implementation. \$\endgroup\$ Commented Jan 21 at 4:09
  • \$\begingroup\$ @noodleman: Oh crap, I do know that; maybe I left "javascript" out of my search terms since I ended up with one JS link (help.hcltechsw.com/dom_designer/9.0.1/reference/… which might be some specific library?) and the other a Java link about Java's Math.rint(), and even more embarrassingly managed to type Java instead of JS in that comment. Deleting it, thanks for spotting the noise and confirming that there's no common JS Math.rint(). \$\endgroup\$ Commented Jan 21 at 4:46
11
\$\begingroup\$

Builtins in various languages

05AB1E (legacy), 1 byte

ò

Only works in the legacy version of 05AB1E, which is builtin in Python. The new version of 05AB1E, which is built in Elixir, will round away from zero.

Try it online or verify all test cases.

C, 5 bytes

lrint

The Linux manual page lists this as being part of the C99, POSIX.1-2001, and POSIX.1-2008 standards. It can be used without a prototype for golfing since it returns long, unlike the 4-byte rint which returns double and thus can handle the full range of double inputs. Both these and nearbyint use the current rounding FP mode, which is nearest-even by default on IEEE-754 systems, unlike C's round function which uses away-from-zero as a tiebreak (like Python 2's round).

C++11 includes the C99 math library (which many C/C++ implementations already provided). Since you need prototypes anyway in C++, std::rint or just rint 4 bytes should be available after #include <cmath> (cppreference).

C# (Visual C# Compiler), 17 bytes

x=>Math.Round(x);

Try it online!

The build in Math.Round in C# already rounds to the nearest even number

Factor, 13 bytes

round-to-even

Attempt This Online!

Forth (gforth), 6 bytes

fround

Try it online!

Haskell, 5 bytes

round

Try it online! From the documentation:

round x returns the nearest integer to x; the even integer if x is equidistant between two integers

Jelly, 3 bytes

ær0

Try it online!

MMIX, 4 bytes/1 instruction

␗�␄�

The replacement characters are in order the output and input registers; Unicode control pictures have been used to encode unprintable characters. This is FINT $x,ROUND_NEAR,$y, which rounds a float to the nearest integer (half-even).

PowerShell Core, 22 bytes

[Math]::Round("$args")

Try it online!

PHP, 20 bytes

PHP's round has a third parameter "mode" that can be used with the constant PHP_ROUND_HALF_EVEN (value: 3)

<?=round($argn,0,3);

Try it online!

Python, 5 bytes

round

Attempt This Online!

R, 5 bytes

round

Attempt This Online!

Rust, 25 bytes

Using libmath

math::round::half_to_even

Thunno 2, 1 byte

V

Try it online!

Vyxal, 1 byte

Try it Online!

\$\endgroup\$
3
  • \$\begingroup\$ Edit in other built-ins that do this exactly. \$\endgroup\$ Commented Aug 27, 2023 at 12:46
  • \$\begingroup\$ C double rint(double) should work, too, with implicit conversion of the integer-valued double return to integer. Except if the double value is out of range for the integer type you assign to, in which case the conversion is undefined behaviour. (In that case lrint has an "unspecified" return value, but not undefined behaviour.). Oh but I guess for code-golf, you can get away without prototyping lrint in typical calling-conventions where long and int are compatible enough. But a function returning double needs a prototype. \$\endgroup\$ Commented Aug 29, 2023 at 20:37
  • \$\begingroup\$ Related: Problems casting NAN floats to int re: it being UB, and unsigned conversion in C works as expected on x86 but not ARM for an example of what happens in practice. \$\endgroup\$ Commented Aug 29, 2023 at 20:42
8
\$\begingroup\$

Jelly, 9 bytes

Jelly's builtin is 3 bytes: ær0 - round to nearest multiple of \$10^0\$, which employs Python's round.

Ḟ,ĊḂÞạÞ⁸Ḣ

Try it online!

How?

Ḟ,ĊḂÞạÞ⁸Ḣ - Link: float, X
Ḟ         - floor {X}
  Ċ       - ceil {X}
 ,        - pair -> [floor(X), ceil(X)]
    Þ     - sort by:
   Ḃ      -   is odd?
      Þ   - sort by:
     ạ ⁸  -   absolute difference {X}
        Ḣ - head
\$\endgroup\$
6
\$\begingroup\$

J, 7 bytes

0".@":]

Try it online!

Formats to zero decimal places and then evaluates back to number.

J, 14 bytes

<.@+1-:@~:4|+:

Try it online!

Evaluates floor(x + 0.5) unless 2*x%4 == 1, in which case floor(x) is returned instead.

<.@+1-:@~:4|+:
          4|+:   2*x%4
    1   ~:       != 1
     -:@         / 2
   +             x +
<.@              floor()
\$\endgroup\$
1
  • \$\begingroup\$ 0".@":] is a brilliant find, and the other one is a nice golf too. \$\endgroup\$
    – Jonah
    Commented Aug 28, 2023 at 4:31
5
\$\begingroup\$

R, 35 25 23 bytes

Edit: -2 bytes thanks to pajonk

\(x)(x+.5-!x%%2-.5)%/%1

Attempt This Online!

\$\endgroup\$
2
  • \$\begingroup\$ That is very well done, comparing to my naive approach! BTW, -2 bytes. \$\endgroup\$
    – pajonk
    Commented Aug 28, 2023 at 18:48
  • \$\begingroup\$ @pajonk - Thanks! \$\endgroup\$ Commented Aug 28, 2023 at 21:25
4
\$\begingroup\$

K (ngn/k), 38 37 34 27 23 bytes

{*r-2!r*=/r:|1_:\x+0.5}

Try it online!

-1 : Shuffle
-3 : Shuffle
-7 : Rework
-4 : Wow

\$\endgroup\$
2
  • \$\begingroup\$ f:{r-(2!r:_x+0.5)*</~-/|1_:\1(2*)\x} is 34 but is @Arnauld’s idea. \$\endgroup\$
    – doug
    Commented Aug 28, 2023 at 4:17
  • \$\begingroup\$ Even shorter without my reflexive use of hooks… \$\endgroup\$
    – doug
    Commented Aug 28, 2023 at 4:40
3
\$\begingroup\$

Retina 0.8.2, 61 bytes

([02468])\.5$|\.[0-4].*
$1
\b9+\.
0$&
T`9d`d`.9*\.
\..+

-0
0

Try it online! Link includes test cases. Explanation:

([02468])\.5$|\.[0-4].*
$1

Remove the .5 after an even digit, and any decimal less than .5.

\b9+\.
0$&

If there is an all 9s integer part with a decimal part, prefix a zero.

T`9d`d`.9*\.

If there is a decimal part then increment the integer part.

\..+

-0
0

Remove any decimal part and change -0 to 0.

Retina, 46 bytes

([02468])\.5$|\.[0-4].*
$1
\d+\..+
$.(*__
-0
0

Try it online! Link includes test cases. Explanation:

([02468])\.5$|\.[0-4].*
$1

Remove the .5 after an even digit, and any decimal less than .5.

\d+\..+
$.(*__

If there is still a decimal part, remove it and increment the integer part.

-0
0

Change -0 to 0.

\$\endgroup\$
3
\$\begingroup\$

J, 22 21 bytes

<.@+&1r2([-AND)1=2|+:

Try it online!

Heavily inspired by Arnauld's approach, except checks for exact 0.5 by doubling and checking that the remainder mod 2 is exactly 1 1=2|+:.

Then subtracts the adjustment bit ANDed with the standard round from the standard round.

\$\endgroup\$
2
\$\begingroup\$

R, 57 bytes

\(x,a=x%/%1)`if`(x-a<.5,a,`if`(x-a>.5|a%%2,ceiling(x),a))

Attempt This Online!

\$\endgroup\$
2
\$\begingroup\$

K (ngn/k), 16 bytes

{_x+0.5-1=4!2*x}

Try it online!

A near-port of my own J answer.

\$\endgroup\$
2
\$\begingroup\$

The nearest-even rounding mode, aka Banker's Rounding, is the default for IEEE-754 math, so normal programs (which haven't changed the FP environment) on most ISAs including x86 can just use instructions that round a float to an integer-valued float, or convert to integer with the current rounding mode. (Most programs don't change the rounding mode away from the default.)

This is basically just using built-ins, but I didn't want to clutter up the existing answer with a bunch of machine-code stuff and associated interesting stuff I wanted to write about x86. If people want to add sections for other ISAs, feel free, although most other mainstream ISAs have fixed-width instructions so it's always either a 2 or 4-byte instruction.

For the convert-to-integer instructions, out-of-range inputs produce the most-negative 2's complement integer of whatever destination width, which Intel calls the "integer indefinite" value. (e.g. 0x80000000 for 32-bit.) For AVX-512 unsigned conversions, the "integer indefinite" value is all-ones, 0xffffffff (e.g. from vcvtss2usi.) Rounding instructions that produce a number in the same format as the input don't have this problem.

x86(-64) machine code, x87 scalar math, 3 bytes

Uses the rounding mode in the x87 control word, which defaults to round-to-nearest w. even as a tie-break. (at reset and finit, and in fresh processes under normal OSes).

Input arg in st0, top of the x87 stack, result stored to an int * pointed-to by RDI (or EDI if the same bytes execute in 32-bit mode). None of the standard calling conventions even for 32-bit mode pass FP args in x87 registers; unlike the SSE part of the answer, you couldn't write a C prototype to describe it to a C compiler.

  db 1f                   fistp  DWORD PTR [rdi]
  c3                      ret

Or to round to an integer-valued long double (modifying st0 in place), 2-byte frndint is D9 FC.

x86(-64) SSE2 or AVX scalar math, 5 bytes

These use the current rounding mode in MXCSR, the SSE math control/status register, which is fully independent of the x87 FP environment. Both use the same 2-bit codes for four different rounding modes, ceil/floor/trunc and nearest-even. (The Python 2 / C round() rounding mode is not directly available in hardware on x86, so should be avoided for performance reasons unless you specifically want it. IIRC, ARM does have it in hardware, as well as IEEE standard rounding.)

All the relevant SSE / SSE2 / AVX conversion instructions are 4 bytes long, whether encoded as legacy-SSE or AVX, except for conversion into an MMX register. SSE1 cvtps2pi mm0, xmm0 is 0f 2d c0, allowing for a total 4-byte function if you're willing to return a 32-bit signed integer in the bottom of mm0, which is a pretty inconvenient place for it and would need the caller to use longer instructions to store or do math with it.

SSE1 ps instructions are only 3 bytes, but cvtps2dq involves packed integers so was new in SSE2 along with cvtpd2dq (packed-double vs. packed-single, converting to a double-quad-word of packed integers). SSE1 scalar instructions have an extra prefix, so SSE1 cvtss2si eax, xmm0 is also 4 bytes, same as SSE2 cvtsd2si eax, xmm0. An AVX encoding of all of these is available with 2-byte VEX prefixes (plus opcode + modrm byte), again for 4-byte instructions, except there's no cvtps2pi (MMX destination.)

                          sse:       # first arg in XMM0, return in EAX
    f3 0f 2d c0             cvtss2si eax,xmm0         # SSE1
    c3                      ret

Alternatives:  # float vs. double, and scalar vs. packed.
    f2 0f 2d c0             cvtsd2si eax,xmm0         # SSE2 scalar double to i32, note same 0f 2d opcode but different prefix from SSE1 scalar.
       0f 2d c0             cvtps2pi mm0,xmm0         # SSE1 2 floats to 2 i32 in an MMX reg
    66 0f 5b c0             cvtps2dq xmm0,xmm0        # SSE2 packed conversion 4 floats to 4x i32.  cvtpd2dq (2 doubles to 2 i32) is the same size

    c5 f9 5b c0             vcvtps2dq xmm0,xmm0       # AVX encodings; different bits in the C5 ... prefix represent different combinations of prefixes.
    c5 fa 2d c0             vcvtss2si eax,xmm0
    c5 fb 2d c0             vcvtsd2si eax,xmm0      ... etc.

(There are also truncating versions of these, like cvttsd2si, to implement C (int)double rounding semantics of truncation toward 0 without having to change the rounding mode like in the bad old days of x87 before SSE3 fisttp.)

Implementing C rint[f] / nearbyint[f] (rounding to an integer-valued double [or float]) needs multiple instructions until SSE4.1 roundsd / roundps etc, which are 6-byte instructions (with register args and no REX). e.g. 66 0f 3a 0a c0 00 roundss xmm0,xmm0,0x0. (roundss and roundsd have separate opcodes, instead of being distinguished by prefixes like SSE1 addss vs. SSE2 addsd). The instruction needs an immediate to specify the rounding mode: whether to override or not, and if so, which of the 4 modes to use, and whether to suppress FP inexact (precision) exceptions. See roundsd documentation. An immediate of 0 selects rounding-control = from the immediate, with the 00 field being the nearest-even mode that's the default.

(Like the "alternatives" above, I'm showing machine code + assembly of multiple instructions. You'd only ever use one, depending on the input format (float or double, scalar vs. packed).)

66 0f 3a 08 c0 00       roundps xmm0,xmm0,0x0        # other than SSE1, the ps form isn't shorter.
66 0f 3a 0a c0 00       roundss xmm0,xmm0,0x0
66 0f 3a 0b c0 00       roundsd xmm0,xmm0,0x0

c4 e3 79 0b c0 00       vroundsd xmm0,xmm0,xmm0,0x0      # 3-byte VEX prefix needed for instructions in opcode "maps" used by SSE4 and later, i.e. with 3 prefixes such as 66 0f 3a
c4 e3 79 08 c0 00       vroundps xmm0,xmm0,0x0   ... etc.

x86 + AVX-512 not relying on the current rounding mode, 7 bytes

AVX-512 EVEX prefixes can override the rounding mode on a per-instruction basis, for any instruction with scalar or 512-bit vector width, including when vaddps or whatever have to round their result to a representable float/double. (Not 128 or 256-bit vector width until APX and AVX10.2 which finally stops caring about obsolete 32-bit mode so it can use more of the bits of the EVEX prefix, instead of having to slot in to only invalid-in-32-bit-mode encodings of some instruction.) Fun fact: out-of-order exec CPUs that support SMT (e.g. hyperthreading) already have to track the rounding mode on a per-instruction basis internally, because different logical cores could be using different rounding mode settings. So it makes some sense to expose this functionality to programmers.

AVX-512 also allows for conversion directly to unsigned integer, and to packed 64-bit integers, not just scalar-only for conversions involving 64-bit signed integers in SSE/AVX (in 64-bit mode only).

An Intel manual from 2016 when AVX-512 was newish has some details in section 2.5.4 Static Rounding and Suppress All Exceptions, where it compares to instructions like vroundps and vrndscaleps which allow this via an immediate, vs. setting MXCSR.

                         avx512:
    62 f1 ff 18 2d c0       vcvtsd2si rax,xmm0{rn-sae}
    c3                      ret

The NASM syntax I used is vcvtsd2si rax, xmm0, {rn-sae}. The disassembly is from objdump -drwC -Mintel, so is using GAS's .intel_syntax noprefix style for the rounding override.

Using an eax destination wouldn't save any machine code bytes, unlike with legacy SSE (separate REX prefix), or with AVX where vcvtsd2si rax,xmm0 needs a 3-byte VEX prefix for a total length of 5 bytes instead of 4.

\$\endgroup\$
1
\$\begingroup\$

Charcoal, 21 bytes

NθI⊟⌊E⟦⌊θ⌈θ⟧⟦↔⁻θι﹪ι²ι

Try it online! Link is to verbose version of code. Explanation: Port of @JonathanAllan's Jelly answer.

Nθ                      Input as a number
        θ               Input number
       ⌊                Floor
          θ             Input number
         ⌈              Ceiling
     E⟦    ⟧            Map over both values
               θ        Input number
                ι       Current value
             ↔⁻         Absolute difference
                  ι     Current value
                 ﹪      Modulo
                   ²    Literal integer `2`
                    ι   Current value
            ⟦           Make into list
    ⌊                   Take the minimum
   ⊟                    Pop the rounded result
  I                     Cast to string
                        Implicitly print

34 bytes for arbitrary precision:

≔⪪θ.η≔⊟ηζI⁻IΣη∧⎇﹪IΣ粬‹ζ5›ζ5∨№θ-±¹

Try it online! Link is to verbose version of code. Explanation:

≔⪪θ.η≔⊟ηζ

Separate the integer and decimal parts.

I⁻IΣη∧⎇﹪IΣ粬‹ζ5›ζ5∨№θ-±¹

Compare the decimal >= or > than .5 depending on whether the integer is odd or even, and increment the absolute value of the integer if so.

\$\endgroup\$
1
\$\begingroup\$

JavaScript (Node.js), 17 bytes

v=>v*(k=3e-324)/k

Try it online!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.