16
\$\begingroup\$

Satisfying Rounding

You know when you're in science class, and asked to round to 2 sig figs, but your answer is 5.2501...? You should round to 5.3, but that's just so unsatisfying! By rounding to 5.3, you're off by a whole 0.05, which is a large amount compared to 0.1 (the place value you're rounding to)! So help me round in a satisfying way.

To round in a satisfying way, you must round at the first digit you come across that produces a relatively small error - less than half of the maximum error possible when rounding. Basically, you need to round whenever you encounter 0, 1, 8, or 9. If that never happens, return the input as is. Do not round on leading zeroes or ones - that just doesn't feel satisfying.

Input

A string or float value that represents a nonnegative decimal number.

Output

The same decimal number rounded satisfactorily, in either string or float format.

Examples

Input -> Output
0 -> 0
0.5 -> 0.5
0.19 -> 0
0.8 -> 1
5.64511 -> 5.645
18.913 -> 20
88.913 -> 100
36.38299 -> 36.4
621 -> 620
803.22 -> 1000
547.4726 -> 547.4726

This is a challenge, so shortest code wins!

\$\endgroup\$
10
  • \$\begingroup\$ Sandbox \$\endgroup\$
    – Quintec
    Dec 8, 2018 at 15:11
  • \$\begingroup\$ Are strings such as 036.40000 considered a valid output? \$\endgroup\$
    – Arnauld
    Dec 8, 2018 at 16:08
  • 1
    \$\begingroup\$ Can we assume that a .0 part will be given for integers? Also, 0 isn't positive. \$\endgroup\$ Dec 8, 2018 at 16:08
  • \$\begingroup\$ @EriktheOutgolfer No, you may not - also thanks, changed to nonnegative. \$\endgroup\$
    – Quintec
    Dec 8, 2018 at 17:03
  • 1
    \$\begingroup\$ So 19 rounds to 20 but 0.19 rounds to 0? Why? \$\endgroup\$
    – Neil
    Dec 8, 2018 at 19:38

7 Answers 7

2
\$\begingroup\$

JavaScript (ES6),  100 99 98  78 bytes

Takes input as a string. Returns a float.

s=>+(0+s).replace(/\d/g,(d,i)=>j&&+d+((n=s[i+!++s[i]])<2&&i?--j:n>7&&j--),j=1)

Try it online!

How?

We first prepend a leading \$0\$ to the input string, so that we're guaranteed to have a digit before a possible leading \$8\$ or \$9\$, that must trigger the rounding right away.

The flag \$j\$ is set to \$1\$ as long as we are looking for a digit on which we can do a satisfying rounding, and set to \$0\$ afterwards.

Because a leading \$0\$ was added to the string that we're walking through but \$s\$ was left unchanged, \$d\$ contains the current character and \$s[i]\$ is pointing to the next character.

We use the following code to load the next digit in \$n\$, skipping a possible decimal separator:

n = s[i + !++s[i]]

Although strings are immutable in JavaScript, the expression ++s[i] will return \$s[i]+1\$ if it contains a numeric value, even though \$s[i]\$ is not actually incremented. Therefore, the expression !++s[i] is evaluated to \$false\$ (coerced to \$0\$) for all digits (including \$0\$) and to \$true\$ (coerced to \$1\$) for the decimal separator ".".

When the rounding occurs, we yield d + --j if the next digit \$n\$ is \$0\$ or \$1\$ (and it's not the leading digit of the original input) and d + j-- if \$n\$ is \$8\$ or \$9\$. Therefore, \$j\$ is set to \$0\$ in both cases but we add \$0\$ to \$d\$ in the first case (rounding down) and \$1\$ in the second case (rounding up).

\$\endgroup\$
1
  • 1
    \$\begingroup\$ And the pinball/rubber ball falls into a ditch! :) \$\endgroup\$
    – Quintec
    Dec 8, 2018 at 20:17
2
\$\begingroup\$

Ruby, 79 77 69 67 65 bytes

->n,z=n+".0"{z[i=z=~/\./]='';n.to_f.round (z=~/(?!^)[01]|8|9/)-i}

Try it online!

Explanation

  • ->n Take input as a string
  • z=n+".0" Create a temporary string z that is guaranteed to contain a dot and a relevant digit.
  • i=z=~/\./ Determine the position of the decimal dot in z and assign to i.
  • z[i]='' Drop the dot so that it doesn't get in the way further on.
  • z=~/(?!^)[01]|8|9/ Determine the position of non-starting 0-1 or any 8-9, whichever comes first.
  • (...)-i This difference will be the number of decimal places to keep, negative if we will be rounding left of the dot.
  • n.to_f.round ... Convert to float and do the rounding.
\$\endgroup\$
1
\$\begingroup\$

Jelly, 34 bytes

;”.ḟ$µ»"”2e€⁽¡XṾ¤;1i1_i”.$_>¥0ɓVær

Try it online!

-1 thanks to Jonathan Allan.

