Hex to Dec conversion with printf in sh fail for more than 16 digits
Asked Answered
B

3

0

I only have shell available no bash, Perl, python etc.

Using printf small numbers work:

root@DD-WRT:/jffs# printf "%d\n", 0x15a
346

But large numbers fail.

root@DD-WRT:/jffs# printf "%d\n", 0x15abc12345afda325
sh: invalid number '0x15abc12345afda325'
0

Also is it possible to perform hexadecimal arithmetic for example Module using shell ?

Biome answered 24/9, 2018 at 18:47 Comment(1)
16 hex digits is equivalent to 64 bits. Larger number needs a wider or arbitrary precision type which no shells I know support. Who needs to do arbitrary-precision math with a shell?Jamarjamb
T
1

What shell is this? On Linux I see:

$ bash -c 'echo $((0x15abc12345afda325))'
6538120775109288741

wrong

$ dash -c 'echo $((0x15abc12345afda325))'
9223372036854775807

wrong

$ ksh -c 'echo $((0x15abc12345afda325))'
2.49848648488188404e+19

right, but wrong output format

$ ksh -c 'printf "%d\n" $((0x15abc12345afda325))'
ksh: printf: warning: 2.49848648488188404e+19: overflow exception
9223372036854775807
$ ksh -c 'printf "%.0f\n" $((0x15abc12345afda325))'
24984864848818840399

GNU awk

$ gawk -v n=0x15abc12345afda325 'BEGIN {print strtonum(n)}'
24984864848818839552
$ gawk --bignum -v n=0x15abc12345afda325 'BEGIN {print strtonum(n)}'
24984864848818840357

Do you have bc available?

$ hex=15abc12345afda325
$ echo "ibase=16; $hex" | bc
(standard_in) 1: syntax error

hex values need to be in upper case?

$ echo "ibase=16; ${hex^^}" | bc
24984864848818840357

Hmm, different from ksh output. WolframAlpha says 24984864848818840357


I see busybox has dc, but sadly, it's crippled:

$ printf "%s\n" 16 i 15ABC12345AFDA325 p | dc
24984864848818840357
$ printf "%s\n" 16 i 15ABC12345AFDA325 p | busybox dc
dc: syntax error at 'i'
Trici answered 24/9, 2018 at 18:47 Comment(4)
Thanks for the reply. Its on Busy box BusyBox v1.28.4 (2018-05-31 11:07:38 CEST) built-in shell (ash) No bc is not available nor ksh ,dashBiome
Then I think you're out of luck.Trici
@HosamAmleh Looks like you'd have to provide your own printf implementation. -fno-builtin-printfAbreu
looks like there's some bug with gawk. I've checked and it produces different results in the last digits most of the time compared to bc and wolfram alpha. I don't know why you said Hmm, different from ksh output but it's actually the correct one. One is that letters inherent in hexadecimal numbers must always be upper case, because the bc software treats lower case letters as algebraic variablesJamarjamb
J
1

I've attempted to write an arbitrary-precision converter from hexadecimal to decimal in pure sh (actually ash, since busybox sh runs the built-in ash). It needs a lot more effort than bash due to the limited set of features (no arrays) and "strange" errors without clear documentation (like spaces not allowed in expressions)

#!/bin/ash

obase=1000000000    # 1e9, the largest power of 10 that fits in int32_t
ibase=$((1 << 7*4)) # only 7 hex digits, because 0xFFFFFFFF > 1e9

inp="000000${1#0x}"                 # input value in $1 with optional 0x
inp=${inp:$((${#inp}%7)):${#inp}}   # pad the string length to a multiple of 7

carry=0
# workaround, since sh and ash don't support arrays
result0=0       # output digits will be stored in resultX variables in little endian
MSDindex=0      # index of the most significant digit in the result

print_result()
{
    eval echo -n \$result$MSDindex  # print MSD
    if [ $MSDindex -gt 0 ]; then    # print remaining digits
        for i in $(seq $((MSDindex-1)) -1 0); do eval printf "%09d" \$result$i; done
    fi
    echo
}

# Multiply a digit with the result
# $1 contains the value to multiply with the result array
mul()
{
    carry=0
    for i in $(seq 0 $MSDindex); do
        eval let res="$1\\*result$i+carry"
        eval let result$i=res%obase
        let carry=res/obase
    done

    while [ $carry -ne 0 ]; do
        let MSDindex=MSDindex+1
        eval let result$MSDindex=carry%obase
        let carry=carry/obase
    done
}

# Add a digit with the result
# $1 contains the digit to add with the array
add()
{
    eval let res=$1+result0
    eval let result0=res%obase
    let carry=res/obase

    i=1
    while [ $carry -ne 0 ]
    do
        eval let res=carry+result$i
        eval let result$i=res%obase
        let carry=res/obase
        if [ $i -gt $MSDindex ]; then MSDindex=$i; fi
        let i=i+1
    done
}

# main conversion loop
while [ -n "$inp" ]     # iterate through the hex digits, 7 at a time
do
    hexdigit=${inp:0:7}
    mul $ibase          # result = result*input_base+hexdigit
    add 0x$hexdigit

    if [ ${#inp} -gt 7 ]; then
        inp=${inp: $((7-${#inp}))}
    else
        unset inp
    fi
done

print_result

I checked with busybox in my Ubuntu and saw that it supports 64-bit arithmetic, therefore I need a 32-bit limb to avoid overflow when multiply. I choose the output base as 1 000 000 000 because it's the largest power of 10 that can be represented in a 32-bit int. Then the input base need to be smaller than the base (less carry handling needed), thus I choose 0x10000000, the largest power of 16 that's smaller than 1000000000

Of course if your busybox is so crippled that it doesn't support 64-bit int then you have to use base 0x1000 and process 3 hexadecimal digits at once

Confirmed with bc, the result is the same every time

$ v=15ABC12345AFDA325; busybox sh ./hex2dec.sh $v; echo "ibase=16; $v" | bc
24984864848818840357
24984864848818840357
$ v=2B37340113436BA5C23513A1231111C; busybox sh ./hex2dec.sh $v; echo "ibase=16; $v" | bc
3590214682278754501437472025955340572
3590214682278754501437472025955340572
$ v=60431BCD73610ADF2B37340113436BA5C23513A12311111111111;\
> busybox sh ./hex2dec.sh $v; echo "ibase=16; $v" | bc
2474996796503602902399592755755761709869730986038055786310078737
2474996796503602902399592755755761709869730986038055786310078737
Jamarjamb answered 26/9, 2018 at 4:18 Comment(0)
S
0

how many digits do u need ?

printf '0x15abc12345afda965317742584595757
          515927957157574571592325FFFFFFFFFFFF' | 

gawk -nMbe '$++NF = +$_'        
       0x15abc12345afda96531774258459575751
         5927957157574571592325FFFFFFFFFFFF 

642386077699778282185783828
 049060173534486064360167379
  481875931384718910679941119
  • Technically, gawk -nM '$++NF = +$_' suffices;

    -- -b : for byte mode : to speed things up

    -- -e : just to be explicit about main code being sourced along the command line

And if you simply want the answer without the original input, even simpler :

 gawk -nM '$_+=_'
642386077699778282185783828
 049060173534486064360167379
  481875931384718910679941119
Summation answered 28/12, 2022 at 21:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.