Programming Puzzles & Code Golf Stack Exchange is a question and answer site for programming puzzle enthusiasts and code golfers. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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.

Python 2, 1129 bytes

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:

In:  round_string('14.225',2)
Out: '14.23'

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

In:  round_string('123.4',5)
Out: '123.40000'

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!

share|improve this question
2  
Also, for future challenges: Post it in the Sandbox of Proposed Challenges to get feedback and make adjustments if needed, before posting it on the main site. Once again welcome, and enjoy your stay. :) – Kevin Cruijssen 14 hours ago
    
@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. – Matthias 14 hours ago
1  
Regarding the second input argument: 0 is not a positive integer, it is "non-negative". – Stewie Griffin 14 hours ago
1  
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? – Kevin Cruijssen 13 hours ago
1  
@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. – Kevin Cruijssen 12 hours ago

12 Answers 12

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

share|improve this answer
    
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. – Marcos M 11 hours ago
    
This gives 14.22 for the input 14.225 2, and not 14.23 – Digital Trauma 7 hours ago

PHP, 33 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.

share|improve this answer

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
share|improve this answer
1  
printf"%.*f",pop,pop should work – Dada 12 hours ago

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

share|improve this answer

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!

share|improve this answer
1  
from decimal import * and removing the three d. is 4 bytes shorter. – Kevin Cruijssen 11 hours ago
    
@KevinCruijssen: Thanks! – Emigna 10 hours ago
    
You could also do d=Decimal and d() , which would save another 5. (Might be wrong, very sleepy) – Krazor 4 hours ago
    
@Krazor: Unless I did it wrong it saved me 2 bytes. Thanks! – Emigna 4 hours ago
    
Woops, that's what I've meant. Will leave my sleepy thoughts up anyways. – Krazor 4 hours ago

REXX, 24 bytes

arg n p
say format(n,,p)

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

share|improve this answer
    
Wow. nice abuse of the language. – Matthew Roh 13 hours ago
    
Does REXX have an online compiler? – Kevin Cruijssen 12 hours ago
    
It's (mainly) an interpreted language. – idrougge 10 hours ago

TI-Basic, 53 13 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

Thanks to @JulianLachniet for showing that CE calcs have the toString( command which I was not aware of (CD 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.

share|improve this answer

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.

share|improve this answer

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.

share|improve this answer
4  
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. – Riker 11 hours ago
    
@Riker, thanks, golfed. – Amol 10 hours ago

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))

share|improve this answer

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.

share|improve this answer

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 it and any immediately preceding . is 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).

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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