\$\endgroup\$
4
  • \$\begingroup\$ Why ŒV? I think V will work too. \$\endgroup\$ Dec 8, 2018 at 18:15
  • \$\begingroup\$ @JonathanAllan Nope. (basically banker's rounding quirks) \$\endgroup\$ Dec 8, 2018 at 18:16
  • \$\begingroup\$ Oh, because it's not acting on the input? Try _>¥0ɓVær like mine is (I missed usage of the dyadic quick so thanks too!) \$\endgroup\$ Dec 8, 2018 at 18:24
  • \$\begingroup\$ @JonathanAllan Ah, clever usage of chains, thanks. \$\endgroup\$ Dec 8, 2018 at 18:27
1
\$\begingroup\$

Jelly,  30  29 bytes

-1 thanks to Erik the Outgolfer (use of dyadic quick ¥ from his answer)

O;0µ_8H1¦%8ỊTḢ_<48TḢƊ_>¥0ɓVær

A monadic link accepting a list of characters which yields a float.

Try it online! Or see the test-suite.

How

First note that the input string is made exclusively from the characters 0123456789. which have ordinals [48,49,50,51,52,53,54,55,56,57,46], which have remainders when divided by eight of [0,1,2,3,4,5,6,7,0,1,6]. The only characters which are between -1 and 1 inclusive are 0, 1, 8, and 9.

Furthermore if we subtract eight from the ordinals ([40,41,42,43,44,45,46,47,48,49,38]) the same (fairly obviously) holds. If we halve these ([20,20.5,21,21.5,22,22.5,23,23.5,24,24.5,19]) the only characters which have remainders when divided by eight which are between -1 and 1 inclusive are 8 and 9.

O;0µ_8H1¦%8ỊTḢ_<48TḢƊ_>¥0ɓVær - Link: list of characters, S
O                             - ordinal (vectorises across S)
 ;0                           - concatenate a zero
                              - (to cater BOTH for no '0', '1', '8', or '9' AND for no '.')
   µ                          - start a new monadic link (call that X)
    _8                        - subtract eight (vectorises across X)
        ¦                     - sparse application...
       1                      - ...to: indices: one
      H                       - ...do: halve (i.e. halve first ordinal)
         %8                   - modulo by eight (vectorises)
           Ị                  - insignificant (abs(v)<=1?) (vectorises)
            T                 - truthy indices
             Ḣ                - head
                    Ɗ         - last three links as a monad (i.e. f(X)):
               <48            -   less than 48? (i.e. was it a '.' in S or the added 0?)
                  T           -   truthy indices
                   Ḣ          -   head
              _               - subtract
                       ¥      - last two links as a dyad
                      < 0     -   less than zero? (1 if so 0 otherwise)
                     _        -   subtract
                         ɓ    - start a new dyadic chain (i.e. f(S,X))
                          V   - evaluate S as Jelly code (i.e. get S as a float)
                           ær - round to the nearest multiple of 10^(-X)
\$\endgroup\$
1
\$\begingroup\$

Retina 0.8.2, 75 bytes

^[89]
10
T`d`0`(?<=.)[01].*|(?<=8|9).*
T`89d`0d`.\.?[89]
(\.|(\..+?))0+$
$2

Try it online! Link includes test cases. Explanation:

^[89]
10

Handle the case of a leading 8 or 9.

T`d`0`(?<=.)[01].*|(?<=8|9).*

If there's a non-leading 0 or 1, then zero it and the rest of the string out. Also, if there's an 8 or 9, then leave it, but zero out the rest of the string. (But leave the decimal point unchanged in either case.)

T`89d`0d`.\.?[89]

If there's still an 8 or a 9 at this point, then zero it out, and increment the preceding digit (possibly before the decimal point).

(\.|(\..+?))0+$
$2

Delete trailing zeros if they are after a decimal point, but only delete the decimal point if there are no other digits in between.

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

C (gcc), 111 102 bytes

g(_,i,j,k)char*_;{for(i=*_<56?*_++:48,j=3;j;j&=k%8>1|(i=*_++)/48*2)putchar(j&1?i+(k=_[*_<48])/56:48);}

Try it online!

//_: input, as string
//i: current digit, rounded if applicable
//j: tracks whether number is rounded, and whether \0 or '.' has been encountered
//k: digit to test rounding (round if k is one of 0,1,8,9)
//'0'==48, '8'==56
g(_,i,j,k)char*_;{
    for(i=*_<56?*_++:48,j=3;                //special case: if first digit is 8 or 9, use a
                                            //placeholder digit with value 0. initialize j.
        j;                                  //only stop execution when number is rounded and
                                            //'.' or \0 has been encountered.
        j&=k%8>1|(i=*_++)/48*2)             //check if execution should stop.
        putchar(j&1?i+(k=_[*_<48])/56:48);  //print '0' if rounding had already been done;
                                            //otherwise, print digit. round up as needed.
}
\$\endgroup\$
0
\$\begingroup\$

C# (Visual C# Interactive Compiler), 280 bytes

c=>{int d=c.IndexOf('.');int t=c.IndexOfAny(new char[]{'8','9','0','1'},1);var m=c[0]=='8'||c[0]=='9'?1>0:0>1;var z=decimal.Parse(c);Func<decimal>q=()=>(decimal)Math.Pow(10,m?d<0?c.Length:d:d<0?c.Length-t:d>t?d-t:d-t+1);return m?Math.Round(z/q())*q():t<0?z:Math.Round(z/q())*q();}

Try it online!

It can be shorter if I used doubles instead of decimals, but I used decimals to preserve accuracy, or else a number like 547.4726 would be 547.472595214844.

C# (Visual C# Interactive Compiler), 268 bytes

c=>{int d=c.IndexOf('.');int t=c.IndexOfAny(new char[]{'8','9','0','1'},1);var m=c[0]=='8'||c[0]=='9'?1>0:0>1;var z=float.Parse(c);Func<double>q=()=>Math.Pow(10,m?d<0?c.Length:d:d<0?c.Length-t:d>t?d-t:d-t+1);return m?Math.Round(z/q())*q():t<0?z:Math.Round(z/q())*q();}

Try it online!(Less accurate version)

\$\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.