if [ ! "$_FLOAT_SUBR" ]; then _FLOAT_SUBR=1 # # Copyright (c) 2014-2016 Devin Teske # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # $FrauBSD: usr.sbin/bsdconfig/share/float.subr 2016-01-18 13:28:42 -0800 freebsdfrau $ # $FreeBSD$ # ############################################################ INCLUDES BSDCFG_SHARE="/usr/share/bsdconfig" . $BSDCFG_SHARE/common.subr || exit 1 ############################################################ GLOBALS # # Decimal point (locale specific) # FLOAT_DECIMAL_POINT=$( export LC_ALL LC_NUMERIC locale -k decimal_point 2> /dev/null ) case "$FLOAT_DECIMAL_POINT" in *=\"?\") FLOAT_DECIMAL_POINT="${FLOAT_DECIMAL_POINT##*=?}" FLOAT_DECIMAL_POINT="${FLOAT_DECIMAL_POINT%?}" ;; *) FLOAT_DECIMAL_POINT= esac : ${FLOAT_DECIMAL_POINT:=.} # Use C locale default as fall-back ############################################################ FUNCTIONS # f_float [OPTIONS] number OP number [var_to_set] # # Perform floating operation OP between two numbers. # # If $var_to_set is missing or NULL, output is to standard out. # f_float() { local __funcname=f_float local __f_float_np= local __f_float_round= while [ $# -gt 0 ]; do case "$1" in -n) shift 1 && __f_float_np="$1" ;; -n?*) __f_float_np="${1#-n}" ;; -r) __f_float_round=1 ;; -rn) __f_float_round=1; shift 1 && __f_float_np="$1" ;; -rn?*) __f_float_round=1; __f_float_np="${1#-rn}" ;; --) shift 1; break ;; -[0-9]*) break ;; -*) echo "$__funcname: Illegal option $1" >&2 return $FAILURE ;; *) break esac shift 1 done local __f_float_op=unknown case "$2" in +) __f_float_op=add ;; /) __f_float_op=divide ;; *) echo "$__funcname: unsupported arithmetic operator \`$2'" >&2 return $FAILURE esac eval f_float_$__f_float_op ${__f_float_np:+-n \"\$__f_float_np\"} \ ${__f_float_round:+-r} -- \"\$@\" } # f_float_add [--] float [+] float [var_to_set] # # Add two floats. # # If $var_to_set is missing or NULL, output is to standard out. # f_float_add() { local __funcname=f_float_add while [ $# -gt 0 ]; do case "$1" in --) shift 1; break ;; -[0-9]*) break ;; -*) echo "$__funcname: Illegal option $1" >&2 return $FAILURE ;; *) break esac shift 1 done local __float1="${1:-0}" __float2="${2:-0}" __var_to_set="$3" [ "$__float2" = + ] && __float2="${3:-0}" __var_to_set="$4" # # Sanitize float inputs # __float1="${__float1%%[!-0-9$FLOAT_DECIMAL_POINT]*}" __float2="${__float2%%[!-0-9$FLOAT_DECIMAL_POINT]*}" # # Parse float inputs # local __fi1 __fd1= local __fi2 __fd2= case $__float1 in *$FLOAT_DECIMAL_POINT*) __fi1=${__float1%%$FLOAT_DECIMAL_POINT*} __fd1=${__float1#*$FLOAT_DECIMAL_POINT} ;; *) __fi1=$__float1 esac while [ "$__fi1" != "${__fi1#0}" ]; do __fi1=${__fi1#0} done : ${__fi1:=0} case $__float2 in *$FLOAT_DECIMAL_POINT*) __fi2=${__float2%%$FLOAT_DECIMAL_POINT*} __fd2=${__float2#*$FLOAT_DECIMAL_POINT} ;; *) __fi2=$__float2 esac while [ "$__fi2" != "${__fi2#0}" ]; do __fi2=${__fi2#0} done : ${__fi2:=0} # # Test for overflow on integer part of float inputs # if [ $__fi1 -ne 0 ] 2> /dev/null; then : no overflow elif [ $? -gt 1 ]; then echo "$__funcname: $__float1: out of range" >&2 [ "$__var_to_set" ] && setvar "$__var_to_set" "" return $FAILURE fi if [ $__fi2 -ne 0 ] 2> /dev/null; then : no overflow elif [ $? -gt 1 ]; then echo "$__funcname: $__float2: out of range" >&2 [ "$__var_to_set" ] && setvar "$__var_to_set" "" return $FAILURE fi # # Perform integer part of floating-point calculation # local __sumi=$(( $__fi1 + $__fi2 )) __overflow= if [ $__fi1 -gt 0 ]; then [ $__fi2 -gt 0 -a $__sumi -lt 0 ] && __overflow=1 elif [ $__fi2 -lt 0 -a $__sumi -gt 0 ]; then __overflow=1 fi if [ "$__overflow" ]; then echo "$__funcname: $__float1 + $__float2:" \ "result out of range" >&2 [ "$__var_to_set" ] && setvar "$__var_to_set" "" return $FAILURE fi # # Perform decimal part of floating-point calculation # local __sumd= if [ "$__fd1" -o "$__fd2" ]; then # Remove leading [common] zeroes local __lpad=0 : ${__fd1:=0} ${__fd2:=0} while [ "$__fd1" != "${__fd1#0}" -a \ "$__fd2" != "${__fd2#0}" ] do __lpad=$(( $__lpad + 1 )) __fd1=${__fd1#0} __fd2=${__fd2#0} done : ${__fd1:=0} ${__fd2:=0} # Align parts to same order of magnitude while [ ${#__fd1} -lt ${#__fd2} ]; do __fd1=${__fd1}0; done while [ ${#__fd2} -lt ${#__fd1} ]; do __fd2=${__fd2}0; done # Remove leading zeroes while [ "$__fd1" != "${__fd1#0}" ]; do __fd1=${__fd1#0}; done while [ "$__fd2" != "${__fd2#0}" ]; do __fd2=${__fd2#0}; done : ${__fd1:=0} ${__fd2:=0} # Remove trailing zeroes while [ "$__fd1" != "${__fd1%0}" -a \ "$__fd2" != "${__fd2%0}" ] do __fd1=${__fd1%0} __fd2=${__fd2%0} done : ${__fd1:=0} ${__fd2:=0} # Test for overflow if [ $__fd1 -ne 0 ] 2> /dev/null; then : no overflow elif [ $? -gt 1 ]; then echo "$__funcname: $__float1 + $__float2:" \ "result out of range" >&2 [ "$__var_to_set" ] && setvar "$__var_to_set" "" return $FAILURE fi if [ $__fd2 -ne 0 ] 2> /dev/null; then : no overflow elif [ $? -gt 1 ]; then echo "$__funcname: $__float1 + $__float2:" \ "result out of range" >&2 [ "$__var_to_set" ] && setvar "$__var_to_set" "" return $FAILURE fi # Calculate expected radix count for sum local __len if [ ${#__fd1} -gt ${#__fd2} ]; then __len=${#__fd1} else __len=${#__fd2} fi # Sum decimal parts and test for overflow __sumd=$(( $__fd1 + $__fd2 )) if [ $__sumd -lt 0 ]; then echo "$__funcname: $__float1 + $__float2:" \ "result out of range" >&2 [ "$__var_to_set" ] && setvar "$__var_to_set" "" return $FAILURE fi # Increment integer part if new radix was generated if [ ${#__sumd} -gt $__len ]; then __lpad=$(( $__lpad - 1 )) if [ $__lpad -lt 0 ]; then __sumd=${__sumd#1} __sumi=$(( $__sumi + 1 )) fi __overflow= if [ $__fi1 -gt 0 ]; then [ $__fi2 -gt 0 -a $__sumi -lt 0 ] && __overflow=1 elif [ $__fi2 -lt 0 -a $__sumi -gt 0 ]; then __overflow=1 fi if [ "$__overflow" ]; then echo "$__funcname: $__float1 + $__float2:" \ "result out of range" >&2 [ "$__var_to_set" ] && setvar "$__var_to_set" "" return $FAILURE fi fi # Remove trailing zeroes while [ "$__sumd" != "${__sumd%0}" ]; do __sumd=${__sumd%0} done : ${__sumd:=0} # Restore leading zeroes if [ $__sumd -gt 0 ]; then while [ $__lpad -gt 0 ]; do __sumd=0$__sumd __lpad=$(( $__lpad - 1 )) done fi fi # # Combine integer and decimal parts and return result # __sum=$__sumi${__sumd:+$FLOAT_DECIMAL_POINT}$__sumd # # Return result # if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$__sum" else echo "$__sum" fi } # f_float_round_up float [var_to_set] # # Round float up by one. # f_float_round_up() { local __float="$1" __var_to_set="$2" local __fi __fd __fl __ii=1 __overflow= case "$__float" in *$FLOAT_DECIMAL_POINT*) __fi=${__float%%$FLOAT_DECIMAL_POINT*} __fd=${__float#*$FLOAT_DECIMAL_POINT} __fl=${#__fd} ;; *) __fi=$__float __fd= esac if [ "$__fd" ]; then local __lpad=0 while [ "$__fd" != "${__fd#0}" ]; do __lpad=$(( $__lpad + 1 )) __fl=$(( $__fl - 1 )) __fd=${__fd#0} done : ${__fd:=0} __fd=$(( $__fd + 1 )) if [ $__fd -lt 0 ]; then __overflow=1 else [ ${#__fd} -ne $__fl ] || __ii= while [ $__lpad -gt 0 ]; do __fd=0$__fd __lpad=$(( $__lpad - 1 )) done fi fi if [ "$__ii" -a ! "$__overflow" ]; then __fd=${__fd#1} if [ "$__fi" = "${__fi#-}" ]; then __fi=$(( $__fi + 1 )) [ $__fi -gt 0 ] || __overflow=1 else __fi=$(( $__fi - 1 )) [ $__fi -lt 0 ] || __overflow=1 fi fi if [ "$__overflow" ]; then echo "$__funcname: floating point conversion overflow" \ "(unable to round)" >&2 return $FAILURE fi __float="$__fi${__fd:+$FLOAT_DECIMAL_POINT}$__fd" if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$__float" else echo "$__float" fi } # f_float_divide [-r] [-n precision] [--] integer [/] divisor [var_to_set] # # Divide integer by divisor, producing a floating-point number with precision # decimal places. If the `-r' flag is given, round up to the nearest tenth. # # If $var_to_set is missing or NULL, output is to standard out. # f_float_divide() { local __funcname=f_float_divide __np=2 __n=1 __round= while [ $# -gt 0 ]; do case "$1" in -n) shift 1 && __np="$1" ;; -n?*) __np="${1#-n}" ;; -r) __round=1 ;; -rn) __round=1; shift 1 && __np="$1" ;; -rn?*) __round=1; __np="${1#-rn}" ;; --) shift 1; break ;; -[0-9]*) break ;; -*) echo "$__funcname: Illegal option $1" >&2 return $FAILURE ;; *) break esac shift 1 done local __remainder="${1:-0}" __divisor="${2:-1}" __var_to_set="$3" [ "$__divisor" = / ] && __divisor="${3:-1}" __var_to_set="$4" # # Currently only whole integers allowed as input (trim floats) # __remainder="${__remainder%%[!-0-9]*}" __divisor="${__divisor%%[!-0-9]*}" : ${__remainder:=0} ${__divisor:=1} if [ $__remainder -eq 0 ] 2> /dev/null; then : simply testing for integer overflow elif [ $? -eq 2 ]; then echo "$__funcname: $__remainder: out of range" >&2 [ "$__var_to_set" ] && setvar "$__var_to_set" "" return $FAILURE fi if [ $__divisor -eq 0 ] 2> /dev/null; then echo "$__funcname: $__remainder / $__divisor:" \ "division by 0" >&2 [ "$__var_to_set" ] && setvar "$__var_to_set" "" return $FAILURE elif [ $? -eq 2 ]; then echo "$__funcname: $__divisor: out of range" >&2 [ "$__var_to_set" ] && setvar "$__var_to_set" "" return $FAILURE fi # # Perform floating-point calculation # local __quotient=$(( $__remainder / $__divisor )) __floatnum if [ $__quotient -eq 0 ]; then if [ $__remainder -lt 0 -a $__divisor -ge 0 ] || [ $__divisor -lt 0 -a $__remainder -ge 0 ] then __quotient="-$__quotient" fi fi [ $__np -gt 0 ] && __quotient=$__quotient$FLOAT_DECIMAL_POINT while [ $__n -le $__np ]; do __remainder=$(( $__remainder % $__divisor * 10 )) __floatnum=$(( $__remainder / $__divisor )) __quotient=$__quotient${__floatnum#-} __n=$(( $__n + 1 )) done # # Round up if necessary # if [ "$__round" ]; then __remainder=$(( $__remainder % $__divisor * 10 )) if [ $(( $__remainder / $__divisor )) -ge 5 ]; then local __f_float_divide_float f_float_round_up $__quotient __f_float_divide_float && __quotient="$__f_float_divide_float" fi fi # # Return result # if [ "$__var_to_set" ]; then setvar "$__var_to_set" "$__quotient" else echo "$__quotient" fi } fi # ! $_FLOAT_SUBR