/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
stdlib/Printf/src/Printf.jl
1 040 строк
33 KB
Keno Fischer
Add explicitly wrapping versions of integer arithmetic (#50790)
10 июл 2026, 04:36
Не верифицирован
10 июл 2026, 04:36
b6e5cb5
Код
Авторство
О чём код?
# This file is a part of Julia. License is MIT: https://julialang.org/license """ The `Printf` module provides formatted output functions similar to the C standard library's `printf`. It allows formatted printing to an output stream or to a string. """ module Printf using Base.Ryu export @printf, @sprintf public format, Format # format specifier categories const Ints = Union{Val{'d'}, Val{'i'}, Val{'u'}, Val{'x'}, Val{'X'}, Val{'o'}} const Floats = Union{Val{'e'}, Val{'E'}, Val{'f'}, Val{'F'}, Val{'g'}, Val{'G'}, Val{'a'}, Val{'A'}} const Chars = Union{Val{'c'}, Val{'C'}} const Strings = Union{Val{'s'}, Val{'S'}} const Pointer = Val{'p'} const HexBases = Union{Val{'x'}, Val{'X'}, Val{'a'}, Val{'A'}} const PositionCounter = Val{'n'} const MAX_FRACTIONAL_PART_WIDTH = 17 # max significant decimals + 1: `ceil(Int, log10(1 / eps(Float64))) + 1` const MAX_INTEGER_PART_WIDTH = 309 # max exponent: `ceil(Int, log10(prevfloat(typemax(Float64))))` const MAX_FMT_CHARS_WIDTH = 5 # hash | sign +/- | decimal dot | exponent e/E | exponent sign """ Typed representation of a format specifier. `T` is a `Val{'_'}`, where `_` is a valid format specifier character. Fields are the various modifiers allowed for various format specifiers. """ struct Spec{T} # T => %type => Val{'type'} leftalign::Bool plus::Bool space::Bool zero::Bool hash::Bool width::Int precision::Int dynamic_width::Bool dynamic_precision::Bool end # recreate the format specifier string from a typed Spec Base.string(f::Spec{T}; modifier::String="") where {T} = string("%", f.leftalign ? "-" : "", f.plus ? "+" : "", f.space ? " " : "", f.zero ? "0" : "", f.hash ? "#" : "", f.dynamic_width ? "*" : (f.width > 0 ? f.width : ""), f.dynamic_precision ? ".*" : (f.precision == 0 ? ".0" : (f.precision > 0 ? ".$(f.precision)" : "")), modifier, char(T)) Base.show(io::IO, f::Spec) = print(io, string(f)) floatfmt(s::Spec{T}) where {T} = Spec{Val{'f'}}(s.leftalign, s.plus, s.space, s.zero, s.hash, s.width, 0, s.dynamic_width, s.dynamic_precision) ptrfmt(s::Spec{T}, x) where {T} = Spec{Val{'x'}}(s.leftalign, s.plus, s.space, s.zero, true, s.width, sizeof(x) == 8 ? 16 : 8, s.dynamic_width, s.dynamic_precision) """ Printf.Format(format_str) Create a C printf-compatible format object that can be used for formatting values. The input `format_str` can include any valid format specifier character and modifiers. A `Format` object can be passed to `Printf.format(f::Format, args...)` to produce a formatted string, or `Printf.format(io::IO, f::Format, args...)` to print the formatted string directly to `io`. For convenience, the `Printf.format"..."` string macro form can be used for building a `Printf.Format` object at macro-expansion-time. !!! compat "Julia 1.6" `Printf.Format` requires Julia 1.6 or later. """ struct Format{S, T} str::S # original full format string as CodeUnits # keep track of non-format specifier strings to print # length(substringranges) == length(formats) + 1 # so when printing, we start with printing # str[substringranges[1]], then formats[1] + args[1] # then str[substringranges[2]], then formats[2] + args[2] # and so on, then at the end, str[substringranges[end]] substringranges::Vector{UnitRange{Int}} formats::T # Tuple of Specs numarguments::Int # required for dynamic format specifiers end # what number base should be used for a given format specifier? base(T) = T <: HexBases ? 16 : T <: Val{'o'} ? 8 : 10 char(::Type{Val{c}}) where {c} = c struct InvalidFormatStringError <: Exception message::String format::String start_color::Int end_color::Int end function Base.showerror(io::IO, err::InvalidFormatStringError) io_has_color = get(io, :color, false)::Bool println(io, "InvalidFormatStringError: ", err.message) print(io, " \"", @view(err.format[begin:prevind(err.format, err.start_color)])) invalid_text = @view err.format[err.start_color:err.end_color] printstyled(io, invalid_text, color=:red) # +1 is okay, since all format characters are single bytes println(io, @view(err.format[err.end_color+1:end]), "\"") arrow_error = '-'^(length(invalid_text)-1) arrow = " " * ' '^err.start_color * arrow_error * "^\n" if io_has_color printstyled(io, arrow, color=:red) else print(io, arrow) end end # parse format string function Format(f::AbstractString) bytes = codeunits(f) len = length(bytes) pos = 1 numarguments = 0 b = 0x00 local last_percent_pos # skip ahead to first format specifier while pos <= len b = bytes[pos] pos += 1 if b == UInt8('%') last_percent_pos = pos-1 pos > len && throw(InvalidFormatStringError("Format specifier is incomplete", f, last_percent_pos, last_percent_pos)) if bytes[pos] == UInt8('%') # escaped '%' b = bytes[pos] pos += 1 else break end end end strs = [1:pos - 1 - (b == UInt8('%'))] fmts = [] while pos <= len b = bytes[pos] pos += 1 # positioned at start of first format str % # parse flags leftalign = plus = space = zero = hash = false while true if b == UInt8('-') leftalign = true elseif b == UInt8('+') plus = true elseif b == UInt8(' ') space = true elseif b == UInt8('0') zero = true elseif b == UInt8('#') hash = true else break end pos > len && throw(InvalidFormatStringError("Format specifier is incomplete", f, last_percent_pos, pos-1)) b = bytes[pos] pos += 1 end if leftalign zero = false end # parse width width = 0 dynamic_width = false if b == UInt8('*') dynamic_width = true numarguments += 1 b = bytes[pos] pos += 1 else while UInt8('0') <= b <= UInt8('9') width = 10 * width + (b - UInt8('0')) b = bytes[pos] pos += 1 pos > len && break end end # parse precision precision = 0 parsedprecdigits = false dynamic_precision = false if b == UInt8('.') pos > len && throw(InvalidFormatStringError("Precision specifier is missing precision", f, last_percent_pos, pos-1)) parsedprecdigits = true b = bytes[pos] pos += 1 if pos <= len if b == UInt8('*') dynamic_precision = true numarguments += 1 b = bytes[pos] pos += 1 else precision = 0 while UInt8('0') <= b <= UInt8('9') precision = 10precision + (b - UInt8('0')) b = bytes[pos] pos += 1 pos > len && break end end end end # parse length modifier (ignored) if b == UInt8('h') || b == UInt8('l') prev = b pos > len && throw(InvalidFormatStringError("Length modifier is missing type specifier", f, last_percent_pos, pos-1)) b = bytes[pos] pos += 1 if b == prev pos > len && throw(InvalidFormatStringError("Length modifier is missing type specifier", f, last_percent_pos, pos-1)) b = bytes[pos] pos += 1 end elseif b in b"Ljqtz" # q was a synonym for ll above, see `man 3 printf`. Not to be used. pos > len && throw(InvalidFormatStringError("Length modifier is missing type specifier", f, last_percent_pos, pos-1)) b = bytes[pos] pos += 1 end # parse type !(b in b"diouxXDOUeEfFgGaAcCsSpn") && throw(InvalidFormatStringError("'$(Char(b))' is not a valid type specifier", f, last_percent_pos, pos-1)) type = Val{Char(b)} if type <: Ints && precision > 0 # note - we should also set zero to false if dynamic precision > 0 # this is taken care of in fmt() for Ints zero = false elseif (type <: Strings || type <: Chars) && !parsedprecdigits precision = -1 elseif type <: Union{Val{'a'}, Val{'A'}} && !parsedprecdigits precision = -1 elseif type <: Floats && !parsedprecdigits precision = 6 end numarguments += 1 push!(fmts, Spec{type}(leftalign, plus, space, zero, hash, width, precision, dynamic_width, dynamic_precision)) start = pos while pos <= len b = bytes[pos] pos += 1 if b == UInt8('%') last_percent_pos = pos-1 pos > len && throw(InvalidFormatStringError("Format specifier is incomplete", f, last_percent_pos, last_percent_pos)) if bytes[pos] == UInt8('%') # escaped '%' b = bytes[pos] pos += 1 else break end end end push!(strs, start:pos - 1 - (b == UInt8('%'))) end return Format(bytes, strs, Tuple(fmts), numarguments) end macro format_str(str) Format(str) end const hex = b"0123456789abcdef" const HEX = b"0123456789ABCDEF" # write out a single arg according to format options # char @inline function writechar(buf, pos, c) u = bswap(reinterpret(UInt32, c)) while true buf[pos] = u % UInt8 pos += 1 (u >>= 8) == 0 && break end return pos end @inline function rmdynamic(spec::Spec{T}, args, argp) where {T} zero, width, precision = spec.zero, spec.width, spec.precision if spec.dynamic_width width = args[argp]::Integer argp += 1 end if spec.dynamic_precision precision = args[argp]::Integer if zero && T <: Ints && precision > 0 zero = false end argp += 1 end (Spec{T}(spec.leftalign, spec.plus, spec.space, zero, spec.hash, width, precision, false, false), argp) end Base.@constprop :aggressive function fmt(buf, pos, args, argp, spec::Spec{T}) where {T} spec, argp = rmdynamic(spec, args, argp) (fmt(buf, pos, args[argp], spec), argp+1) end function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Chars} leftalign, width = spec.leftalign, spec.width c = Char(first(arg)) w = textwidth(c) if !leftalign && width > w for _ = 1:(width - w) buf[pos] = UInt8(' ') pos += 1 end end pos = writechar(buf, pos, c) if leftalign && width > w for _ = 1:(width - w) buf[pos] = UInt8(' ') pos += 1 end end return pos end # strings function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Strings} leftalign, hash, width, prec = spec.leftalign, spec.hash, spec.width, spec.precision str = string(arg) slen = textwidth(str)::Int + (hash ? arg isa AbstractString ? 2 : 1 : 0) op = p = prec == -1 ? slen : min(slen, prec) if !leftalign && width > p for _ = 1:(width - p) buf[pos] = UInt8(' ') pos += 1 end end if hash if arg isa Symbol buf[pos] = UInt8(':') pos += 1 p -= 1 elseif arg isa AbstractString buf[pos] = UInt8('"') pos += 1 p -= 1 end end for c in str p -= textwidth(c) p < 0 && break pos = writechar(buf, pos, c) end if hash && arg isa AbstractString && p > 0 buf[pos] = UInt8('"') pos += 1 end if leftalign && width > op for _ = 1:(width - op) buf[pos] = UInt8(' ') pos += 1 end end return pos end # integers toint(x) = x toint(x::Rational) = Integer(x) fmt(buf, pos, arg::AbstractFloat, spec::Spec{T}) where {T <: Ints} = fmt(buf, pos, arg, floatfmt(spec)) function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Ints} leftalign, plus, space, zero, hash, width, prec = spec.leftalign, spec.plus, spec.space, spec.zero, spec.hash, spec.width, spec.precision bs = base(T) arg2 = toint(arg) n = i = ndigits(arg2, base=bs, pad=1) neg = arg2 < 0 x = arg2 isa Base.BitSigned ? unsigned(abs(arg2)) : abs(arg2) arglen = n + (neg || (plus | space)) + (T == Val{'o'} && hash ? 1 : 0) + (T == Val{'x'} && hash ? 2 : 0) + (T == Val{'X'} && hash ? 2 : 0) arglen2 = arglen < width && prec > 0 ? arglen + min(max(0, prec - n), width - arglen) : arglen if !leftalign && !zero && arglen2 < width # pad left w/ spaces for _ = 1:(width - arglen2) buf[pos] = UInt8(' ') pos += 1 end end if neg buf[pos] = UInt8('-'); pos += 1 elseif plus # plus overrides space buf[pos] = UInt8('+'); pos += 1 elseif space buf[pos] = UInt8(' '); pos += 1 end if T == Val{'o'} && hash buf[pos] = UInt8('0') pos += 1 elseif T == Val{'x'} && hash buf[pos] = UInt8('0') buf[pos + 1] = UInt8('x') pos += 2 elseif T == Val{'X'} && hash buf[pos] = UInt8('0') buf[pos + 1] = UInt8('X') pos += 2 end if zero && arglen2 < width for _ = 1:(width - arglen2) buf[pos] = UInt8('0') pos += 1 end elseif n < prec for _ = 1:(prec - n) buf[pos] = UInt8('0') pos += 1 end elseif arglen < arglen2 for _ = 1:(arglen2 - arglen) buf[pos] = UInt8('0') pos += 1 end end while i > 0 @inbounds buf[pos + i - 1] = bs == 16 ? (T == Val{'x'} ? hex[(x & 0x0f) + 1] : HEX[(x & 0x0f) + 1]) : (48 + (bs == 8 ? (x & 0x07) : rem(x, 10))) if bs == 8 x >>= 3 elseif bs == 16 x >>= 4 else x = oftype(x, div(x, 10)) end i -= 1 end pos += n if leftalign && arglen2 < width # pad right for _ = 1:(width - arglen2) buf[pos] = UInt8(' ') pos += 1 end end return pos end # floats """ Printf.tofloat(x) Convert an argument to a Base float type for printf formatting. By default, arguments are converted to `Float64` via `Float64(x)`. Custom numeric types that have a conversion to a Base float type that wish to hook into printf formatting can extend this method like: ```julia Printf.tofloat(x::MyCustomType) = convert_my_custom_type_to_float(x) ``` For arbitrary precision numerics, you might extend the method like: ```julia Printf.tofloat(x::MyArbitraryPrecisionType) = BigFloat(x) ``` !!! compat "Julia 1.6" This function requires Julia 1.6 or later. """ tofloat(x) = Float64(x) tofloat(x::Base.IEEEFloat) = x tofloat(x::BigFloat) = x _snprintf(ptr, siz, str, arg) = @ccall "libmpfr".mpfr_snprintf(ptr::Ptr{UInt8}, siz::Csize_t, str::Ptr{UInt8}; arg::Ref{BigFloat})::Cint # Arbitrary constant for a maximum number of bytes we want to output for a BigFloat. # 8KiB seems like a reasonable default. Larger BigFloat representations should probably # use a custom printing routine. Printing values with results larger than this ourselves # seems like a dangerous thing to do. const __BIG_FLOAT_MAX__ = 8192 function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Floats} leftalign, plus, space, zero, hash, width, prec = spec.leftalign, spec.plus, spec.space, spec.zero, spec.hash, spec.width, spec.precision x = tofloat(arg) if x isa BigFloat if isfinite(x) GC.@preserve buf begin siz = length(buf) - pos + 1 str = string(spec; modifier="R") required_length = _snprintf(pointer(buf, pos), siz, str, x) if required_length > siz required_length > __BIG_FLOAT_MAX__ && throw(ArgumentError("The given BigFloat requires $required_length bytes to be printed, which is more than the maximum of $__BIG_FLOAT_MAX__ bytes supported.")) resize!(buf, required_length + 1) required_length = _snprintf(pointer(buf, pos), required_length + 1, str, x) end required_length > 0 || throw(ArgumentError("The given BigFloat would produce less than the maximum allowed number of bytes $__BIG_FLOAT_MAX__, but still couldn't be printed fully for an unknown reason.")) return pos + required_length end end x = Float64(x) end if T == Val{'e'} || T == Val{'E'} newpos = Ryu.writeexp(buf, pos, x, prec, plus, space, hash, char(T), UInt8('.')) elseif T == Val{'f'} || T == Val{'F'} newpos = Ryu.writefixed(buf, pos, x, prec, plus, space, hash, UInt8('.')) elseif T == Val{'g'} || T == Val{'G'} if isinf(x) || isnan(x) newpos = Ryu.writeshortest(buf, pos, x, plus, space) else # C11-compliant general format prec = prec == 0 ? 1 : prec # format the value in scientific notation and parse the exponent part exp = let p = Ryu.writeexp(buf, pos, x, prec) b1, b2, b3, b4 = buf[p-4], buf[p-3], buf[p-2], buf[p-1] Z = UInt8('0') if b1 == UInt8('e') # two-digit exponent sign = b2 == UInt8('+') ? 1 : -1 exp = 10 * (b3 - Z) + (b4 - Z) else # three-digit exponent sign = b1 == UInt8('+') ? 1 : -1 exp = 100 * (b2 - Z) + 10 * (b3 - Z) + (b4 - Z) end flipsign(exp, sign) end if -4 ≤ exp < prec newpos = Ryu.writefixed(buf, pos, x, prec - (exp + 1), plus, space, hash, UInt8('.'), !hash) else newpos = Ryu.writeexp(buf, pos, x, prec - 1, plus, space, hash, T == Val{'g'} ? UInt8('e') : UInt8('E'), UInt8('.'), !hash) end end elseif T == Val{'a'} || T == Val{'A'} x, neg = x < 0 || x === -Base.zero(x) ? (-x, true) : (x, false) newpos = pos if neg buf[newpos] = UInt8('-') newpos += 1 elseif plus buf[newpos] = UInt8('+') newpos += 1 elseif space buf[newpos] = UInt8(' ') newpos += 1 end if isnan(x) buf[newpos] = UInt8('N') buf[newpos + 1] = UInt8('a') buf[newpos + 2] = UInt8('N') newpos += 3 elseif !isfinite(x) buf[newpos] = UInt8('I') buf[newpos + 1] = UInt8('n') buf[newpos + 2] = UInt8('f') newpos += 3 else buf[newpos] = UInt8('0') newpos += 1 buf[newpos] = T <: Val{'a'} ? UInt8('x') : UInt8('X') newpos += 1 if arg == 0 buf[newpos] = UInt8('0') newpos += 1 if prec > 0 buf[newpos] = UInt8('.') newpos += 1 while prec > 0 buf[newpos] = UInt8('0') newpos += 1 prec -= 1 end end buf[newpos] = T <: Val{'a'} ? UInt8('p') : UInt8('P') buf[newpos + 1] = UInt8('+') buf[newpos + 2] = UInt8('0') newpos += 3 else if prec > -1 s, p = frexp(x) sigbits = 4 * min(prec, 13) s = 0.25 * round(ldexp(s, 1 + sigbits)) # ensure last 2 exponent bits either 01 or 10 u = (reinterpret(UInt64, s) & 0x003f_ffff_ffff_ffff) >> (52 - sigbits) i = n = (sizeof(u) << 1) - (leading_zeros(u) >> 2) else s, p = frexp(x) s *= 2.0 u = (reinterpret(UInt64, s) & 0x001f_ffff_ffff_ffff) t = (trailing_zeros(u) >> 2) u >>= (t << 2) i = n = 14 - t end frac = u > 9 || hash || prec > 0 while i > 1 buf[newpos + i] = T == Val{'a'} ? hex[(u & 0x0f) + 1] : HEX[(u & 0x0f) + 1] u >>= 4 i -= 1 prec -= 1 end if frac buf[newpos + 1] = UInt8('.') end buf[newpos] = T == Val{'a'} ? hex[(u & 0x0f) + 1] : HEX[(u & 0x0f) + 1] newpos += n + frac while prec > 0 buf[newpos] = UInt8('0') newpos += 1 prec -= 1 end buf[newpos] = T <: Val{'a'} ? UInt8('p') : UInt8('P') newpos += 1 p -= 1 buf[newpos] = p < 0 ? UInt8('-') : UInt8('+') p = p < 0 ? -p : p newpos += 1 n = i = ndigits(p, base=10, pad=1) while i > 0 buf[newpos + i - 1] = 48 + rem(p, 10) p = oftype(p, div(p, 10)) i -= 1 end newpos += n end end end if newpos - pos < width # need to pad if leftalign # easy case, just pad spaces after number for _ = 1:(width - (newpos - pos)) buf[newpos] = UInt8(' ') newpos += 1 end else # right aligned n = width - (newpos - pos) if zero && isfinite(x) ex = (arg < 0 || (plus | space)) + (T <: Union{Val{'a'}, Val{'A'}} ? 2 : 0) so = pos + ex len = (newpos - pos) - ex copyto!(buf, so + n, buf, so, len) for i = so:(so + n - 1) buf[i] = UInt8('0') end newpos += n else copyto!(buf, pos + n, buf, pos, newpos - pos) for i = pos:(pos + n - 1) buf[i] = UInt8(' ') end newpos += n end end end return newpos end # pointers fmt(buf, pos, arg, spec::Spec{Pointer}) = fmt(buf, pos, UInt64(arg), ptrfmt(spec, arg)) # position counters function fmt(buf, pos, arg::Ref{<:Integer}, ::Spec{PositionCounter}) arg[] = pos - 1 pos end # old Printf compat function fix_dec end function ini_dec end # generic fallback function fmtfallback(buf, pos, arg, spec::Spec{T}) where {T} leftalign, plus, space, zero, hash, width, prec = spec.leftalign, spec.plus, spec.space, spec.zero, spec.hash, spec.width, spec.precision buf2 = Base.StringVector( MAX_INTEGER_PART_WIDTH + MAX_FRACTIONAL_PART_WIDTH + MAX_FMT_CHARS_WIDTH ) ise = T <: Union{Val{'e'}, Val{'E'}} isg = T <: Union{Val{'g'}, Val{'G'}} isf = T <: Val{'f'} if isg prec = prec == 0 ? 1 : prec arg = round(arg, sigdigits=prec) end n, pt, neg = isf ? fix_dec(arg, prec, buf2) : ini_dec(arg, min(prec + ise, length(buf2) - 1), buf2) if isg && !hash while buf2[n] == UInt8('0') n -= 1 end end expform = ise || (isg && !(-4 < pt <= prec)) n2 = n + (expform ? 4 : 0) + (prec > 0 || hash) + (neg || (plus | space)) + (isf && pt >= n ? prec + 1 : 0) if !leftalign && !zero && n2 < width # pad left w/ spaces for _ = 1:(width - n2) buf[pos] = UInt8(' ') pos += 1 end end if neg buf[pos] = UInt8('-'); pos += 1 elseif plus # plus overrides space buf[pos] = UInt8('+'); pos += 1 elseif space buf[pos] = UInt8(' '); pos += 1 end if zero && n2 < width for _ = 1:(width - n2) buf[pos] = UInt8('0') pos += 1 end end if expform buf[pos] = buf2[1] pos += 1 if n > 1 || hash buf[pos] = UInt8('.') pos += 1 for i = 2:n buf[pos] = buf2[i] pos += 1 end end buf[pos] = T <: Val{'e'} || T <: Val{'g'} ? UInt8('e') : UInt8('E') pos += 1 exp = pt - 1 buf[pos] = exp < 0 ? UInt8('-') : UInt8('+') pos += 1 exp = abs(exp) if exp < 10 buf[pos] = UInt8('0') buf[pos + 1] = 48 + exp pos += 2 else buf[pos] = 48 + div(exp, 10) buf[pos + 1] = 48 + rem(exp, 10) pos += 2 end elseif pt <= 0 buf[pos] = UInt8('0') buf[pos + 1] = UInt8('.') pos += 2 while pt < 0 buf[pos] = UInt8('0') pos += 1 pt += 1 end for i = 1:n buf[pos] = buf2[i] pos += 1 end elseif pt >= n for i = 1:n buf[pos] = buf2[i] pos += 1 end while pt > n buf[pos] = UInt8('0') pos += 1 n += 1 end if hash || (isf && prec > 0) buf[pos] = UInt8('.') pos += 1 while prec > 0 buf[pos] = UInt8('0') pos += 1 prec -= 1 end end else for i = 1:pt buf[pos] = buf2[i] pos += 1 end buf[pos] = UInt8('.') pos += 1 for i = pt+1:n buf[pos] = buf2[i] pos += 1 end end if leftalign && n2 < width # pad right for _ = 1:(width - n2) buf[pos] = UInt8(' ') pos += 1 end end return pos end const UNROLL_UPTO = 16 # if you have your own buffer + pos, write formatted args directly to it @inline function format(buf::Vector{UInt8}, pos::Integer, f::Format, args...) # write out first substring escapechar = false for i in f.substringranges[1] b = f.str[i] if !escapechar buf[pos] = b pos += 1 escapechar = b === UInt8('%') else escapechar = false end end # for each format, write out arg and next substring # unroll up to 16 formats N = length(f.formats) argp = 1 Base.@nexprs 16 i -> begin if N >= i pos, argp = fmt(buf, pos, args, argp, f.formats[i]) for j in f.substringranges[i + 1] b = f.str[j] if !escapechar buf[pos] = b pos += 1 escapechar = b === UInt8('%') else escapechar = false end end end end if N > 16 for i = 17:length(f.formats) pos, argp = fmt(buf, pos, args, argp, f.formats[i]) for j in f.substringranges[i + 1] b = f.str[j] if !escapechar buf[pos] = b pos += 1 escapechar = b === UInt8('%') else escapechar = false end end end end return pos end @inline function plength(f::Spec{T}, args, argp) where {T} f, argp = rmdynamic(f, args, argp) (plength(f, args[argp]), argp+1) end function plength(f::Spec{T}, x) where {T <: Chars} c = Char(first(x)) w = textwidth(c) return max(f.width, w) + (ncodeunits(c) - w) end plength(f::Spec{Pointer}, x) = max(f.width, 2 * sizeof(x) + 2) function plength(f::Spec{T}, x) where {T <: Strings} str = string(x) sw = textwidth(str) p = f.precision == -1 ? (sw + (f.hash ? (x isa Symbol ? 1 : 2) : 0)) : f.precision return max(f.width, p) + (sizeof(str) - sw) end function plength(f::Spec{T}, x) where {T <: Ints} x2 = toint(x) return max( f.width, f.precision + ndigits(x2, base=base(T), pad=1) + MAX_FMT_CHARS_WIDTH ) end plength(f::Spec{T}, x::AbstractFloat) where {T <: Ints} = max(f.width, f.hash + MAX_INTEGER_PART_WIDTH + 0 + MAX_FMT_CHARS_WIDTH) plength(f::Spec{T}, x) where {T <: Floats} = max(f.width, f.hash + MAX_INTEGER_PART_WIDTH + f.precision + MAX_FMT_CHARS_WIDTH) plength(::Spec{PositionCounter}, x) = 0 @inline function computelen(substringranges, formats, args) len = sum(length, substringranges) N = length(formats) # unroll up to 16 formats argp = 1 Base.@nexprs 16 i -> begin if N >= i l, argp = plength(formats[i], args, argp) len += l end end if N > 16 for i = 17:length(formats) l, argp = plength(formats[i], args, argp) len += l end end return len end @noinline argmismatch(a, b) = throw(ArgumentError("Number of format specifiers and number of provided args differ: $a != $b")) """ Printf.format(f::Printf.Format, args...) => String Printf.format(io::IO, f::Printf.Format, args...) Apply a printf format object `f` to provided `args` and return the formatted string (1st method), or print directly to an `io` object (2nd method). See [`@printf`](@ref) for more details on C `printf` support. """ function format end # Since it will specialize on `f`, which has a Tuple-type often of length(args), we might as well specialize on `args` too. function format(io::IO, f::Format, args::Vararg{Any,N}) where N # => Nothing f.numarguments == length(args) || argmismatch(f.numarguments, length(args)) buf = Base.StringVector(computelen(f.substringranges, f.formats, args)) pos = format(buf, 1, f, args...) write(io, resize!(buf, pos - 1)) return end function format(f::Format, args::Vararg{Any,N}) where N # => String f.numarguments == length(args) || argmismatch(f.numarguments, length(args)) buf = Base.StringVector(computelen(f.substringranges, f.formats, args)) pos = format(buf, 1, f, args...) return String(resize!(buf, pos - 1)) end """ @printf([io::IO], "%Fmt", args...) Print `args` using C `printf` style format specification string. Optionally, an `IO` may be passed as the first argument to redirect output. # Examples ```jldoctest julia> @printf "Hello %s" "world" Hello world julia> @printf "Scientific notation %e" 1.234 Scientific notation 1.234000e+00 julia> @printf "Scientific notation three digits %.3e" 1.23456 Scientific notation three digits 1.235e+00 julia> @printf "Decimal two digits %.2f" 1.23456 Decimal two digits 1.23 julia> @printf "Padded to length 5 %5i" 123 Padded to length 5 123 julia> @printf "Padded with zeros to length 6 %06i" 123 Padded with zeros to length 6 000123 julia> @printf "Use shorter of decimal or scientific %g %g" 1.23 12300000.0 Use shorter of decimal or scientific 1.23 1.23e+07 julia> @printf "Use dynamic width and precision %*.*f" 10 2 0.12345 Use dynamic width and precision 0.12 ``` For a systematic specification of the format, see [here](https://en.cppreference.com/w/c/io/fprintf). See also [`@sprintf`](@ref) to get the result as a `String` instead of it being printed. # Caveats `Inf` and `NaN` are printed consistently as `Inf` and `NaN` for flags `%a`, `%A`, `%e`, `%E`, `%f`, `%F`, `%g`, and `%G`. Furthermore, if a floating point number is equally close to the numeric values of two possible output strings, the output string further away from zero is chosen. # Examples ```jldoctest julia> @printf("%f %F %f %F", Inf, Inf, NaN, NaN) Inf Inf NaN NaN julia> @printf "%.0f %.1f %f" 0.5 0.025 -0.0078125 0 0.0 -0.007812 ``` !!! compat "Julia 1.8" Starting in Julia 1.8, `%s` (string) and `%c` (character) widths are computed using [`textwidth`](@ref), which e.g. ignores zero-width characters (such as combining characters for diacritical marks) and treats certain "wide" characters (e.g. emoji) as width `2`. !!! compat "Julia 1.10" Dynamic width specifiers like `%*s` and `%0*.*f` require Julia 1.10. """ macro printf(io_or_fmt, args...) if io_or_fmt isa String fmt = Format(io_or_fmt) return esc(:($Printf.format(stdout, $fmt, $(args...)))) else io = io_or_fmt isempty(args) && throw(ArgumentError("No format string provided to `@printf` - use like `@printf [io] <format string> [<args...>].")) fmt_str = first(args) fmt_str isa String || throw(ArgumentError("First argument to `@printf` after `io` must be a format string")) fmt = Format(fmt_str) return esc(:($Printf.format($io, $fmt, $(Base.tail(args)...)))) end end """ @sprintf("%Fmt", args...) Return [`@printf`](@ref) formatted output as string. # Examples ```jldoctest julia> @sprintf "this is a %s %15.1f" "test" 34.567 "this is a test 34.6" ``` """ macro sprintf(fmt, args...) fmt isa String || throw(ArgumentError("First argument to `@sprintf` must be a format string.")) f = Format(fmt) return esc(:($Printf.format($f, $(args...)))) end end # module