10
\$\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\$
  • \$\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 Mar 17 '17 at 10:44
  • \$\begingroup\$ TI-Basic: round(A,B 5 bytes \$\endgroup\$ – Julian Lachniet Mar 17 '17 at 10:58
  • 1
    \$\begingroup\$ Regarding the second input argument: 0 is not a positive integer, it is "non-negative". \$\endgroup\$ – Stewie Griffin Mar 17 '17 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\$ – Kevin Cruijssen Mar 17 '17 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\$ – Kevin Cruijssen Mar 17 '17 at 12:37

18 Answers 18

2
\$\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\$
  • \$\begingroup\$ Why is this 4 instead of 8 bytes? \$\endgroup\$ – Matthias Mar 20 '17 at 7:52
  • \$\begingroup\$ @Matthias Because Dyalog APL has a SBCS \$\endgroup\$ – Adám Mar 20 '17 at 7:53
5
\$\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\$
  • 2
    \$\begingroup\$ printf"%.*f",pop,pop should work \$\endgroup\$ – Dada Mar 17 '17 at 12:56
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
    \$\begingroup\$ You can write "%.$argv[2]f" instead of "%.{$argv[2]}f", saving 2 bytes. \$\endgroup\$ – Ismael Miguel Mar 18 '17 at 2:48
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\$

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\$
  • 1
    \$\begingroup\$ from decimal import * and removing the three d. is 4 bytes shorter. \$\endgroup\$ – Kevin Cruijssen Mar 17 '17 at 14:13
  • \$\begingroup\$ @KevinCruijssen: Thanks! \$\endgroup\$ – Emigna Mar 17 '17 at 14:26
  • 2
    \$\begingroup\$ You could also do d=Decimal and d() , which would save another 5. (Might be wrong, very sleepy) \$\endgroup\$ – FMaz Mar 17 '17 at 21:11
  • \$\begingroup\$ @Krazor: Unless I did it wrong it saved me 2 bytes. Thanks! \$\endgroup\$ – Emigna Mar 17 '17 at 21:15
  • \$\begingroup\$ Woops, that's what I've meant. Will leave my sleepy thoughts up anyways. \$\endgroup\$ – FMaz Mar 17 '17 at 21:16
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\$
  • \$\begingroup\$ Wow. nice abuse of the language. \$\endgroup\$ – lol Mar 17 '17 at 12:09
  • \$\begingroup\$ Does REXX have an online compiler? \$\endgroup\$ – Kevin Cruijssen Mar 17 '17 at 12:39
  • \$\begingroup\$ It's (mainly) an interpreted language. \$\endgroup\$ – idrougge Mar 17 '17 at 14:20
  • \$\begingroup\$ Try it online! \$\endgroup\$ – idrougge Mar 29 '17 at 13:55
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\$
  • \$\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 Mar 17 '17 at 13:57
  • 2
    \$\begingroup\$ This gives 14.22 for the input 14.225 2, and not 14.23 \$\endgroup\$ – Digital Trauma Mar 17 '17 at 18:04
3
\$\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\$
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\$

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\$
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\$
  • \$\begingroup\$ How is N used? \$\endgroup\$ – Matthias Mar 18 '17 at 12:54
  • \$\begingroup\$ @Matthias Thanks for catching that typo! I accidentally removed the last three bytes with my previous edit \$\endgroup\$ – Timtech Mar 18 '17 at 14:02
3
\$\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\$
  • 1
    \$\begingroup\$ One byte shorter: <T>T c(T n,int d){return(T)"".format("%."+d+"f",new Double(n+""));} \$\endgroup\$ – cliffroot Mar 17 '17 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 Mar 17 '17 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 Mar 17 '17 at 16:04
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\$
  • 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\$ – Rɪᴋᴇʀ Mar 17 '17 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\$ – Jonathan Allan Mar 18 '17 at 2:48
  • 2
    \$\begingroup\$ ... if you're interested there are tips for golfing in Python here. \$\endgroup\$ – Jonathan Allan Mar 18 '17 at 2:50
2
\$\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\$
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\$
  • \$\begingroup\$ Is there a TIO for this one? \$\endgroup\$ – Matthias Mar 19 '17 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\$ – Mama Fun Roll Mar 19 '17 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\$
  • \$\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\$ – Conor O'Brien Mar 19 '17 at 0:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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