Round the string

10

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!

Matthias

Posted 2017-03-17T09:49:14.177

Reputation: 207

@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 – 2017-03-17T10:44:42.297

TI-Basic: round(A,B 5 bytes – Julian Lachniet – 2017-03-17T10:58:45.333

@JulianLachniet Actually no, the input number is a string so maybe round(expr(Str0,B but then you need to convert that back into a string representation for output. – Timtech – 2017-03-17T11:03:07.627

1Regarding the second input argument: 0 is not a positive integer, it is "non-negative". – Stewie Griffin – 2017-03-17T11:04:52.683

@Timtech toString(round(expr(Str0),B – Julian Lachniet – 2017-03-17T11:06:07.420

1I 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 – 2017-03-17T12:12:24.550

@KevinCruijssen Added the example (trailing zeros). Is it possible to make the Python code interactive like Javascript on StackExchange? – Matthias – 2017-03-17T12:29:33.230

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 – 2017-03-17T12:37:38.463

@KevinCruijssen Thanks never used TIO before, but very handy. – Matthias – 2017-03-17T12:57:06.017

@JonathanAllan Edit: added rounding explicitly – Matthias – 2017-03-17T13:35:42.780

Related: Round to n Sig Figs

– Neil – 2017-03-17T14:06:52.660

Several answers seem to use printf and doubles, but they'll all fail with enough digits before or after the decimal dot. Can we assume any size/precision limits for the input? – Dennis – 2017-03-17T14:42:33.470

@Dennis: No limitations are specified, so all sizes (Input argument 1) and precision (Input argument 2) sould be supported (as long as your underlying memory system does not become a bottleneck). – Matthias – 2017-03-17T15:02:26.143

Can the inputs be taken in any order? – Business Cat – 2017-03-17T15:27:06.227

For the record, base 10! is an exceedingly odd base to choose. – Jules – 2017-03-17T15:32:04.540

@BasicSunset order is as specified and fixed – Matthias – 2017-03-17T17:18:46.540

@Jules it is base 10. Since the initial solutions just used the default rounding without paying attention to the underlying binary implementation, I added an exclamation mark. But since this is confusing, I will remove it. – Matthias – 2017-03-17T17:20:25.370

Interesting, thanks for the tip @JulianLachniet, didn't notice they added that to the CE calcs. – Timtech – 2017-03-17T17:57:23.433

@Timtech Funny, because I don't have a CE myself ;). This drives my crazy. Also, what is "IEEE" – Julian Lachniet – 2017-03-17T18:50:09.467

@JulianLachniet https://en.wikipedia.org/wiki/IEEE_floating_point

– Timtech – 2017-03-17T18:51:32.237

@JulianLachniet IEEE is an organization which specifies a.o. standards such as the IEEE 754 standard for floating points which has a huge impact on hardware and software. Basically it deals with the tradeoff between the number of bits for the mantissa (impacts the precision of the floating point numbers) and exponent (impacts the range of the floating point numbers) – Matthias – 2017-03-17T18:57:40.083

Is 123.4 valid output for round_string('123.4',5)? Is 123456790. valid output for round_string('123456789.987654321',0)? – Dennis – 2017-03-18T13:09:12.243

@Dennis 1) round_string('123.4',5) = '123.40000' 2) round_string('123456789.987654321',0) = '123456790' – Matthias – 2017-03-18T13:17:35.213

You should clarify that in your post. Examples do not count as part of the specification. – Dennis – 2017-03-18T13:19:37.190

Also, since we have to support negative numbers, there should be negative number in the test cases, preferably ones that check for correct implementation of round half away from zero. – Dennis – 2017-03-18T13:21:37.027

@Dennis updated – Matthias – 2017-03-18T13:30:16.380

Were you not a bit quick on the accept trigger? – Adám – 2017-03-19T21:20:26.970

@Adám indeed. First thought I needed to manually verify the Wonder version and now there is the APL one which is even shorter. I guess we now reached the limit unless some language has a one byte built-in. – Matthias – 2017-03-20T07:50:57.960

@Matthias APL has a built-in , if you allow numeric input. Takes number as right argument and precision as left argument. – Adám – 2017-03-20T07:54:44.873

May I suggest you add some test cases? Currently my code works correctly for every single one of your test cases. But it fails on test cases like 987654321.666666666; 7, 987654321.666666666; 8, 987654321.666666666; 9. – Kevin Cruijssen – 2017-03-20T08:35:33.477

@Matthias Can we take input as a number? – Adám – 2017-03-20T10:30:37.317

@Adám only the second argument – Matthias – 2017-03-20T10:33:30.663

Answers

2

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

Adám

Posted 2017-03-17T09:49:14.177

Reputation: 37 779

Why is this 4 instead of 8 bytes? – Matthias – 2017-03-20T07:52:25.077

@Matthias Because Dyalog APL has a SBCS

– Adám – 2017-03-20T07:53:25.903

5

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

Denis Ibaev

Posted 2017-03-17T09:49:14.177

Reputation: 876

2printf"%.*f",pop,pop should work – Dada – 2017-03-17T12:56:52.517

5

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.

Titus

Posted 2017-03-17T09:49:14.177

Reputation: 13 814

1You can write "%.$argv[2]f" instead of "%.{$argv[2]}f", saving 2 bytes. – Ismael Miguel – 2017-03-18T02:48:00.343

4

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!

Emigna

Posted 2017-03-17T09:49:14.177

Reputation: 50 798

1from decimal import * and removing the three d. is 4 bytes shorter. – Kevin Cruijssen – 2017-03-17T14:13:34.650

@KevinCruijssen: Thanks! – Emigna – 2017-03-17T14:26:38.863

2You could also do d=Decimal and d(), which would save another 5. (Might be wrong, very sleepy) – FMaz – 2017-03-17T21:11:16.700

@Krazor: Unless I did it wrong it saved me 2 bytes. Thanks! – Emigna – 2017-03-17T21:15:21.703

Woops, that's what I've meant. Will leave my sleepy thoughts up anyways. – FMaz – 2017-03-17T21:16:28.597

4

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!

idrougge

Posted 2017-03-17T09:49:14.177

Reputation: 641

Wow. nice abuse of the language. – Matthew Roh – 2017-03-17T12:09:11.913

Does REXX have an online compiler? – Kevin Cruijssen – 2017-03-17T12:39:28.733

It's (mainly) an interpreted language. – idrougge – 2017-03-17T14:20:03.670

Try it online! – idrougge – 2017-03-29T13:55:37.437

4

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

SztupY

Posted 2017-03-17T09:49:14.177

Reputation: 3 639

4

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

powelles

Posted 2017-03-17T09:49:14.177

Reputation: 1 277

3

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.

Timtech

Posted 2017-03-17T09:49:14.177

Reputation: 12 038

How is N used? – Matthias – 2017-03-18T12:54:32.013

@Matthias Thanks for catching that typo! I accidentally removed the last three bytes with my previous edit – Timtech – 2017-03-18T14:02:00.577

3

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

Kevin Cruijssen

Posted 2017-03-17T09:49:14.177

Reputation: 67 575

1One byte shorter: <T>T c(T n,int d){return(T)"".format("%."+d+"f",new Double(n+""));} – cliffroot – 2017-03-17T16:02:28.893

2

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.

– CAD97 – 2017-03-17T16:02:29.210

1In fact, you fail the example cases in the question which you reproduced here: 123456789.987654321, 4 should be 123456789.9877, not 123456789.9876 – CAD97 – 2017-03-17T16:04:31.000

3

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

marcosm

Posted 2017-03-17T09:49:14.177

Reputation: 986

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. – marcosm – 2017-03-17T13:57:20.020

2This gives 14.22 for the input 14.225 2, and not 14.23 – Digital Trauma – 2017-03-17T18:04:27.150

3

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.

Engineer Toast

Posted 2017-03-17T09:49:14.177

Reputation: 5 769

3

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.

Neil

Posted 2017-03-17T09:49:14.177

Reputation: 95 035

2

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.

Amol

Posted 2017-03-17T09:49:14.177

Reputation: 21

5Hey, 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. – Rɪᴋᴇʀ – 2017-03-17T14:08:40.273

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. – Jonathan Allan – 2017-03-18T02:48:45.347

2

... if you're interested there are tips for golfing in Python here.

– Jonathan Allan – 2017-03-18T02:50:17.307

2

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

Neil

Posted 2017-03-17T09:49:14.177

Reputation: 95 035

1

Python 3 + SymPy, 54 bytes

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

Try it online!

Dennis

Posted 2017-03-17T09:49:14.177

Reputation: 196 637

1

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.

Richard Donovan

Posted 2017-03-17T09:49:14.177

Reputation: 87

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' – Conor O'Brien – 2017-03-19T00:14:19.043

1

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

2xsaiko

Posted 2017-03-17T09:49:14.177

Reputation: 699

1

Wonder, 10 bytes

@@fix#1E#0

Usage:

@@fix#1E#0

Set decimal precision and add trailing zeros if necessary.

Mama Fun Roll

Posted 2017-03-17T09:49:14.177

Reputation: 7 234

Is there a TIO for this one? – Matthias – 2017-03-19T18:00:58.717

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. – Mama Fun Roll – 2017-03-19T22:18:44.823