【问题标题】:Hex to Dec conversion with printf in sh fail for more than 16 digits使用 sh 中的 printf 进行十六进制到十进制转换失败超过 16 位
【发布时间】:2018-09-24 18:47:30
【问题描述】:

我只有 shell 可用,没有 bash、Perl、python 等。

使用printf 小数字有效:

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

但大量失败。

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

还可以执行十六进制算术,例如使用 shell 的模块吗?

【问题讨论】:

  • 16 个十六进制数字相当于 64 位。更大的数字需要更宽或任意精度的类型,我知道没有任何 shell 支持。谁需要使用 shell 进行任意精度的数学运算?

标签: shell hex decimal sh base-conversion


【解决方案1】:

这是什么外壳?在 Linux 上我看到了:

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

错了

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

错了

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

正确,但错误的输出格式

$ 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

您有bc 可用吗?

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

十六进制值需要大写吗?

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

嗯,与 ksh 输出不同。 WolframAlpha says 24984864848818840357


我看到busybox有dc,但很遗憾,它已经残废了:

$ printf "%s\n" 16 i 15ABC12345AFDA325 p | dc
24984864848818840357
$ printf "%s\n" 16 i 15ABC12345AFDA325 p | busybox dc
dc: syntax error at 'i'

【讨论】:

【解决方案2】:

我尝试用纯 sh(实际上是 ash,因为 busybox sh 运行内置的 ash)编写一个任意精度转换器,从十六进制到十进制。由于功能集有限(无数组)和没有明确文档的“奇怪”错误(如表达式中不允许的空格),它需要比 bash 更多的努力

#!/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

我在我的 Ubuntu 中检查了 busybox,发现它支持 64 位算术,因此我需要一个 32 位肢体来避免乘法时溢出。我选择输出基数为1 000 000 000,因为它是可以用 32 位整数表示的 10 的最大幂。那么输入基数需要小于基数(需要较少的进位处理),因此我选择 0x10000000,即小于 1000000000 的 16 的最大幂

当然,如果您的busybox 严重到不支持64 位int,那么您必须使用0x1000 基数并一次处理3 个十六进制数字

用bc确认,每次结果都一样

$ 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

【讨论】:

    猜你喜欢
    • 2011-02-19
    • 1970-01-01
    • 1970-01-01
    • 2017-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多