Home > Software design >  Python: How to round numbers smaller than 1 adaptively with specified precision?
Python: How to round numbers smaller than 1 adaptively with specified precision?

Time:12-16

I usually use

x = round(x, 3)

to round a number to the precision of 3 digits. Now I have this array:

[-1.10882605e-04 -2.01874994e-05  3.24209095e-05 -1.56917988e-05
 -4.61406358e-05  1.99080610e-05  7.04079594e-05  2.64600122e-05
 -3.53022316e-05  1.50542793e-05]

And using the same code just flattens everything down to 0. What I would like to have though is a function that gives me the most significant 3 digits rounded like it usually works for numbers larger than 1. Like this:

special_round(0.00034567, 3)
=
0.000346

Any idea how this could be done? Thanks!

CodePudding user response:

Here is a solution that figures out the order of magnitude and does an elment wise rounding.

Note that this will only work correctly for values < 1 and > -1, which I guess is a valid assumption regarding your example data.

import numpy as np
a = np.array([-1.10882605e-04, -2.01874994e-05,  3.24209095e-05, -1.56917988e-05,
 -4.61406358e-05,  1.99080610e-05,  7.04079594e-05 , 2.64600122e-05,
 -3.53022316e-05 , 1.50542793e-05])

def special_round(vec):
  exponents = np.floor(np.log10(np.abs(vec))).astype(int)
  return np.stack([np.round(v, decimals=-e 3) for v, e in zip(vec, exponents)])

b = special_round(a)
>>> array([-1.109e-04, -2.019e-05,  3.242e-05, -1.569e-05, -4.614e-05,
        1.991e-05,  7.041e-05,  2.646e-05, -3.530e-05,  1.505e-05])

CodePudding user response:

Problem is, numbers you provided are starting to be so small that you are approaching limit of floating point precision, thus some artifacts show up seemingly for no reason.

def special_round(number, precision):
    negative = number < 0
    number = abs(number)
    i = 0
    while number <= 1 or number >= 10:
        if number <= 1:
            i  = 1
            number *= 10
        else:
            i  = -1
            number /= 10
    rounded = round(number, precision)
    if negative:
        rounded = -rounded
    return rounded * (10 ** -i)

Output:

[-0.0001109, -2.019e-05, 3.2420000000000005e-05, -1.569e-05, -4.614e-05, 1.9910000000000004e-05, 7.041000000000001e-05, 2.646e-05, -3.5300000000000004e-05, 1.505e-05]

CodePudding user response:

You can do so by creating a specific function using the math package:

from math import log10 , floor
import numpy as np


def round_it(x, sig):
    return round(x, sig-int(floor(log10(abs(x))))-1)

a = np.array([-1.10882605e-04, -2.01874994e-05,  3.24209095e-05, -1.56917988e-05,
              -4.61406358e-05,  1.99080610e-05,  7.04079594e-05,  2.64600122e-05,
              -3.53022316e-05,  1.50542793e-05])
round_it_np = np.vectorize(round_it)  # vectorize the function to apply on numpy array
round_it_np(a, 3)  # 3 is rounding with 3 significant digits

This will result in

array([-1.11e-04, -2.02e-05,  3.24e-05, -1.57e-05, -4.61e-05,  1.99e-05,
    7.04e-05,  2.65e-05, -3.53e-05,  1.51e-05])

CodePudding user response:

Here is a solution:

from math import log10, ceil

def special_round(x, n) :
    lx = log10(abs(x))
    if lx >= 0 : return round(x, n)
    return round(x, n-ceil(lx))

for x in [10.23456, 1.23456, 0.23456, 0.023456, 0.0023456] :
    print (x, special_round(x, 3))
    print (-x, special_round(-x, 3))

Output:

10.23456 10.235
-10.23456 -10.235
1.23456 1.235
-1.23456 -1.235
0.23456 0.235
-0.23456 -0.235
0.023456 0.0235
-0.023456 -0.0235
0.0023456 0.00235
-0.0023456 -0.00235

CodePudding user response:

You can use the common logarithm (provided by the built-in math module) to calculate the position of the first significant digit in your number (with 2 representing the hundreds, 1 representing the tens, 0 representing the ones, -1 representing the 0.x, -2 representing the 0.0x and so on...). Knowing the position of the first significant digit, you can use it to properly round the number.

import math

def special_round(n, significant_digits=0):
    first_significant_digit = math.ceil((math.log10(abs(n))))
    round_digits = significant_digits - first_significant_digit
    return round(n, round_digits)

>>> special_round(0.00034567, 3)
>>> 0.000346
  • Related