11
\$\begingroup\$

Some decimal numbers cannot be precisely represented as binary floats due to the internal representation of the binary floats. For example: rounding 14.225 to two decimal digits does not result in 14.23 as one might expect but in 14.22.

Python:

In: round(14.225, 2)
Out: 14.22

Assume, however, that we have a string representation of 14.225 as '14.225', we should be able to achieve our desired rounding '14.23' as a string representation.

This approach can be generalized to arbitrary precision.

Possible Python 2/3 Solution

import sys

def round_string(string, precision):
    assert(int(precision) >= 0)
    float(string)

    decimal_point = string.find('.')
    if decimal_point == -1:
        if precision == 0:
            return string
        return string + '.' + '0' * precision

    all_decimals = string[decimal_point+1:]
    nb_missing_decimals = precision - len(all_decimals)
    if nb_missing_decimals >= 0:
        if precision == 0:
            return string[:decimal_point]
        return string + '0' * nb_missing_decimals

    if int(all_decimals[precision]) < 5:
        if precision == 0:
            return string[:decimal_point]
        return string[:decimal_point+precision+1]

    sign = '-' if string[0] == '-' else '' 
    integer_part = abs(int(string[:decimal_point]))
    if precision == 0:
        return sign + str(integer_part + 1)
    decimals = str(int(all_decimals[:precision]) + 1)
    nb_missing_decimals = precision - len(decimals)
    if nb_missing_decimals >= 0:
        return sign + str(integer_part) + '.' + '0' * nb_missing_decimals + decimals
    return sign + str(integer_part + 1) + '.' + '0' * precision

Try it online!

Usage:

     # No IEEE 754 format rounding
In:  round_string('14.225',2)
Out: '14.23'

     # Trailing zeros
In:  round_string('123.4',5)
Out: '123.40000'

In: round_string('99.9',0)
Out: '100'

    # Negative values
In: round_string('-99.9',0)
Out: '-100'

In: round_string('1',0)
Out: '1'

    # No unnecessary decimal point
In: round_string('1.',0)
Out: '1'

    # No unnecessary decimal point
In: round_string('1.0',0)
Out: '1'

In:  for i in range(8): 
         print(round_string('123456789.987654321',i))
Out: 123456790
     123456790.0
     123456789.99
     123456789.988
     123456789.9877
     123456789.98765
     123456789.987654
     123456789.9876543

Task

Input argument 1: a string containing

  • at least one digit (0, 1, 2, 3, 4, 5,6, 7, 8, 9),
  • at most one decimal point (.) which must be preceded by at least one digit,
  • an optional minus (-) as first character.

Input argument 2: a non-negative integer

Output: the correctly rounded (base 10) string

rounding = Round half away from zero

This is a . The lowest number of bytes wins!

