在当前语言环境下,如何确定哪个字符用作小数分隔符(小数点)或千位分隔符?

问题描述 投票:0回答:2

这个答案到

Format currency in Bash
,我想知道如何确定哪些字符用作数字分隔符。

关于区域设置和数字格式存在很多问题,例如:

printf '%.5f\n' $(bc -l <<<'4*a(1)')
3.14159

LANG=de_DE printf '%.5f\n' $(bc -l <<<'4*a(1)')
bash: printf: 3.14159265358979323844: invalid number
3,00000

二进制计算器

bc
似乎无法正确处理区域设置...

在提到的答案下,搜索小数分隔符(或基数字符),我用过这个:

int2amount() {
    local TIMEFORMAT=%U _decsep
    read _decsep < <(eval 'time true' 2>&1)
    _decsep=${_decsep//[0-9]}
    ...
}

这个工作正常:

pi() { local TIMEFORMAT=%U _decsep;read _decsep < <(eval 'time true' 2>&1);_decsep=${_decsep//[0-9]};
       local pi=$(bc -l <<<'4*a(1)')
       printf '%.5f\n' ${pi/./$_decsep}
}

pi
3.14159
LANG=de_DE pi
3,14159

但是因为千位分隔符更容易找到:

printf -v ts "%'d" 1111 ; ts=${ts//1}

没有分叉,因此系统占用空间非常轻。

所以我可以想象在源文件的开头,类似:

numericSeparators() {
    local TIMEFORMAT=%U
    read NUM_DEC_SEP < <(eval 'time true' 2>&1)
    NUM_DEC_SEP=${NUM_DEC_SEP//[0-9]}
    printf -v NUM_THO_SEP "%'d" 1111
    NUM_THO_SEP=${NUM_THO_SEP//1}
}
numericSeparators
declare -r NUM_THO_SEP NUM_DEC_SEP
...

但我认为

<(eval 'time true' 2>&1)
对于目标来说很沉重。我正在寻找一种更轻和/或更干净的方法来确定它们(甚至是decimalthousand分隔符)。


 自我回答


感谢dan的回答,我的功能会变得更简单更快!

样本正确/适应

bc
的输出:

pi() {
    local _decsep pi=$(bc -l <<<'4*a(1)')
    printf -v _decsep %.1f 1
    printf '%.5f\n' ${pi/./${_decsep:1:1}}
}
pi
3.14159
LANG=de_DE.UTF-8 pi
3,14159

一个小函数,将设置两个变量:

NUM_THO_SEP
用于千位分隔符和
NUM_DEC_SEP
用于小数分隔符:

numericSeparators() {
    local numtest
    printf -v numtest "%'.1f" 1111
    NUM_THO_SEP=${numtest:1:1}
    NUM_THO_SEP=${NUM_THO_SEP/1}
    NUM_DEC_SEP=${numtest: -2:1}
}
numericSeparators
for loctest in {C,en_US,fr_{CH,FR},de_{CH,DE},it_{CH,IT}}{,.UTF8}  ;do
    LANG=$loctest numericSeparators
    LANG=C printf 'LANG=%-12s thsnd=%-1s \e[2m(%q)\e[0m\e[45G radix=%q\n' \
        "$loctest" "$NUM_THO_SEP"{,} "$NUM_DEC_SEP"
done
LANG=C            thsnd=  ('')               radix=.
LANG=C.UTF8       thsnd=  ('')               radix=.
LANG=en_US        thsnd=, (\,)               radix=.
LANG=en_US.UTF8   thsnd=, (\,)               radix=.
LANG=fr_CH        thsnd=' (\')               radix=.
LANG=fr_CH.UTF8   thsnd=’ ($'\342\200\231')  radix=.
LANG=fr_FR        thsnd=�($'\240')          radix=\,
LANG=fr_FR.UTF8   thsnd=  ($'\342\200\257')  radix=\,
LANG=de_CH        thsnd=' (\')               radix=.
LANG=de_CH.UTF8   thsnd=’ ($'\342\200\231')  radix=.
LANG=de_DE        thsnd=. (.)                radix=\,
LANG=de_DE.UTF8   thsnd=. (.)                radix=\,
LANG=it_CH        thsnd=' (\')               radix=.
LANG=it_CH.UTF8   thsnd=’ ($'\342\200\231')  radix=.
LANG=it_IT        thsnd=. (.)                radix=\,
LANG=it_IT.UTF8   thsnd=. (.)                radix=\,

注意:由于我的终端是 UTF-8,他们无法以纯 ASCII (

NO-BREAKABLE SPACE
) 打印出
$'\240'
。这是因为它们显示了 替换字符

bash locale number-formatting
2个回答
3
投票

您可以通过以下方式获取语言环境的基数字符(小数分隔符):

printf -v ds '%#.1f' 1
ds=${ds//[0-9]}

还有千位分组分隔符,其中:

printf -v ts "%'d" 1111
ts=${ts//1}

某些语言环境(例如

C
)没有千位分隔符,在这种情况下
$ts
为空。相反,如果区域设置未定义基数字符,则 POSIX (
printf(3)
) 表示它应默认为
.
#
标志保证它将被打印。


0
投票

在绝大多数情况下,您甚至不必知道所处的区域设置即可正确解码任何值,无论您自己的区域设置如何。

因为任何底数都不可能有 2 个小数点 (RP),所以可以使用

gsub()
或类似的快速计数工具来找出
,
.
中哪一个有多个副本。

  • 如果两者都存在,那么这可能是有问题的输入。

  • 如果两者都存在,则右侧的一个必须是 RP

  • 当只有一个且不明确时,请考虑:

如果该字符右侧有 0 个或更多数字,但不是 3 位数字,则不可能是千位

更有可能的是,数千 sep 的两侧都以数字结尾,但具有前缘或后缘小数点并不罕见

只有 4 到 6 位数字(包括小数点以下,假设其仍然不明确)需要额外的上下文才能正确解码。

© www.soinside.com 2019 - 2024. All rights reserved.