Applescript之谜:一个科学记数法转换子程序
Applescript mystery: A scientific notation conversion subroutine
我手上有点神秘。我找到了一个子程序 (http://macscripter.net/viewtopic.php?id=27520) 将一个科学数字转换成一串数字。但是,无论我如何尝试,它似乎都会删除剩余的数字。
“1.23456789E+4”应该变成“12345.6789”。相反,它只是 returns“12345”。
试试 运行 下面的代码,您就会明白我的意思了。我调用一个对话框来公开结果:
set xx to 1.23456789E+4
set yy to number_to_string(xx)
display dialog yy
on number_to_string(this_number)
set this_number to this_number as string
set deci to character 2 of (0.5 as text)
set x to the offset of deci in this_number
set z to the offset of "E" in this_number
if this_number contains "E+" then
set y to the offset of "+" in this_number
set the decimal_adjust to characters (y - (length of this_number)) thru ¬
-1 of this_number as string as number
if x is not 0 then
set the first_part to characters 1 thru (x - 1) of this_number as string
else
set the first_part to ""
end if
set the second_part to characters (x + 1) thru (z - 1) of this_number as string
set the converted_number to the first_part
repeat with i from 1 to the decimal_adjust
try
set the converted_number to ¬
the converted_number & character i of the second_part
on error
set the converted_number to the converted_number & "0"
end try
end repeat
return the converted_number
else
if this_number contains "E-" then
set y to the offset of "-" in this_number
if x is not 0 then
set the first_part to text 1 thru (x - 1) of this_number
else
set the first_part to ""
end if
set the second_part to text (x + 1) thru (z - 1) of this_number
set the converted_number to the first_part & second_part
set n to text (y + 1) thru -1 of this_number as number
set zero to "0."
if n > 1 then
repeat (n - 1) times
set zero to zero & "0"
end repeat
end if
set converted_number to zero & converted_number
else
set converted_number to this_number
end if
end if
return converted_number
end number_to_string
至于为什么你的 AppleScript 代码不起作用:
你的代码只关注指数,而不关注尾数[的位数=141=](指数前的小数部分)。
因此,以1.23456789E+4
为输入,严格提取尾数的4位组成结果,不管尾数有多少位:1
和 2345678
的 前 4 位数字 ,得到 12345
.
在 AppleScript 中做到这一点很重要,所以 我建议使用 do shell script
和 shell 命令,使用 bc
, a POSIX arbitrary-precision calculation utility,这将得到你快多了:
set xx to "1.23456789E+4" # define as *string* to avoid rounding errors
# Perform transformation via handler defined below.
set yy to my toDecFraction(xx)
display alert yy
on toDecFraction(numStr)
local maxDecPlaces
# For *negative* exponents: set the maximum number of decimal places in the result.
# For *positive* exponents: the number of decimal places in the result is
# automatically chosen to accommodate all digits.
# In either case: the max. number of decimal places supported is 2,147,483,647.
set maxDecPlaces to 32
do shell script "{ printf 'scale=" & maxDecPlaces & ¬
"; '; sed -E 's/[eE]\+?/*10^/g' <<<" & quoted form of (numStr as text) & "; } |
bc | tr -d '\n\' |
sed -E -e '/\./!b' -e 's/\.0+$//;t' -e 's/0+$//; s/^(-?)(\.)/\10\2/'"
end toDecFraction
printf 'scale=<n>;'
,当通过管道传输到 bc
时,指示它在 negative 的情况下使用 <n>
小数位的精度指数;如果指数是 positive,bc
会自动选择保留所有数字的精度。
- 小数位数的上限是假设的
2,147,483,647
(!) (2^32/2-1
),但请注意,您为 maxDecPlaces
选择的数字越高(在在负指数的情况下)或输入的小数位数越多(在正指数的情况下),转换所需的时间越长,尽管 实际上,在性能上几乎没有差异,比如, 32 对 200(!) 个小数位。请注意,如果限制太低,则会发生截断,而不是舍入。
- 可以计算精确保留所有数字所需的小数位数,但它需要非平凡的词法分析, 所以选择一个足够高的上限是一种务实的妥协。
sed -E 's/[eE]\+?/*10^/g''
将科学计数法重新格式化为 bc
可以求值的完全等价的算术表达式;例如。:
1e2
-> 1*10^2
.3e+1
-> .3*10^1
2.5e-2
-> 2.5*10^-2
- 将该表达式传递给
bc
只是将其结果 打印为小数部分 ,小数位数与输入所暗示的一样多(在正指数的情况下) ,或通过变量指定 scale
(在负指数的情况下)
tr -d '\n\'
需要删除 \
个字符。以及 bc
在输出长度超过 70 个字符的数字时插入的换行符。
sed -E -e '/\./!b' -e 's/\.0+$//;t' -e 's/0+$//; s/^(-?)(\.)/\10\2/'
清理结果从结果中删除尾随零(如果没有保留小数位,还删除小数点),前置 0
,如果(的绝对值)结果是 < 1.
注:
- 如果结果的整数部分是
0
,它是打印,所以,例如,1e-2
打印为 0.01
,这在 AppleScript 中很正常 - 而不是 .01
。
- 如果您不想要前导
0
,请将上面代码中的 -e 's/0+$//; s/^(-?)(\.)/\10\2/'
替换为 -e 's/0+$//'
。
bc
是设计使然 不是 语言环境感知 ,因此 基数字符 ("decimal point") 它期望输入并产生输出是 always .
为了比较,这里有一个使用 纯 bash
代码执行转换的处理程序 词法 - 你可以看,滚动自己的转换的努力是不平凡的 - 并且在 AppleScript 中会更加冗长。
实际上,这两种方法的表现大致相同。
此解决方案的优点是小数位数没有限制,所有数字都会自动保留并且无法识别的数字字符串会可靠地引发错误.
set xx to "1.23456789E+4" # define as *string* to avoid rounding errors
# Perform transformation via handler defined below.
set yy to my toDecFraction(xx)
display alert yy
# SYNOPSIS
# toDecFraction(numString)
# DESCRIPTION
# Textually reformats the specified number string from decimal exponential (scientific) notation
# (e.g., 1.234e+2) to a decimal fraction (e.g., 123.4).
# Leading and trailing whitespace is acceptable.
# Input that is in integer form or already a decimal fraction is accepted, and echoed *unmodified*.
# No fractional part is output if there is none; e.g., '1.2e1' results in '12'.
# Numbers with an integer part of 0 are output with the leading zero (e.g. '0.1', not '.1')
# Unrecognized number strings result in an error.
# There is no limit on the number of decimal places and there are no rounding errors, given that
# the transformation is purely *lexical*.
# NOTE: This function is NOT locale-aware: a '.' must always be used as the radix character.
# EXAMPLES
# my toDecFraction('1.234567e+2') # -> '123.4567'
# my toDecFraction(toDecFraction '+1e-3') # -> '0.001'
# my toDecFraction('-1.23e+3') # -> '-1230'
# my toDecFraction ('1e-1') # -> '0.01'
on toDecFraction(numStr)
try
do shell script "
toDecFraction() {
local numStr leadingZero sign intPart fractPart expSign exponent allDigits intDigitCount intDigits fractDigits padCount result
{ [[ == '--' ]] && shift; } || { [[ == '-z' ]] && { leadingZero=1; shift; } }
read -r numStr <<<\"\" # trim leading and trailing whitespace
# Parse into constituent parts and fail, if not recognized as decimal integer / exponential notation.
[[ $numStr =~ ^([+-]?)([[:digit:]]+)?\.?(([[:digit:]]+)?([eE]([+-]?)([[:digit:]]+))?)?$ ]] || return 1
sign=${BASH_REMATCH[1]} intPart=${BASH_REMATCH[2]}
fractPart=${BASH_REMATCH[4]} expSign=${BASH_REMATCH[6]} exponent=${BASH_REMATCH[7]}
# If there's neither an integer nor a fractional part, fail.
[[ -n $intPart || -n $fractPart ]] || return 1
# debugging: echo \"[$sign][$intPart].[$fractPart]e[$expSign][$exponent]\"
# If there's no exponent involved, output the number as is
# (It is either an integer or already a decimal fraction.)
[[ -n $exponent ]] || { echo \"\"; return 0; }
allDigits=${intPart}${fractPart}
# Calculate the number of integer digits in the resulting decimal fraction,
# after resolving the exponent.
intDigitCount=$(( ${#intPart} + ${expSign}${exponent} ))
# If the sign was an explicit +, set it to the empty string - we don't want to output it.
[[ $sign == '+' ]] && sign=''
if (( intDigitCount > 0 )); then # at least 1 integer digit
intDigits=${allDigits:0:intDigitCount}
padCount=$(( intDigitCount - ${#intDigits} ))
(( padCount > 0 )) && intDigits=${intDigits}$(printf \"%${padCount}s\" | tr ' ' '0')
fractDigits=${allDigits:intDigitCount} # determine what goes after the radix character
result=${sign}${intDigits}${fractDigits:+.}${fractDigits}
# Remove leading zeros, if any.
[[ $result =~ ^0+([^0].*)?$ ]] && result=\"${BASH_REMATCH[1]}\"
else # result is < 1
padCount=$(( -intDigitCount ))
result=${sign}${leadingZero:+0}.$(printf \"%${padCount}s\" | tr ' ' '0')${intPart}${fractPart}
fi
# Trim an empty fractional part, and ensure that if
# the result is empty, '0' is output.
[[ $result =~ ^([^.]*)\.0+$ ]] && result=\"${BASH_REMATCH[1]}\"
printf '%s\n' \"${result:-0}\"
}
toDecFraction -z " & quoted form of (numStr as text)
on error number errNum
error "Not recognized as a number: " & (numStr as text) number (500 + errNum)
end try
end toDecFraction
这是 嵌入的 bash
函数,具有正确的语法突出显示:
toDecFraction() {
local numStr leadingZero sign intPart fractPart expSign exponent allDigits intDigitCount intDigits fractDigits padCount result
{ [[ == '--' ]] && shift; } || { [[ == '-z' ]] && { leadingZero=1; shift; } }
read -r numStr <<<"" # trim leading and trailing whitespace
# Parse into constituent parts and fail, if not recognized as decimal integer / exponential notation.
[[ $numStr =~ ^([+-]?)([[:digit:]]+)?\.?(([[:digit:]]+)?([eE]([+-]?)([[:digit:]]+))?)?$ ]] || return 1
sign=${BASH_REMATCH[1]} intPart=${BASH_REMATCH[2]}
fractPart=${BASH_REMATCH[4]} expSign=${BASH_REMATCH[6]} exponent=${BASH_REMATCH[7]}
# If there's neither an integer nor a fractional part, fail.
[[ -n $intPart || -n $fractPart ]] || return 1
# debugging: echo "[$sign][$intPart].[$fractPart]e[$expSign][$exponent]"
# If there's no exponent involved, output the number as is
# (It is either an integer or already a decimal fraction.)
[[ -n $exponent ]] || { echo ""; return 0; }
allDigits=${intPart}${fractPart}
# Calculate the number of integer digits in the resulting decimal fraction,
# after resolving the exponent.
intDigitCount=$(( ${#intPart} + ${expSign}${exponent} ))
# If the sign was an explicit +, set it to the empty string - we don't want to output it.
[[ $sign == '+' ]] && sign=''
if (( intDigitCount > 0 )); then # at least 1 integer digit
intDigits=${allDigits:0:intDigitCount}
padCount=$(( intDigitCount - ${#intDigits} ))
(( padCount > 0 )) && intDigits=${intDigits}$(printf "%${padCount}s" | tr ' ' '0')
fractDigits=${allDigits:intDigitCount} # determine what goes after the radix character
result=${sign}${intDigits}${fractDigits:+.}${fractDigits}
# Remove leading zeros, if any.
[[ $result =~ ^0+([^0].*)?$ ]] && result="${BASH_REMATCH[1]}"
else # result is < 1
padCount=$(( -intDigitCount ))
result=${sign}${leadingZero:+0}.$(printf "%${padCount}s" | tr ' ' '0')${intPart}${fractPart}
fi
# Trim an empty fractional part, and ensure that if
# the result is empty, '0' is output.
[[ $result =~ ^([^.]*)\.0+$ ]] && result="${BASH_REMATCH[1]}"
printf '%s\n' "${result:-0}"
}
最后,这是一个更简单的shell命令,但是不推荐,因为它是受双精度浮点值固有的舍入误差影响,因此您不能保证(忠实地)保留所有数字。:
set xx to "1.23456789E+4"
set yy to do shell script "awk -v n=" & quoted form of (xx as text) & " 'BEGIN \
{ CONVFMT=\"%.11f\"; ns=\"\"(n + 0); if (ns ~ /\./) gsub(\"0+$\",\"\",ns); print ns }'"
display alert yy
该命令使用 awk
识别科学记数法的本机能力,并使用(隐式应用)printf
数字格式 "%.11f"
将结果数字转换回字符串 - 即, 11位小数;在返回结果之前,任何尾随零都被修剪(使用 gsub()
)。
乍一看,似乎 没问题:结果是 12345.6789
。
但是,如果将小数位数更改为 12 (CONVFMT=\"%.12f\"
),则会出现舍入错误:12345.678900000001
(!)
您不会提前知道这种情况何时发生,因此如果需要忠实保留所有数字,则此方法不可行。
快速 google 搜索出现 this,但正如您所指出的,它是相同的基本代码,但不适合您。为了弥补我的愚蠢错误,我写了这个 applescript 来完成这项工作。它适用于 positive/negative 指数和 positive/negative 数字。祝你好运。
numberToString(-1.23456789E+4)
on numberToString(aNumber)
set aNumber to aNumber as text
-- check for a negative number
set isNegative to false
if character 1 of aNumber is "-" then
set isNegative to true
set aNumber to text 2 thru -1 of aNumber
end if
try
set a to the offset of "." in aNumber
set b to the offset of "E" in aNumber
set c to the offset of "+" in aNumber
set d to the offset of "-" in aNumber
if b is 0 then -- we do not have an exponential number
if isNegative then
return "-" & aNumber
else
return aNumber
end if
end if
if a is 0 then
set firstPart to ""
else
set firstPart to text 1 thru (a - 1) of aNumber
end if
set secondPart to text (a + 1) thru (b - 1) of aNumber
if c is 0 and d is 0 then -- assume a positive exponent
set isPositiveExponent to true
set thirdPart to text (b + 1) thru -1 of aNumber
else if c is not 0 then
set isPositiveExponent to true
set thirdPart to text (b + 2) thru -1 of aNumber
else
set isPositiveExponent to false
set thirdPart to text (b + 2) thru -1 of aNumber
end if
set thirdPart to thirdPart as number
if isPositiveExponent then
set newNumber to firstPart
set theRemainder to secondPart
repeat with i from 1 to thirdPart
try
set newNumber to newNumber & character i of secondPart
if theRemainder is not "" then
if (count of theRemainder) is 1 then
set theRemainder to ""
else
set theRemainder to text 2 thru -1 of theRemainder
end if
end if
on error
set newNumber to newNumber & "0"
end try
end repeat
if theRemainder is not "" then
set newNumber to newNumber & "." & theRemainder
end if
else
set newNumber to ""
set theRemainder to firstPart
repeat with i from 1 to thirdPart
try
set newNumber to character -i of firstPart & newNumber
if theRemainder is not "" then
if (count of theRemainder) is 1 then
set theRemainder to ""
else
set theRemainder to text 1 thru -2 of theRemainder
end if
end if
on error
set newNumber to "0" & newNumber
end try
end repeat
if theRemainder is not "" then
set newNumber to theRemainder & "." & newNumber & secondPart
else
set newNumber to "0." & newNumber & secondPart
end if
end if
on error
if isNegative then
return "-" & aNumber
else
return aNumber
end if
end try
if isNegative then
return "-" & newNumber
else
return newNumber
end if
end numberToString
我手上有点神秘。我找到了一个子程序 (http://macscripter.net/viewtopic.php?id=27520) 将一个科学数字转换成一串数字。但是,无论我如何尝试,它似乎都会删除剩余的数字。
“1.23456789E+4”应该变成“12345.6789”。相反,它只是 returns“12345”。
试试 运行 下面的代码,您就会明白我的意思了。我调用一个对话框来公开结果:
set xx to 1.23456789E+4
set yy to number_to_string(xx)
display dialog yy
on number_to_string(this_number)
set this_number to this_number as string
set deci to character 2 of (0.5 as text)
set x to the offset of deci in this_number
set z to the offset of "E" in this_number
if this_number contains "E+" then
set y to the offset of "+" in this_number
set the decimal_adjust to characters (y - (length of this_number)) thru ¬
-1 of this_number as string as number
if x is not 0 then
set the first_part to characters 1 thru (x - 1) of this_number as string
else
set the first_part to ""
end if
set the second_part to characters (x + 1) thru (z - 1) of this_number as string
set the converted_number to the first_part
repeat with i from 1 to the decimal_adjust
try
set the converted_number to ¬
the converted_number & character i of the second_part
on error
set the converted_number to the converted_number & "0"
end try
end repeat
return the converted_number
else
if this_number contains "E-" then
set y to the offset of "-" in this_number
if x is not 0 then
set the first_part to text 1 thru (x - 1) of this_number
else
set the first_part to ""
end if
set the second_part to text (x + 1) thru (z - 1) of this_number
set the converted_number to the first_part & second_part
set n to text (y + 1) thru -1 of this_number as number
set zero to "0."
if n > 1 then
repeat (n - 1) times
set zero to zero & "0"
end repeat
end if
set converted_number to zero & converted_number
else
set converted_number to this_number
end if
end if
return converted_number
end number_to_string
至于为什么你的 AppleScript 代码不起作用:
你的代码只关注指数,而不关注尾数[的位数=141=](指数前的小数部分)。
因此,以1.23456789E+4
为输入,严格提取尾数的4位组成结果,不管尾数有多少位:1
和 2345678
的 前 4 位数字 ,得到 12345
.
在 AppleScript 中做到这一点很重要,所以 我建议使用 do shell script
和 shell 命令,使用 bc
, a POSIX arbitrary-precision calculation utility,这将得到你快多了:
set xx to "1.23456789E+4" # define as *string* to avoid rounding errors
# Perform transformation via handler defined below.
set yy to my toDecFraction(xx)
display alert yy
on toDecFraction(numStr)
local maxDecPlaces
# For *negative* exponents: set the maximum number of decimal places in the result.
# For *positive* exponents: the number of decimal places in the result is
# automatically chosen to accommodate all digits.
# In either case: the max. number of decimal places supported is 2,147,483,647.
set maxDecPlaces to 32
do shell script "{ printf 'scale=" & maxDecPlaces & ¬
"; '; sed -E 's/[eE]\+?/*10^/g' <<<" & quoted form of (numStr as text) & "; } |
bc | tr -d '\n\' |
sed -E -e '/\./!b' -e 's/\.0+$//;t' -e 's/0+$//; s/^(-?)(\.)/\10\2/'"
end toDecFraction
printf 'scale=<n>;'
,当通过管道传输到bc
时,指示它在 negative 的情况下使用<n>
小数位的精度指数;如果指数是 positive,bc
会自动选择保留所有数字的精度。- 小数位数的上限是假设的
2,147,483,647
(!) (2^32/2-1
),但请注意,您为maxDecPlaces
选择的数字越高(在在负指数的情况下)或输入的小数位数越多(在正指数的情况下),转换所需的时间越长,尽管 实际上,在性能上几乎没有差异,比如, 32 对 200(!) 个小数位。请注意,如果限制太低,则会发生截断,而不是舍入。 - 可以计算精确保留所有数字所需的小数位数,但它需要非平凡的词法分析, 所以选择一个足够高的上限是一种务实的妥协。
- 小数位数的上限是假设的
sed -E 's/[eE]\+?/*10^/g''
将科学计数法重新格式化为bc
可以求值的完全等价的算术表达式;例如。:1e2
->1*10^2
.3e+1
->.3*10^1
2.5e-2
->2.5*10^-2
- 将该表达式传递给
bc
只是将其结果 打印为小数部分 ,小数位数与输入所暗示的一样多(在正指数的情况下) ,或通过变量指定scale
(在负指数的情况下) tr -d '\n\'
需要删除\
个字符。以及bc
在输出长度超过 70 个字符的数字时插入的换行符。sed -E -e '/\./!b' -e 's/\.0+$//;t' -e 's/0+$//; s/^(-?)(\.)/\10\2/'
清理结果从结果中删除尾随零(如果没有保留小数位,还删除小数点),前置0
,如果(的绝对值)结果是 < 1.
注:
- 如果结果的整数部分是
0
,它是打印,所以,例如,1e-2
打印为0.01
,这在 AppleScript 中很正常 - 而不是.01
。- 如果您不想要前导
0
,请将上面代码中的-e 's/0+$//; s/^(-?)(\.)/\10\2/'
替换为-e 's/0+$//'
。
- 如果您不想要前导
bc
是设计使然 不是 语言环境感知 ,因此 基数字符 ("decimal point") 它期望输入并产生输出是 always.
为了比较,这里有一个使用 纯 bash
代码执行转换的处理程序 词法 - 你可以看,滚动自己的转换的努力是不平凡的 - 并且在 AppleScript 中会更加冗长。
实际上,这两种方法的表现大致相同。 此解决方案的优点是小数位数没有限制,所有数字都会自动保留并且无法识别的数字字符串会可靠地引发错误.
set xx to "1.23456789E+4" # define as *string* to avoid rounding errors
# Perform transformation via handler defined below.
set yy to my toDecFraction(xx)
display alert yy
# SYNOPSIS
# toDecFraction(numString)
# DESCRIPTION
# Textually reformats the specified number string from decimal exponential (scientific) notation
# (e.g., 1.234e+2) to a decimal fraction (e.g., 123.4).
# Leading and trailing whitespace is acceptable.
# Input that is in integer form or already a decimal fraction is accepted, and echoed *unmodified*.
# No fractional part is output if there is none; e.g., '1.2e1' results in '12'.
# Numbers with an integer part of 0 are output with the leading zero (e.g. '0.1', not '.1')
# Unrecognized number strings result in an error.
# There is no limit on the number of decimal places and there are no rounding errors, given that
# the transformation is purely *lexical*.
# NOTE: This function is NOT locale-aware: a '.' must always be used as the radix character.
# EXAMPLES
# my toDecFraction('1.234567e+2') # -> '123.4567'
# my toDecFraction(toDecFraction '+1e-3') # -> '0.001'
# my toDecFraction('-1.23e+3') # -> '-1230'
# my toDecFraction ('1e-1') # -> '0.01'
on toDecFraction(numStr)
try
do shell script "
toDecFraction() {
local numStr leadingZero sign intPart fractPart expSign exponent allDigits intDigitCount intDigits fractDigits padCount result
{ [[ == '--' ]] && shift; } || { [[ == '-z' ]] && { leadingZero=1; shift; } }
read -r numStr <<<\"\" # trim leading and trailing whitespace
# Parse into constituent parts and fail, if not recognized as decimal integer / exponential notation.
[[ $numStr =~ ^([+-]?)([[:digit:]]+)?\.?(([[:digit:]]+)?([eE]([+-]?)([[:digit:]]+))?)?$ ]] || return 1
sign=${BASH_REMATCH[1]} intPart=${BASH_REMATCH[2]}
fractPart=${BASH_REMATCH[4]} expSign=${BASH_REMATCH[6]} exponent=${BASH_REMATCH[7]}
# If there's neither an integer nor a fractional part, fail.
[[ -n $intPart || -n $fractPart ]] || return 1
# debugging: echo \"[$sign][$intPart].[$fractPart]e[$expSign][$exponent]\"
# If there's no exponent involved, output the number as is
# (It is either an integer or already a decimal fraction.)
[[ -n $exponent ]] || { echo \"\"; return 0; }
allDigits=${intPart}${fractPart}
# Calculate the number of integer digits in the resulting decimal fraction,
# after resolving the exponent.
intDigitCount=$(( ${#intPart} + ${expSign}${exponent} ))
# If the sign was an explicit +, set it to the empty string - we don't want to output it.
[[ $sign == '+' ]] && sign=''
if (( intDigitCount > 0 )); then # at least 1 integer digit
intDigits=${allDigits:0:intDigitCount}
padCount=$(( intDigitCount - ${#intDigits} ))
(( padCount > 0 )) && intDigits=${intDigits}$(printf \"%${padCount}s\" | tr ' ' '0')
fractDigits=${allDigits:intDigitCount} # determine what goes after the radix character
result=${sign}${intDigits}${fractDigits:+.}${fractDigits}
# Remove leading zeros, if any.
[[ $result =~ ^0+([^0].*)?$ ]] && result=\"${BASH_REMATCH[1]}\"
else # result is < 1
padCount=$(( -intDigitCount ))
result=${sign}${leadingZero:+0}.$(printf \"%${padCount}s\" | tr ' ' '0')${intPart}${fractPart}
fi
# Trim an empty fractional part, and ensure that if
# the result is empty, '0' is output.
[[ $result =~ ^([^.]*)\.0+$ ]] && result=\"${BASH_REMATCH[1]}\"
printf '%s\n' \"${result:-0}\"
}
toDecFraction -z " & quoted form of (numStr as text)
on error number errNum
error "Not recognized as a number: " & (numStr as text) number (500 + errNum)
end try
end toDecFraction
这是 嵌入的 bash
函数,具有正确的语法突出显示:
toDecFraction() {
local numStr leadingZero sign intPart fractPart expSign exponent allDigits intDigitCount intDigits fractDigits padCount result
{ [[ == '--' ]] && shift; } || { [[ == '-z' ]] && { leadingZero=1; shift; } }
read -r numStr <<<"" # trim leading and trailing whitespace
# Parse into constituent parts and fail, if not recognized as decimal integer / exponential notation.
[[ $numStr =~ ^([+-]?)([[:digit:]]+)?\.?(([[:digit:]]+)?([eE]([+-]?)([[:digit:]]+))?)?$ ]] || return 1
sign=${BASH_REMATCH[1]} intPart=${BASH_REMATCH[2]}
fractPart=${BASH_REMATCH[4]} expSign=${BASH_REMATCH[6]} exponent=${BASH_REMATCH[7]}
# If there's neither an integer nor a fractional part, fail.
[[ -n $intPart || -n $fractPart ]] || return 1
# debugging: echo "[$sign][$intPart].[$fractPart]e[$expSign][$exponent]"
# If there's no exponent involved, output the number as is
# (It is either an integer or already a decimal fraction.)
[[ -n $exponent ]] || { echo ""; return 0; }
allDigits=${intPart}${fractPart}
# Calculate the number of integer digits in the resulting decimal fraction,
# after resolving the exponent.
intDigitCount=$(( ${#intPart} + ${expSign}${exponent} ))
# If the sign was an explicit +, set it to the empty string - we don't want to output it.
[[ $sign == '+' ]] && sign=''
if (( intDigitCount > 0 )); then # at least 1 integer digit
intDigits=${allDigits:0:intDigitCount}
padCount=$(( intDigitCount - ${#intDigits} ))
(( padCount > 0 )) && intDigits=${intDigits}$(printf "%${padCount}s" | tr ' ' '0')
fractDigits=${allDigits:intDigitCount} # determine what goes after the radix character
result=${sign}${intDigits}${fractDigits:+.}${fractDigits}
# Remove leading zeros, if any.
[[ $result =~ ^0+([^0].*)?$ ]] && result="${BASH_REMATCH[1]}"
else # result is < 1
padCount=$(( -intDigitCount ))
result=${sign}${leadingZero:+0}.$(printf "%${padCount}s" | tr ' ' '0')${intPart}${fractPart}
fi
# Trim an empty fractional part, and ensure that if
# the result is empty, '0' is output.
[[ $result =~ ^([^.]*)\.0+$ ]] && result="${BASH_REMATCH[1]}"
printf '%s\n' "${result:-0}"
}
最后,这是一个更简单的shell命令,但是不推荐,因为它是受双精度浮点值固有的舍入误差影响,因此您不能保证(忠实地)保留所有数字。:
set xx to "1.23456789E+4"
set yy to do shell script "awk -v n=" & quoted form of (xx as text) & " 'BEGIN \
{ CONVFMT=\"%.11f\"; ns=\"\"(n + 0); if (ns ~ /\./) gsub(\"0+$\",\"\",ns); print ns }'"
display alert yy
该命令使用 awk
识别科学记数法的本机能力,并使用(隐式应用)printf
数字格式 "%.11f"
将结果数字转换回字符串 - 即, 11位小数;在返回结果之前,任何尾随零都被修剪(使用 gsub()
)。
乍一看,似乎 没问题:结果是 12345.6789
。
但是,如果将小数位数更改为 12 (CONVFMT=\"%.12f\"
),则会出现舍入错误:12345.678900000001
(!)
您不会提前知道这种情况何时发生,因此如果需要忠实保留所有数字,则此方法不可行。
快速 google 搜索出现 this,但正如您所指出的,它是相同的基本代码,但不适合您。为了弥补我的愚蠢错误,我写了这个 applescript 来完成这项工作。它适用于 positive/negative 指数和 positive/negative 数字。祝你好运。
numberToString(-1.23456789E+4)
on numberToString(aNumber)
set aNumber to aNumber as text
-- check for a negative number
set isNegative to false
if character 1 of aNumber is "-" then
set isNegative to true
set aNumber to text 2 thru -1 of aNumber
end if
try
set a to the offset of "." in aNumber
set b to the offset of "E" in aNumber
set c to the offset of "+" in aNumber
set d to the offset of "-" in aNumber
if b is 0 then -- we do not have an exponential number
if isNegative then
return "-" & aNumber
else
return aNumber
end if
end if
if a is 0 then
set firstPart to ""
else
set firstPart to text 1 thru (a - 1) of aNumber
end if
set secondPart to text (a + 1) thru (b - 1) of aNumber
if c is 0 and d is 0 then -- assume a positive exponent
set isPositiveExponent to true
set thirdPart to text (b + 1) thru -1 of aNumber
else if c is not 0 then
set isPositiveExponent to true
set thirdPart to text (b + 2) thru -1 of aNumber
else
set isPositiveExponent to false
set thirdPart to text (b + 2) thru -1 of aNumber
end if
set thirdPart to thirdPart as number
if isPositiveExponent then
set newNumber to firstPart
set theRemainder to secondPart
repeat with i from 1 to thirdPart
try
set newNumber to newNumber & character i of secondPart
if theRemainder is not "" then
if (count of theRemainder) is 1 then
set theRemainder to ""
else
set theRemainder to text 2 thru -1 of theRemainder
end if
end if
on error
set newNumber to newNumber & "0"
end try
end repeat
if theRemainder is not "" then
set newNumber to newNumber & "." & theRemainder
end if
else
set newNumber to ""
set theRemainder to firstPart
repeat with i from 1 to thirdPart
try
set newNumber to character -i of firstPart & newNumber
if theRemainder is not "" then
if (count of theRemainder) is 1 then
set theRemainder to ""
else
set theRemainder to text 1 thru -2 of theRemainder
end if
end if
on error
set newNumber to "0" & newNumber
end try
end repeat
if theRemainder is not "" then
set newNumber to theRemainder & "." & newNumber & secondPart
else
set newNumber to "0." & newNumber & secondPart
end if
end if
on error
if isNegative then
return "-" & aNumber
else
return aNumber
end if
end try
if isNegative then
return "-" & newNumber
else
return newNumber
end if
end numberToString