\$\endgroup\$
32
  • \$\begingroup\$ @KevinCruijssen 1) You do not need to stick to strings in the body of your implementation and are allowed to use built-in rounding. Unfortunately (for the question) the IEEE 754 standard is a widely used standard and thus built-in rounding will not result in the desired behavior. 2) Ok, wasn't aware of the sandbox. \$\endgroup\$
    – Matthias
    Commented Mar 17, 2017 at 10:44
  • \$\begingroup\$ TI-Basic: round(A,B 5 bytes \$\endgroup\$ Commented Mar 17, 2017 at 10:58
  • 1
    \$\begingroup\$ Regarding the second input argument: 0 is not a positive integer, it is "non-negative". \$\endgroup\$ Commented Mar 17, 2017 at 11:04
  • 1
    \$\begingroup\$ I assume we add trailing zeros if needed? Could you perhaps add a test case for 123.4 & 5 --> 123.40000? Or can we assume the second input will never be larger than the amount of decimals after the point in the first input? \$\endgroup\$ Commented Mar 17, 2017 at 12:12
  • 1
    \$\begingroup\$ @Matthias Unless you can integrate the Python with the JavaScript (I've never programmed Python, and barely JS, so I honestly don't know if it's possible) no. But you could always add a Try it online link with your test code. EDIT: Also, it's usually better to wait at least a couple of days until you accept an answer. \$\endgroup\$ Commented Mar 17, 2017 at 12:37

18 Answers 18

5
\$\begingroup\$

PHP, 33 31 bytes

PHP rounds correctly too (at least on 64 bit):

printf("%.$argv[2]f",$argv[1]);

takes input from command line arguments. Run with -r.

PHP, no built-ins, 133 bytes

[,$n,$r]=$argv;if($p=strpos(_.$n,46))for($d=$n[$p+=$r],$n=substr($n,0,$p-!$r);$d>4;$n[$p]=(5+$d=$n[$p]-4)%10)$p-=$n[--$p]<"/";echo$n;

Run with -nr or test it online.

breakdown

[,$n,$r]=$argv;             // import arguments
if($p=strpos(_.$n,46))      // if number contains dot
    for($d=$n[$p+=$r],          // 1. $d= ($r+1)-th decimal 
        $n=substr($n,0,$p-!$r); // 2. cut everything behind $r-th decimal
        $d>4;                   // 3. loop while previous decimal needs increment
        $n[$p]=(5+$d=$n[$p]-4)%10   // B. $d=current digit-4, increment current digit
    )
        $p-=$n[--$p]<"/";           // A. move cursor left, skip dot
echo$n;

A null byte doesn´t work; so I have to use substr.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ You can write "%.$argv[2]f" instead of "%.{$argv[2]}f", saving 2 bytes. \$\endgroup\$ Commented Mar 18, 2017 at 2:48
4
\$\begingroup\$

Perl, 22 20 bytes

printf"%.*f",pop,pop

Using:

perl -e 'printf"%.*f",pop,pop' 123456789.987654321 3

It is Dada’s version of code. Previous:

printf"%*2\$.*f",@ARGV
\$\endgroup\$
1
  • 2
    \$\begingroup\$ printf"%.*f",pop,pop should work \$\endgroup\$
    – Dada
    Commented Mar 17, 2017 at 12:56
4
\$\begingroup\$

Ruby 2.3, 12 + 45 = 57

Uses the BigDecimal built-in, but it needs to be required before use, which is cheaper to do as a flag.

the flag: -rbigdecimal

the function:

->(s,i){BigDecimal.new(s).round(i).to_s('f')}

Ruby 2.3 by default uses ROUND_HALF_UP

\$\endgroup\$
4
\$\begingroup\$

Javascript (ES6), 44 bytes

n=>p=>(Math.round(n*10**p)/10**p).toFixed(p)

Try it online:

const f = n=>p=>(Math.round(n*10**p)/10**p).toFixed(p)

console.log(f('14.225')(2));

[...Array(8).keys()].map(i=>console.log(f('123456789.987654321')(i)))

console.log(f('123.4')(5))

\$\endgroup\$
4
\$\begingroup\$

Batch, 390 bytes

@echo off
set s=%1
set m=
if %s:~,1%==- set m=-&set s=%s:~1%
set d=%s:*.=%
if %d%==%s% (set d=)else call set s=%%s:.%d%=%%
for /l %%i in (0,1,%2)do call set d=%%d%%0
call set/ac=%%d:~%2,1%%/5
call set d=00%s%%%d:~,%2%%
set z=
:l
set/ac+=%d:~-1%
set d=%d:~,-1%
if %c%==10 set c=1&set z=%z%0&goto l
set d=%m%%d:~2%%c%%z%
if %2==0 (echo %d%)else call echo %%d:~,-%2%%.%%d:~-%2%%

Explanation. Starts by extracting the sign, if applicable. Then, splits the number into integer and fraction digits. The fraction is padded with n+1 zeros to ensure it has more than n digits. The nth (zero-indexed) digit is divided by 5, and this is the initial carry. The integer and n fraction digits are concatenated, and the carry added character by character. (The extra zeros guard against carry ripple.) After the carry stops rippling the number is reconstructed and any decimal point inserted.

\$\endgroup\$
4
\$\begingroup\$

Python, 114 105 103 96 91 89 bytes

Saved 5 bytes thanks to Kevin Cruijssen
Saved 2 bytes thanks to Krazor

from decimal import*
d=Decimal
lambda x,y:d(x).quantize(d('0.'[y>0]+'1'*y),ROUND_HALF_UP)

Try it online!

\$\endgroup\$
4
  • 1
    \$\begingroup\$ from decimal import * and removing the three d. is 4 bytes shorter. \$\endgroup\$ Commented Mar 17, 2017 at 14:13
  • 2
    \$\begingroup\$ You could also do d=Decimal and d() , which would save another 5. (Might be wrong, very sleepy) \$\endgroup\$
    – FMaz
    Commented Mar 17, 2017 at 21:11
  • \$\begingroup\$ @Krazor: Unless I did it wrong it saved me 2 bytes. Thanks! \$\endgroup\$
    – Emigna
    Commented Mar 17, 2017 at 21:15
  • \$\begingroup\$ Woops, that's what I've meant. Will leave my sleepy thoughts up anyways. \$\endgroup\$
    – FMaz
    Commented Mar 17, 2017 at 21:16
4
\$\begingroup\$

Java 7, 77 72 71 bytes

<T>T c(T n,int d){return(T)"".format("%."+d+"f",new Double(n+""));}

-1 byte thanks to @cliffroot

72-byte answer:

String c(String n,int d){return n.format("%."+d+"f",new Double(n));}

Unlike Python, Java already rounds correctly and already returns a String when you use String.format("%.2f", aDouble) with the 2 replaced with the amount of decimals you want.

EDIT/NOTE: Yes, I'm aware new Float(n) is 1 byte shorter than new Double(n), but apparently it fails for the test cases with 123456789.987654321. See this test code regarding Double vs Float.

Explanation:

<T> T c(T n, int d){               // Method with generic-T & integer parameters and generic-T return-type (generic-T will be String in this case)
  return (T)"".format("%."+d+"f",  //  Return the correctly rounded output as String
    new Double(n+""));             //  After we've converted the input String to a decimal
}                                  // End of method

Test code:

Try it here.

class M{
  static <T>T c(T n,int d){return(T)"".format("%."+d+"f",new Double(n+""));}

  public static void main(String[] a){
    System.out.println(c("14.225", 2));
    System.out.println(c("123.4", 5));
    System.out.println(c("99.9", 0));
    System.out.println(c("-99.9", 0));
    System.out.println(c("1", 0));
    System.out.println(c("1.", 0));
    System.out.println(c("1.0", 0));
    for(int i = 0; i < 8; i++){
      System.out.println(c("123456789.987654321", i));
    }
  }
}

Output:

14.23
123.40000
100
-100
1
1
1
123456790
123456790.0
123456789.99
123456789.988
123456789.9877
123456789.98765
123456789.987654
123456789.9876543
\$\endgroup\$
3
  • 1
    \$\begingroup\$ One byte shorter: <T>T c(T n,int d){return(T)"".format("%."+d+"f",new Double(n+""));} \$\endgroup\$
    – cliffroot
    Commented Mar 17, 2017 at 16:02
  • 2
    \$\begingroup\$ This solution doesn't work. Though the example is potentially a round half-even/away-0 issue, floating point errors do occur and OP has since clarified that arbitrary precision should be supported. \$\endgroup\$
    – CAD97
    Commented Mar 17, 2017 at 16:02
  • 1
    \$\begingroup\$ In fact, you fail the example cases in the question which you reproduced here: 123456789.987654321, 4 should be 123456789.9877, not 123456789.9876 \$\endgroup\$
    – CAD97
    Commented Mar 17, 2017 at 16:04
4
\$\begingroup\$

REXX, 24 bytes

arg n p
say format(n,,p)

Since REXX always uses text representation of numbers, correct rounding of numbers is free.

Try it online!

\$\endgroup\$
4
  • \$\begingroup\$ Wow. nice abuse of the language. \$\endgroup\$ Commented Mar 17, 2017 at 12:09
  • \$\begingroup\$ Does REXX have an online compiler? \$\endgroup\$ Commented Mar 17, 2017 at 12:39
  • \$\begingroup\$ It's (mainly) an interpreted language. \$\endgroup\$
    – idrougge
    Commented Mar 17, 2017 at 14:20
  • \$\begingroup\$ Try it online! \$\endgroup\$
    – idrougge
    Commented Mar 29, 2017 at 13:55
3
\$\begingroup\$

APL (Dyalog), 4 bytes

Dyalog APL uses enough internal precision.

⎕⍕⍎⍞

Try it online!

⍎⍞ execute string input

⎕⍕ get numeric input and use that as precision for formatting

\$\endgroup\$
2
  • \$\begingroup\$ Why is this 4 instead of 8 bytes? \$\endgroup\$
    – Matthias
    Commented Mar 20, 2017 at 7:52
  • \$\begingroup\$ @Matthias Because Dyalog APL has a SBCS \$\endgroup\$
    – Adám
    Commented Mar 20, 2017 at 7:53
3
\$\begingroup\$

BASH, 26 23 21 bytes

bc<<<"scale=$2;$1/1"

usage

save to round_string.sh, chmod +x round_string.sh

./round_string.sh 23456789.987654321 3

edit:no need to load library

\$\endgroup\$
2
  • \$\begingroup\$ Explanation: bc uses arbitrary precission, create a here doc with '<<<' contaning the value of scale as the second parameter and the first parameter divided by 1 to force scale interpretation. \$\endgroup\$
    – marcosm
    Commented Mar 17, 2017 at 13:57
  • 2
    \$\begingroup\$ This gives 14.22 for the input 14.225 2, and not 14.23 \$\endgroup\$ Commented Mar 17, 2017 at 18:04
3
\$\begingroup\$

AHK, 25 bytes

a=%1%
Send % Round(a,%2%)

Again I am foiled by the inability of AHK to use passed in parameters directly in functions that accept either a variable name or a number. If I replace a with 1 in the Round function, it uses the value 1. If I try %1%, it tries to use the first argument's contents as a variable name, which doesn't work. Having to set it as another variable first cost me 6 bytes.

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

TI-Basic, 53 16 bytes

TI-Basic does not use IEEE and the below method works for 0-9 (inclusive) decimal positions.

Prompt Str1,N
toString(round(expr(Str1),N

Thanks to @JulianLachniet for showing that CE calcs have the toString( command which I was not aware of (Color Edition calcs OS 5.2 or higher are required).

P.S. I did have a second line with sub(Str1,1,N+inString(Str1,". but then I realized it was useless.

\$\endgroup\$
2
  • \$\begingroup\$ How is N used? \$\endgroup\$
    – Matthias
    Commented Mar 18, 2017 at 12:54
  • \$\begingroup\$ @Matthias Thanks for catching that typo! I accidentally removed the last three bytes with my previous edit \$\endgroup\$
    – Timtech
    Commented Mar 18, 2017 at 14:02
3
\$\begingroup\$

JavaScript (ES6), 155 bytes

(s,n)=>s.replace(/(-?\d+).?(.*)/,(m,i,d)=>i+'.'+(d+'0'.repeat(++n)).slice(0,n)).replace(/([0-8]?)([.9]*?)\.?(.)$/,(m,n,c,r)=>r>4?-~n+c.replace(/9/g,0):n+c)

Explanation: The string is first normalised to contain a . and n+1 decimal digits. The trailing digit, any preceding 9s or .s, and any preceding digit, are then considered. If the last digit is less than 5 then it and any immediately preceding . are simply removed but if it is greater than 5 then the 9s are changed to 0s and the previous digit incremented (or 1 prefixed if there was no previous digit).

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

Python (2/3), 394 bytes

def rnd(s,p):
    m=s[0]=='-'and'-'or''
    if m:s=s[1:]
    d=s.find('.')
    l=len(s)
    if d<0:
        if p>0:d=l;l+=1;s+='.'
        else:return m+s
    e=(d+p+1)-l
    if e>0:return m+s+'0'*e
    o=''
    c=0
    for i in range(l-1,-1,-1):
        x=s[i]
        if i<=d+p:
            if i!=d:
                n=int(x)+c
                if n>9:n=0;c=1 
                else:c=0
                o+=str(n)
            else:
                if p>0:o+=x
        if i==d+p+1:c=int(x)>4
    if c:o+='1'
    return m+''.join(reversed(o))

Works for arbitrary precision numbers.

\$\endgroup\$
3
  • 5
    \$\begingroup\$ Hey, and welcome to PPCG! However, this isn't golfed. There's a lot of whitespace you can remove. Answers on this site are required to be golfed, sorry. \$\endgroup\$
    – Riker
    Commented Mar 17, 2017 at 14:08
  • \$\begingroup\$ Just some things (there is likely a lot more)... The function name can be one byte. The first line can use s[0]<'0' and could also use string multiplication, m='-'*(s[0]<'0'). Lines without any block statement spans can be joined together with ; (e.g. o='';c=0). Some if statements could be probably be replaced by list indexing to further reduce the need for line breaks and tabs. The final line could use a slice, o[::-1], instead of reversed(o) and the ''.join is redundant. You may well also be able to rewrite it to avoid the need for multiple return statements. \$\endgroup\$ Commented Mar 18, 2017 at 2:48
  • 2
    \$\begingroup\$ ... if you're interested there are tips for golfing in Python here. \$\endgroup\$ Commented Mar 18, 2017 at 2:50
1
\$\begingroup\$

Python 3 + SymPy, 54 bytes

from sympy import*
lambda s,p:'%.*f'%(p,S(s).round(p))

Try it online!

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

Scala, 44 bytes

(s:String,p:Int)=>s"%.${p}f"format s.toFloat

Test:

scala> var x = (s:String,p:Int)=>s"%.${p}f"format s.toFloat
x: (String, Int) => String = <function2>

scala> x("14.225",2)
res13: String = 14.23
\$\endgroup\$
1
\$\begingroup\$

Wonder, 10 bytes

@@fix#1E#0

Usage:

@@fix#1E#0

Set decimal precision and add trailing zeros if necessary.

\$\endgroup\$
2
  • \$\begingroup\$ Is there a TIO for this one? \$\endgroup\$
    – Matthias
    Commented Mar 19, 2017 at 18:00
  • \$\begingroup\$ No there isn't, but the installation is pretty easy. Make sure you have Node.js (v6+), and npm i -g wonderlang. Use the wonder command to fire up the REPL, and paste the code in. \$\endgroup\$ Commented Mar 19, 2017 at 22:18
1
\$\begingroup\$

J, 22 17 bytes

((10 j.[)]@:":".)

NB.    2    ((10 j.[)]@:":".)   '12.45678'
NB.    12.46 

Thanks to @Conor O'Brien for correcting my understanding of the rules.

t=:4 :'(10 j.x)":".y'

    NB.    Examples
    NB.    4 t'12.45678'
    NB.    12.4568
    NB.    4 t'12.456780'
    NB.    12.4568
    NB.    4 t'12.4567801'
    NB.    12.4568
    NB.    2 t'12.45678'
    NB.      12.46
    NB.    2 t'12.4567801'
    NB.      12.46
    NB.    2 (10 j.[)":". '_12.4567801'
    NB.     _12.46

format    
    x t y
where x is a digit number of decimal places required and y
is the character string containing the value to be rounded.
\$\endgroup\$
1
  • \$\begingroup\$ The challenge requires you to take the number of digits after the decimal point to round to N decimals, not N points of precision. As such, 2 t '1234.456' should give 1234.46 instead of 6 t '1234.456' \$\endgroup\$ Commented Mar 19, 2017 at 0:14

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.