使用 Julia,如何在工程符号中格式化 Avogadro 常数(或其他数字)?

Using Julia, how to format Avogadro's Constant (or other numbers) in engineering notation?

工程记数法与科学记数法的区别在于:

  1. 指数总是3的倍数,并且

  2. 小数点左边的数字被缩放到范围从 1 到 999。

我的用例要求在小数点右侧指定 0 到 13 位数字。默认为 4。

以下是所需示例:

const Avogadro = 6.022140857e23

str = eng_notation(Avogadro, digits=0)             
# str = "602E+21"

str = eng_notation(Avogadro, digits=1)             
# str = "602.2E+21"

# Default 4 digits to right of decimal point.
str = eng_notation(Avogadro)                       
# str = "602.2141E+21"  

str = eng_notation(Avogadro, digits=10)            
# str = "602.2140857000E+21"

# Negative and fractional numbers should also work.
str = eng_notation(-0.01234567, digits=7)          
# str = "-12.4567000E-03"

有什么建议吗?

编辑:我将要求更新为小数点右边的 0 到 13 位(之前是 0 到 15)。

下面更新的 eng_notation() 函数似乎解决了这个问题。 小数点右边的位数现在限制为 0 到 13 位,而不是 0 到 15 位。

这里有一些例子:

julia> const Avogadro = 6.022140857e23
6.022140857e23

julia> eng_notation(Avogadro, digits=0)
"602E+21"

julia> eng_notation(Avogadro, digits=1)
"602.2E+21"

julia> eng_notation(Avogadro)
"602.2141E+21"

julia> eng_notation(Avogadro, digits=10)
"602.2140857000E+21"

julia> eng_notation(-0.01234567, digits=7)
"-12.3456700E-03"

julia> eng_notation(Avogadro, digits=13, plus_sign=true)
"+602.2140857000000E+21"

julia> eng_notation(floatmax(Float64), digits=13)
"179.7693134862316E+306"

julia> eng_notation(floatmin(Float64), digits=13)
"22.2507385850720E-309"

这是更新后的代码:

