5

I want to round down a float to a specific number of significant digits. Very similar to this answer: https://stackoverflow.com/a/3411435/281021 but instead of the normal round() behavior, it should round down always. I can't use math.floor() because it turns the float into an int.

Basically, 0.45 should become 0.4 instead of 0.5. And 1945.01 should become 1000.0 instead of 2000.0.

Community
  • 1
  • 1
Albert
  • 3,611
  • 3
  • 28
  • 52

4 Answers4

3

Scientific representation seems to be the way to go, but numerical techniques usually end up faster for me than string techniques. You do get the occasional floating point error however...

from math import *


def roundDown(x, sigfigs=1): #towards -inf 
    exponent = floor(log10(copysign(x,1))) #we don't want to accidentally try and get an imaginary log (it won't work anyway)
    mantissa = x/10**exponent #get full precision mantissa
    # change floor here to ceil or round to round up or to zero
    mantissa = floor(mantissa * 10**(sigfigs-1)) / 10**(sigfigs-1) #round mantissa to sigfigs
    return mantissa * 10**exponent

Rounding towards zero or +inf is as easy as changing the floor to ceil or round. Another benefit of computing the mantissa and exponent numerically rather than casting to a string is that the number of sigfigs can be easily changed

Aaron
  • 10,133
  • 1
  • 24
  • 40
  • This will fail in the extreme case, e.g. `roundDown(decimal.Decimal('1.99999999999999999999999999999'))`, but I guess it works with floats. – zvone Oct 20 '16 at 18:35
  • even floats are subject to floating point error: `roundDown(15.64, 3)` -> `15.600000000000001` – Aaron Oct 20 '16 at 18:38
  • Thanks! Changing `floor` on line 5 to `ceil` to round up works, but changing it to `round` makes it round _from zero_, not _to zero_: `roundZero(2.57) == 2.6, roundZero(-2.57) == -2.6`. How to make it round to zero? – Dennis Golomazov Sep 25 '19 at 14:46
0

Use scientific notion to take the significant digit and the power, and calculate the result

def significant_1 (s):
    l = len(str(s))   ####make sure there is enough precision
    a = ('%.' + str(l) + 'E') % decimal.Decimal(s)
    #print (a)
    significate_d = a.split(".")[0]
    times = a.split("E")[1]

    result = int(significate_d) * (10 ** int(times))

    return result


print (significant_1(1999))

print (significant_1(1945.01))

print (significant_1(0.45))

output:

1000
1000
0.4
dgg32
  • 1,409
  • 1
  • 13
  • 33
0

I think it's a good and simple way:

def convert(number, interval):
    return int(number/interval)*interval

Sample outputs:

1923,1000 -> 1000
12.45,0.1 -> 12.4
Mehdi Saman Booy
  • 2,760
  • 5
  • 26
  • 32
  • 1
    In this case, the user needs to specify the interval every time. The question only needs the first significant digit and the power. – dgg32 Oct 20 '16 at 17:41
  • I think the question must be more clear. because I couldn't understand why 1945.1 is converted to 1000. and not 1900 or 1940! – Mehdi Saman Booy Oct 20 '16 at 18:37
-2
 round(number[, ndigits])

Return the floating point value number rounded to ndigits digits after the decimal point. If ndigits is omitted, it defaults to zero. The result is a floating point number. Values are rounded to the closest multiple of 10 to the power minus ndigits; if two multiples are equally close, rounding is done away from 0 (so, for example, round(0.5) is 1.0 and round(-0.5) is -1.0).

https://docs.python.org/2/library/functions.html#round

bravosierra99
  • 1,331
  • 11
  • 23