How to printf fixed number or digits in floating in bash - bash

I want to output floating numbers with exact number of digits without padding in the front, since I want to prepend $ sign
For example I read numbers:
1.2345
12.345
123.456
and I want output:
$1.23450
$12.3450
$123.456
I tried many different variations in printf but can't fix number of total digits. I also didn't find answer to this particular problem
printf "$%.7f\n" 1.2345 12.345 123.456
neither with "$%.7f\n "$%07f\n "$%6.4f\n "$%.7g\n
Do I need to get different formatting for for different magnitude of numbers ie 1, 10, 100?

How to printf fixed number or digits in floating in bash
Print first 7 characters from the printed number.
printf "$%.7s\n" "$(printf "%.6f" 1234.2345)"
Do I need to get different formatting for for different magnitude of numbers ie 1, 10, 100?
Yes, you should implement your own formatting function for custom formatting requirements. There is no such printf formatting specifier for what you want.

Here is an alternative to using floating-point formatting in Bash:
#!/usr/bin/env bash
float_numbers=(1.2345 12.345 123.456)
# Get the longest number's length
for float in "${float_numbers[#]}"; do
max_length=$((${#float} > max_length ? ${#float} : max_length))
done
for float in "${float_numbers[#]}"; do
# Generate needed padding
printf -v padding '%-*s' $((max_length - ${#float})) ''
# Print $float followed by its 0 padding
printf '$%s%s\n' "$float" "${padding// /0}"
done

Related

Weird "expr:Division by Zero" output when running through a while loop

Greeting
I am currently developing a function thaT converts each decimal to binary without using awk sed printf xxd od perl ibase, obase , bc
However, the function managed to convert into decimal to binary but for some reason, it is outputting the "expr:Division by Zero" at the end of the converted binary
I have tried to remove expr and set as a normal formula but it distributed another error so i have no choice to stuck with this since it is the closet thing that converts decimal to binary
for i in $d do #$d is the decimal
num = $d #decimal number
div = 128 #it is the power number (we should start dividing by 128)
sec = 0 #to run the loop 8 times
while [[ $seq -ne 9 ]]
do
bin=`expr $num / $div`
echo -n "$bin" # we can add the replacing x and space here
rem=`expr $num % $div` # gets the remainder
div=$(expr $div / 2) #to get the decreasing power of 2
num=$rem #next the num should be equal to the remainder
sec=$(sec + 1)
done
done
#OUTPUT
Output : 11111000expr:division by zero
Any hint will be much appreciated
There are lots of things wrong here, starting with simple syntax. If you want to convert from decimal representation to binary representation using pure bash, you can use this script:
#!/bin/bash
dec=123456 # for example
bin=
n=$dec
while ((n)); do
bin=$((n & 1))$bin
((n >>= 1))
done
echo "$dec(decimal) = ${bin:-0}(binary)"
This should work for all non-negative integers which can be represented in 63 bits (unless your bash version is very old).

Converting from decimal to hexadecimal for ELROND blockchain

I am trying to do some smart contract testing for Elrond blockchain with multiple wallets and different token amounts.
The Elrond blockchain requires some hexadecimal encodings for the Smart Contract interaction. The problem is that my hex encoding matches some conversions like in this webpage http://207.244.241.38/elrond-converters/ but some others are not:
For example here is my for loop:
for line in $file; do
MAIAR_AMOUNT=$(echo $line | awk -F "," '{print $2}')
MAIAR_AMOUNT_HEX=$(printf "%04x" $MAIAR_AMOUNT)
echo -e "$MAIAR_AMOUNT > $MAIAR_AMOUNT_HEX"
done
And here is the output
620000000000 > 905ae13800
1009000000000 > eaed162a00
2925000000000 > 2a907960200
31000000000 > 737be7600
111000000000 > 19d81d9600
The first one is the decimal value I want to convert, the second is the hexadecimal.
Now if I compare the results with http://207.244.241.38/elrond-converters/
A value like 2925000000000 is 02a907960200 not 2a907960200 like I have in my output. (notice the 0 at the beginning)
But a value like 620000000000 is matching with the website 905ae13800
Of course adding a 0 in front of %04x is not gonna help me.
Now if I go to this guy repository (link below) I can see there is a calculus made, but I don't know JavaScript/TypeScript so I don't know how to interpret it in Bash.
https://github.com/bogdan-rosianu/elrond-converters/blob/main/src/index.ts#L30
It looks the converted hex string should have even number of digits.
Then would you please try:
MAIAR_AMOUNT_HEX=$(printf "%x" "$MAIAR_AMOUNT")
(( ${#MAIAR_AMOUNT_HEX} % 2 )) && MAIAR_AMOUNT_HEX="0$MAIAR_AMOUNT_HEX"
The condition (( ${#MAIAR_AMOUNT_HEX} % 2 )) is evaluated to be true if
$MAIAR_AMOUNT_HEX has odd length. Then 0 is prepended to adjust
the length.
The 4 in %04x is how many digits to pad the output to. Use %012x to pad the output to 12 hex digits.
❯ printf '%012x' 2925000000000
02a907960200

Zero padding numbers in a Bash loop

I'm trying to make a list with a simple bash looping
I want this:
000000
000001
000002
They give me this:
0
1
2
My shell code:
countBEG="000000"
countEND="999999"
while [ $countBEG != $countEND ]
do
echo "$countBEG"
countBEG=$[$countBEG +1]
done
Change your echo to use printf, where you can specify format for left padding.
printf "%06d\n" "$countBEG"
This sets 6 as fixed length of the output, using zeros to fill empty spaces.
You're looking for:
seq -w "$countBEG" "$countEND"
The -w option does the padding.
The following command will produce the desired output (no need for the loop) :
printf '%06d\n' {1..999999}
Explanation :
{1..999999} is expanded by bash to the sequence of 1 to 999999
the format string '%06d\n' tells printf to display the number it is given as argument padded to 6 digits and followed by a linefeed
printf repeats this output if it is given more arguments than is defined in its format specification

How to make single-digit cents have a leading zero while doing modulus?

RAW_AMT=000078753603
I need amt = 787536.03
This is the code:
AMT=$(${EXPR} ${RAW_AMT} / 100).$(${EXPR} ${RAW_AMT} % 100)
but it displays as AMT = 787536.3
printf can be used to format numbers in any way you choose. In particular, printf '%02d' prints a value with two digits, padding with a zero on the left.
Writing this for bash in a robust and efficient manner (no forks, no execs, no implicit temporary files) may look like:
#!/usr/bin/env bash
shopt -s extglob # enable extglob syntax
raw_amt=000078753603 # original input value
unpadded_amt=${raw_amt##+(0)} # trim leading 0's -- otherwise a value that starts with
# 0s can be treated as octal rather than decimal.
# use a format string to control formatting of our value
printf -v amt '%d.%02d' "$(( unpadded_amt / 100 ))" "$(( unpadded_amt % 100 ))"
echo "$amt"
...or, a less efficient implementation compatible with POSIX sh:
#!/bin/sh
raw_amt=000078753603 # original input value
# remove trailing zeros. This requires a fork, but not an exec on any shell
# where expr is builtin
unpadded_amt=$(expr "$raw_amt" : '0*\([^0].*\)$')
# use a format string to control formatting of our value
amt=$(printf '%d.%02d' "$(( unpadded_amt / 100 ))" "$(( unpadded_amt % 100 ))")
echo "$amt"
using awk is a bit easier:
kent$ raw=000078753603
kent$ awk '{$0*=1;sub(/..$/,".&")}7' <<<$raw
787536.03
$0*=1 will remove the leading zeros
sub(...) will add a point before ..$
7 a non-zero number, will execute awk's default action, print the result out.

Value too great for base (error token is "09")

When running this part of my bash script am getting an error
Script
value=0
for (( t=0; t <= 4; t++ ))
do
d1=${filedates[$t]}
d2=${filedates[$t+1]}
((diff_sec=d2-d1))
SEC=$diff_sec
compare=$((${SEC}/(60*60*24)))
value=$((value+compare))
done
Output
jad.sh: line 28: ((: 10#2014-01-09: value too great for base (error token is "09")
jad.sh: line 30: /(60*60*24): syntax error: operand expected (error token is "/(60*60*24)")
d1 and d2 are dates in that form 2014-01-09 and 2014-01-10
Any solution please?
Prepend the string "10#" to the front of your variables. That forces bash to treat them as decimal, even though the leading zero would normally make them octal.
What are d1 and d2? Are they dates or seconds?
Generally, this error occurs if you are trying to do arithmetic with numbers containing a zero-prefix e.g. 09.
Example:
$ echo $((09+1))
-bash: 09: value too great for base (error token is "09")
In order to perform arithmetic with 0-prefixed numbers you need to tell bash to use base-10 by specifying 10#:
$ echo $((10#09+1))
10
As others have said, the error results from Bash interpreting digit sequences with leading zeros as octal numbers. If you have control over the process creating the date values and you're using date, you can prefix the output format string with a hyphen to remove leading zero padding.
Without prefixing date format with hyphen:
$ (( $(date --date='9:00' +%H) > 10 )) && echo true || echo oops
-bash: ((: 09: value too great for base (error token is "09")
oops
With prefixing date format with hyphen:
$ (( $(date --date='9:00' +%-H) > 10 )) && echo true || echo oops
true
From the date man page:
By default, date pads numeric fields with zeroes. The following
optional flags may follow '%':
- (hyphen) do not pad the field
d1 and d2 are dates in that form 2014-01-09 and 2014-01-10
and then
((diff_sec=d2-d1))
What do you expect to get? ((diffsec=2014-01-09-2014-01-10)) ??
You need to convert the dates to seconds first:
d1=$( date -d "${filedates[$t]}" +%s )
d2=$( date -d "${filedates[$t+1]}" +%s )
(( compare = (d2 - d1) / (60*60*24) ))
(( value += compare ))
Posting some tips here related to the title of this question, but not directly related to the details of the original question. I realize that's a bit controversial action on Stack Overflow, however these related questions:
convert octal to decimal in bash [duplicate]
Value too great for base (error token is "08") [duplicate]
point to this one, and yet they are closed and hence, I could not post this answer there. Therefore, this seemed like a logical place (at least to me) to post this information that may help others in a similar situation, especially new-to-BaSH programmers.
An alternative approach to ensuring a number is treated as a 10-base integer is to use printf. This command instructs printf to treat $num as an integer and round it to 0 decimal places.
num="$(printf "%.0f" "$num")"
Or, if you want to also ensure there are no non-numeric characters in the string, you can do this:
num="$(printf "%.0f" "${num//[!0-9]/}")"
Both commands will strip out leading zeroes and round decimal values to the nearest whole number. Note the first (simpler) solution works with negative numbers, but the second does not (it will always return absolute value).
Note that printf rounds down, meaning .01 to 0.5 is rounded down to 0, while .51 to .99 is rounded up to 1. Basically, the difference between rounding up versus down in this case is that printf rounds down 0.5 and any below. I mention this because 0.5 rounded up is a more common practice.
Now, addressing the OP's specific scenario.... Combining printf with awk allows arithmetic expressions not possible with printf alone.
This
compare=$((${SEC}/(606024)))
could be alternatively be expressed as
compare=$(awk -v sec=$SEC 'BEGIN { print int(sec/(60*60*24))}')
or
compare="$(printf "%.0f" "$(awk "BEGIN { print ( $SEC / ( 60 * 60 * 24 ) ) }")")"
Meanwhile,
value=$((value+compare))
Could be calculated as
value="$(printf "%.0f" "$(awk "BEGIN { print ( $value + $compare ) }")")"
You don't need the $ and the {} in an arithmetic expansion expression. It should look like this:
compare=$((SEC/(60*60*24)))
For 'mm' and 'dd' values in dates, I use this trick:
mm="1${date:5,2}" # where 5 is the offset to mm in the date
let mm=$mm-100 # turn 108 into 8, and 109 into 9

Resources