"""
    eng_notation(num, digits=4, spec="E", plus_sign=false)

Return `num` in engineering notation where the exponent is a multiple of 3 and the 
number before the decimal point ranges from 1 to 999.

# Arguments
- `num`: any subtype of `Number`.  `Complex` subtypes are passed through unchanged.

    Numbers greater than (in absolute value) `floatmax(Float64)`=1.7976931348623157e308 
    are passed through unchanged.  

    Numbers less than (in absolute value) `floatmin(Float64)`=2.2250738585072014e-308 and > 0.0 
    are passed through unchanged. 
- `digits`: the number of digits to the right of the decimal point.  `digits` is clipped from 0 to 13.  
- `spec`: "E", 'E', "e", or 'e' sets case of the the exponent letter.
- `plus_sign`: when `true` includes a plus sign, "+", in front of numbers that are >= 0.0.

# Examples
```julia_repl
julia> const Avogadro = 6.022140857e23
6.022140857e23

julia> eng_notation(Avogadro, digits=0)
"602E+21"

julia> eng_notation(Avogadro, digits=1)
"602.2E+21"

julia> eng_notation(Avogadro)
"602.2141E+21"

julia> eng_notation(Avogadro, digits=10)
"602.2140857000E+21"

julia> eng_notation(-0.01234567, spec="e", digits=7)
"-12.3456700e-03"

julia> eng_notation(Avogadro, digits=13, plus_sign=true)
"+602.2140857000000E+21"

julia> eng_notation(floatmax(Float64), digits=13)
"179.7693134862316E+306"

julia> eng_notation(floatmin(Float64), digits=13)
"22.2507385850720E-309"
```
"""
function eng_notation(num::Number; digits=4, spec="E", plus_sign=false)
    # Complex subtypes are just passed through unchanged.
    if typeof(num) <: Complex; return num; end
    # Values larger/smaller that Float64 limits just pass through unchanged.
    if abs(num) > floatmax(Float64); return num; end # max=1.7976931348623157e308
    if abs(num) < floatmin(Float64) && num != 0; return num; end # min=2.2250738585072014e-308
    # Min of 0 and max of 13 digits after the decimal point (dp).
    digits = digits < 0 ? 0 : digits
    digits = digits > 13 ? 13 : digits
    # Don't add a dp when 0 digits after dp.
    dec_pt = digits == 0 ? "" : "."
    spec_char = spec[1] == 'E' ? 'E' : 'e'
    sign = ifelse(num < 0, "-", ifelse(plus_sign, "+", "")) 

    # This Julia code is modified from Java code at:
    # http://www.labbookpages.co.uk/software/java/engNotation.html
    # If the value is zero, then simply return 0 with the correct number of digits.
    if num == 0; return string(sign, 0, dec_pt, "0"^digits, spec_char, "+00"); end
    # If the value is negative, make it positive so the log10 works
    pos_num = num < 0 ? -num : num
    log10_num = log10(pos_num);
    # Determine how many orders of 3 magnitudes the value is.
    count = floor(log10_num/3);
    # Scale num into the range 1 <= num < 1000.
    val = num/10.0^(3count)

    if digits == 0
        val_int = Int(round(val, digits=0))
    else
        val_int = Int(trunc(val))
    end
    n_val_digits = length(string(val_int))
    n_val_digits = ifelse(val_int < 0, n_val_digits-1, n_val_digits) # Account for - sign
    # Determine fractional digits to requested number of digits.
    # Use 15 below because 1 + 15 = 16, and 16 sigdigits is around the limit of Float64.
    num_str = @sprintf "%+.15e" num   
    # Remove sign and decimal pt. 
    digits_str = replace(num_str[2:end], "." => "")
    e_index = findlast("e", digits_str).start
    # Remove exponent.
    digits_str = digits_str[1:e_index-1]
    # Jump over leading digits to get digits to right of dec pt.
    frac_digits = digits_str[n_val_digits+1:end]
    if digits == 0
        frac_digits = ""
    else
        frac_digits = string(Int(round(parse(Int, frac_digits), sigdigits=digits)))
        # Round may not give us digits zeros, so we just pad to the right.
        frac_digits = rpad(frac_digits, digits, "0")
        frac_digits = frac_digits[1:digits]
    end    
    # Determine the scaled exponent and pad with zeros for small exponents.
    exp = Int(3count)
    exp_sign = exp >= 0 ? "+" : "-"
    exp_digits = lpad(abs(exp), 2, "0")
    return string(sign, abs(val_int), dec_pt, frac_digits, spec_char, exp_sign, exp_digits)
end # eng_notation()

这里有几个测试:

function test_eng_notation()
    @testset "Test eng_notation() function" begin
        Avogadro = 6.022140857e23
        @test eng_notation(Avogadro, digits=0) == "602E+21"
        @test eng_notation(Avogadro, digits=1) == "602.2E+21"
        @test eng_notation(Avogadro) == "602.2141E+21"
        @test eng_notation(Avogadro, digits=10) == "602.2140857000E+21"
        @test eng_notation(-0.01234567, spec="e", digits=7) == "-12.3456700e-03"
        @test eng_notation(Avogadro, digits=13, plus_sign=true) == "+602.2140857000000E+21"
        @test eng_notation(floatmax(Float64), digits=13) == "179.7693134862316E+306"
        @test eng_notation(floatmin(Float64), digits=13) == "22.2507385850720E-309"
    end
    return nothing
end

使用NumericIO.jl

julia> using NumericIO

julia> const Avogadro = 6.022140857e23;

julia> formatted(Avogadro, :ENG, ndigits=4, charset=:ASCII)
"602.2E21"

julia> formatted(Avogadro, :ENG, ndigits=4)
"602.2×10²¹"