Sample Header Ad - 728x90

Replicating floating point arithmetic WITHOUT bc utility

2 votes
3 answers
1836 views
I'm needing to write a script on some embedded Linux systems that don't have bc installed on them and won't be able to have it installed. The script I'm writing is essentially a monitoring script that takes certain corrective actions at different load values. For example, at load average 1.5, do something. I am wondering if there's a simple way to take the load average variable and just multiply it by 100 _or_ simply move the decimal place to the right two spaces and pad with 0s if necessary, that would then make this integer arithmetic and normal bash (()) arithmetic expansion could take over. Right now, I'm breaking the floating point number its truncated integer and the decimal as a whole number(e.g. 1.5, LOAD1_INT=1, LOAD1_DECIMAL=50) but would like to simplify it if possible. Current (complicated) version:
CRIT_LOAD=3.5
if [[ $CRIT_LOAD =~ ^[0-9]{1,2}\.[0-9]{1,2}$ ]]; then
    # Since bash can't handle floating point arithmetic, break $CRIT_LOAD float into 2 separate integers
    CRIT_LOAD_INT=$(echo $CRIT_LOAD | cut -d'.' -f1)
    CRIT_LOAD_DECIMAL=$(echo $CRIT_LOAD | cut -d'.' -f2)
elif [[ $CRIT_LOAD =~ ^[0-9]{1,2}$ ]]; then
    # If $CRIT_LOAD is already an int, update variables so Monitor code works unchanged
    CRIT_LOAD_INT=$CRIT_LOAD
    CRIT_LOAD_DECIMAL=0
else
    # Set a default value of 1.0 if we can't parse CRIT_LOAD value
    CRIT_LOAD_INT=1
    CRIT_LOAD_DECIMAL=0
fi
LOAD1=$(cat /proc/loadavg | cut -d' ' -f1)
LOAD1_INT=$(echo $LOAD1 | cut -d'.' -f1)
LOAD1_DECIMAL=$(echo $LOAD1 | cut -d'.' -f2)

# Current load int is already higher than critical threshold int
if (( LOAD1_INT > CRIT_LOAD_INT )); then
    log "CRITICAL: Load values have exceeded threshold."
elif (( LOAD1_INT == CRIT_LOAD_INT )); then
    # If current load int is same as crit threshold int, compare decimals
    if (( LOAD1_DECIMAL > CRIT_LOAD_DECIMAL )); then
        log "CRITICAL: Load values have exceeded threshold."
    fi
fi
Is there a way to just pare down all this code by simply turning loadavg (e.g. 1.50) into an int (e.g. 150)? Again, _without_ using the bc utility as it't not available on these systems. **EDIT:** I wound up taking the printf command @ilkkachu suggested and modified it into a function for use in my code. I chose this route over the awk command as there are other places in this code where calling a function to emulate floating point arithmetic simplifies code readability and re-usability. Marking his answer as solution.
function dec_to_int() {
    DECIMAL=$1
    SCALE_FACTOR=$2
    # printf removes decimal and allows $SCALE_FACTOR additional spaces to be included, 0-pads numbers that would be too small otherwise
    # NOTE: printf will round number if the values it keeps are greater than the scale factor
    # e.g. SCALE_FACTOR=2, 1.759 -> 176
    SCALED_INT=$(printf "%.0f\n" "${DECIMAL}e${SCALE_FACTOR}")
    echo $SCALED_INT
}

LOAD1=$(cat /proc/loadavg | cut -d' ' -f1)
LOAD1=$(dec_to_int $LOAD1 2)
Asked by William (43 rep)
May 16, 2022, 01:26 PM
Last activity: Sep 4, 2022, 05:57 AM