• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37616

10 Sep 2023 01:51AM UTC coverage: 86.489% (+0.3%) from 86.196%
#37616

push

local

web-flow
Make _global_logstate a typed global (#51257)

73902 of 85447 relevant lines covered (86.49%)

13068259.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.08
/stdlib/Printf/src/Printf.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
module Printf
4

5
using Base.Ryu
6

7
export @printf, @sprintf
8

9
# format specifier categories
10
const Ints = Union{Val{'d'}, Val{'i'}, Val{'u'}, Val{'x'}, Val{'X'}, Val{'o'}}
11
const Floats = Union{Val{'e'}, Val{'E'}, Val{'f'}, Val{'F'}, Val{'g'}, Val{'G'}, Val{'a'}, Val{'A'}}
12
const Chars = Union{Val{'c'}, Val{'C'}}
13
const Strings = Union{Val{'s'}, Val{'S'}}
14
const Pointer = Val{'p'}
15
const HexBases = Union{Val{'x'}, Val{'X'}, Val{'a'}, Val{'A'}}
16
const PositionCounter = Val{'n'}
17

18
const MAX_FRACTIONAL_PART_WIDTH = 17  # max significant decimals + 1: `ceil(Int, log10(1 / eps(Float64))) + 1`
19
const MAX_INTEGER_PART_WIDTH = 309  # max exponent: `ceil(Int, log10(prevfloat(typemax(Float64))))`
20
const MAX_FMT_CHARS_WIDTH = 5  # hash | sign +/- | decimal dot | exponent e/E | exponent sign
21

22
"""
23
Typed representation of a format specifier.
24

25
`T` is a `Val{'_'}`, where `_` is a valid format specifier character.
26

27
Fields are the various modifiers allowed for various format specifiers.
28
"""
29
struct Spec{T} # T => %type => Val{'type'}
30
    leftalign::Bool
5,200✔
31
    plus::Bool
32
    space::Bool
33
    zero::Bool
34
    hash::Bool
35
    width::Int
36
    precision::Int
37
    dynamic_width::Bool
38
    dynamic_precision::Bool
39
end
40

41
# recreate the format specifier string from a typed Spec
42
Base.string(f::Spec{T}; modifier::String="") where {T} =
164✔
43
    string("%",
44
           f.leftalign ? "-" : "",
45
           f.plus ? "+" : "",
46
           f.space ? " " : "",
47
           f.zero ? "0" : "",
48
           f.hash ? "#" : "",
49
           f.dynamic_width ? "*" : (f.width > 0 ? f.width : ""),
50
           f.dynamic_precision ? ".*" : (f.precision == 0 ? ".0" : (f.precision > 0 ? ".$(f.precision)" : "")),
51
           modifier,
52
           char(T))
53

54
Base.show(io::IO, f::Spec) = print(io, string(f))
×
55

56
floatfmt(s::Spec{T}) where {T} =
11✔
57
    Spec{Val{'f'}}(s.leftalign, s.plus, s.space, s.zero, s.hash, s.width, 0, s.dynamic_width, s.dynamic_precision)
58
ptrfmt(s::Spec{T}, x) where {T} =
13✔
59
    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)
60

61
"""
62
    Printf.Format(format_str)
63

64
Create a C printf-compatible format object that can be used for formatting values.
65

66
The input `format_str` can include any valid format specifier character and modifiers.
67

68
A `Format` object can be passed to `Printf.format(f::Format, args...)` to produce a
69
formatted string, or `Printf.format(io::IO, f::Format, args...)` to print the
70
formatted string directly to `io`.
71

72
For convenience, the `Printf.format"..."` string macro form can be used for building
73
a `Printf.Format` object at macro-expansion-time.
74

75
!!! compat "Julia 1.6"
76
    `Printf.Format` requires Julia 1.6 or later.
77
"""
78
struct Format{S, T}
79
    str::S # original full format string as CodeUnits
1,609✔
80
    # keep track of non-format specifier strings to print
81
    # length(substringranges) == length(formats) + 1
82
    # so when printing, we start with printing
83
      # str[substringranges[1]], then formats[1] + args[1]
84
      # then str[substringranges[2]], then formats[2] + args[2]
85
      # and so on, then at the end, str[substringranges[end]]
86
    substringranges::Vector{UnitRange{Int}}
87
    formats::T # Tuple of Specs
88
    numarguments::Int  # required for dynamic format specifiers
89
end
90

91
# what number base should be used for a given format specifier?
92
base(T) = T <: HexBases ? 16 : T <: Val{'o'} ? 8 : 10
2,179✔
93
char(::Type{Val{c}}) where {c} = c
249✔
94

95
struct InvalidFormatStringError <: Exception
96
    message::String
12✔
97
    format::String
98
    start_color::Int
99
    end_color::Int
100
end
101

102
function Base.showerror(io::IO, err::InvalidFormatStringError)
×
103
    io_has_color = get(io, :color, false)::Bool
×
104

105
    println(io, "InvalidFormatStringError: ", err.message)
×
106
    print(io, "    \"", @view(err.format[begin:prevind(err.format, err.start_color)]))
×
107
    invalid_text = @view err.format[err.start_color:err.end_color]
×
108

109
    printstyled(io, invalid_text, color=:red)
×
110

111
    # +1 is okay, since all format characters are single bytes
112
    println(io, @view(err.format[err.end_color+1:end]), "\"")
×
113

114
    arrow_error = '-'^(length(invalid_text)-1)
×
115
    arrow = "    " * ' '^err.start_color * arrow_error * "^\n"
×
116
    if io_has_color
×
117
        printstyled(io, arrow, color=:red)
×
118
    else
119
        print(io, arrow)
×
120
    end
121
end
122

123
# parse format string
124
function Format(f::AbstractString)
1,621✔
125
    bytes = codeunits(f)
1,621✔
126
    len = length(bytes)
1,621✔
127
    pos = 1
×
128
    numarguments = 0
×
129

130
    b = 0x00
×
131
    local last_percent_pos
×
132

133
    # skip ahead to first format specifier
134
    while pos <= len
2,033✔
135
        b = bytes[pos]
2,022✔
136
        pos += 1
2,022✔
137
        if b == UInt8('%')
2,022✔
138
            last_percent_pos = pos-1
1,619✔
139
            pos > len && throw(InvalidFormatStringError("Format specifier is incomplete", f, last_percent_pos, last_percent_pos))
1,619✔
140
            if bytes[pos] == UInt8('%')
1,619✔
141
                # escaped '%'
142
                b = bytes[pos]
9✔
143
                pos += 1
9✔
144
            else
145
                break
1,610✔
146
            end
147
        end
148
    end
412✔
149
    strs = [1:pos - 1 - (b == UInt8('%'))]
3,196✔
150
    fmts = []
1,621✔
151
    while pos <= len
3,338✔
152
        b = bytes[pos]
1,729✔
153
        pos += 1
1,729✔
154
        # positioned at start of first format str %
155
        # parse flags
156
        leftalign = plus = space = zero = hash = false
×
157
        while true
3,171✔
158
            if b == UInt8('-')
3,171✔
159
                leftalign = true
308✔
160
            elseif b == UInt8('+')
2,863✔
161
                plus = true
247✔
162
            elseif b == UInt8(' ')
2,616✔
163
                space = true
249✔
164
            elseif b == UInt8('0')
2,367✔
165
                zero = true
363✔
166
            elseif b == UInt8('#')
2,004✔
167
                hash = true
276✔
168
            else
169
                break
1,728✔
170
            end
171
            pos > len && throw(InvalidFormatStringError("Format specifier is incomplete", f, last_percent_pos, pos-1))
1,443✔
172
            b = bytes[pos]
1,442✔
173
            pos += 1
1,442✔
174
        end
1,442✔
175
        if leftalign
1,728✔
176
            zero = false
×
177
        end
178
        # parse width
179
        width = 0
1,728✔
180
        dynamic_width = false
×
181
        if b == UInt8('*')
1,728✔
182
            dynamic_width = true
×
183
            numarguments += 1
258✔
184
            b = bytes[pos]
258✔
185
            pos += 1
258✔
186
        else
187
            while b - UInt8('0') < 0x0a
2,300✔
188
            width = 10 * width + (b - UInt8('0'))
1,242✔
189
                b = bytes[pos]
1,242✔
190
                pos += 1
1,242✔
191
                pos > len && break
1,242✔
192
            end
830✔
193
        end
194
        # parse precision
195
        precision = 0
1,728✔
196
        parsedprecdigits = false
×
197
        dynamic_precision = false
×
198
        if b == UInt8('.')
1,728✔
199
            pos > len && throw(InvalidFormatStringError("Precision specifier is missing precision", f, last_percent_pos, pos-1))
771✔
200
            parsedprecdigits = true
×
201
            b = bytes[pos]
770✔
202
            pos += 1
770✔
203
            if pos <= len
770✔
204
                if b == UInt8('*')
727✔
205
                    dynamic_precision = true
×
206
                    numarguments += 1
181✔
207
                    b = bytes[pos]
181✔
208
                    pos += 1
181✔
209
                else
210
                    precision = 0
×
211
                    while b - UInt8('0') < 0x0a
684✔
212
                        precision = 10precision + (b - UInt8('0'))
612✔
213
                        b = bytes[pos]
612✔
214
                        pos += 1
612✔
215
                        pos > len && break
612✔
216
                    end
138✔
217
                end
218
            end
219
        end
220
        # parse length modifier (ignored)
221
        if b == UInt8('h') || b == UInt8('l')
3,452✔
222
            prev = b
×
223
            pos > len && throw(InvalidFormatStringError("Length modifier is missing type specifier", f, last_percent_pos, pos-1))
109✔
224
            b = bytes[pos]
108✔
225
            pos += 1
108✔
226
            if b == prev
108✔
227
                pos > len && throw(InvalidFormatStringError("Length modifier is missing type specifier", f, last_percent_pos, pos-1))
108✔
228
                b = bytes[pos]
107✔
229
                pos += 1
107✔
230
            end
231
        elseif b in b"Ljqtz" # q was a synonym for ll above, see `man 3 printf`. Not to be used.
1,618✔
232
            pos > len && throw(InvalidFormatStringError("Length modifier is missing type specifier", f, last_percent_pos, pos-1))
43✔
233
            b = bytes[pos]
42✔
234
            pos += 1
42✔
235
        end
236
        # parse type
237
        !(b in b"diouxXDOUeEfFgGaAcCsSpn") && throw(InvalidFormatStringError("'$(Char(b))' is not a valid type specifier", f, last_percent_pos, pos-1))
1,724✔
238
        type = Val{Char(b)}
1,718✔
239
        if type <: Ints && precision > 0
1,718✔
240
            # note - we should also set zero to false if dynamic precison > 0
241
            # this is taken care of in fmt() for Ints
242
            zero = false
132✔
243
        elseif (type <: Strings || type <: Chars) && !parsedprecdigits
3,058✔
244
            precision = -1
104✔
245
        elseif type <: Union{Val{'a'}, Val{'A'}} && !parsedprecdigits
1,482✔
246
            precision = -1
11✔
247
        elseif type <: Floats && !parsedprecdigits
1,471✔
248
            precision = 6
×
249
        end
250
        numarguments += 1
1,718✔
251
        push!(fmts, Spec{type}(leftalign, plus, space, zero, hash, width, precision, dynamic_width, dynamic_precision))
1,718✔
252
        start = pos
×
253
        while pos <= len
2,107✔
254
            b = bytes[pos]
509✔
255
            pos += 1
509✔
256
            if b == UInt8('%')
509✔
257
                last_percent_pos = pos-1
122✔
258
                pos > len && throw(InvalidFormatStringError("Format specifier is incomplete", f, last_percent_pos, last_percent_pos))
122✔
259
                if bytes[pos] == UInt8('%')
121✔
260
                    # escaped '%'
261
                    b = bytes[pos]
2✔
262
                    pos += 1
2✔
263
                else
264
                    break
119✔
265
                end
266
            end
267
        end
389✔
268
        push!(strs, start:pos - 1 - (b == UInt8('%')))
3,302✔
269
    end
1,717✔
270
    return Format(bytes, strs, Tuple(fmts), numarguments)
1,609✔
271
end
272

273
macro format_str(str)
274
    Format(str)
275
end
276

277
const hex = b"0123456789abcdef"
278
const HEX = b"0123456789ABCDEF"
279

280
# write out a single arg according to format options
281
# char
282
@inline function writechar(buf, pos, c)
421✔
283
    u = bswap(reinterpret(UInt32, c))
421✔
284
    while true
458✔
285
        buf[pos] = u % UInt8
458✔
286
        pos += 1
458✔
287
        (u >>= 8) == 0 && break
458✔
288
    end
37✔
289
    return pos
421✔
290
end
291

292

293
@inline function rmdynamic(spec::Spec{T}, args, argp) where {T}
3,458✔
294
    zero, width, precision = spec.zero, spec.width, spec.precision
5,694✔
295
    if spec.dynamic_width
5,694✔
296
        width = args[argp]
507✔
297
        argp += 1
516✔
298
    end
299
    if spec.dynamic_precision
5,694✔
300
        precision = args[argp]
362✔
301
        if zero && T <: Ints && precision > 0
362✔
302
            zero = false
30✔
303
        end
304
        argp += 1
362✔
305
    end
306
    (Spec{T}(spec.leftalign, spec.plus, spec.space, zero, spec.hash, width, precision, false, false), argp)
5,802✔
307
end
308

309
@inline function fmt(buf, pos, args, argp, spec::Spec{T}) where {T}
1,729✔
310
    spec, argp = rmdynamic(spec, args, argp)
2,901✔
311
    (fmt(buf, pos, args[argp], spec), argp+1)
4,189✔
312
end
313

314
@inline function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Chars}
39✔
315
    leftalign, width = spec.leftalign, spec.width
39✔
316
    c = Char(first(arg))
40✔
317
    w = textwidth(c)
39✔
318
    if !leftalign && width > w
39✔
319
        for _ = 1:(width - w)
20✔
320
            buf[pos] = UInt8(' ')
70✔
321
            pos += 1
70✔
322
        end
130✔
323
    end
324
    pos = writechar(buf, pos, c)
51✔
325
    if leftalign && width > w
39✔
326
        for _ = 1:(width - w)
18✔
327
            buf[pos] = UInt8(' ')
120✔
328
            pos += 1
120✔
329
        end
231✔
330
    end
331
    return pos
39✔
332
end
333

334
# strings
335
@inline function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Strings}
112✔
336
    leftalign, hash, width, prec = spec.leftalign, spec.hash, spec.width, spec.precision
112✔
337
    str = string(arg)
112✔
338
    slen = textwidth(str)::Int + (hash ? arg isa AbstractString ? 2 : 1 : 0)
112✔
339
    op = p = prec == -1 ? slen : min(slen, prec)
161✔
340
    if !leftalign && width > p
112✔
341
        for _ = 1:(width - p)
72✔
342
            buf[pos] = UInt8(' ')
311✔
343
            pos += 1
311✔
344
        end
586✔
345
    end
346
    if hash
112✔
347
        if arg isa Symbol
19✔
348
            buf[pos] = UInt8(':')
7✔
349
            pos += 1
7✔
350
            p -= 1
7✔
351
        elseif arg isa AbstractString
12✔
352
            buf[pos] = UInt8('"')
12✔
353
            pos += 1
12✔
354
            p -= 1
12✔
355
        end
356
    end
357
    for c in str
214✔
358
        p -= textwidth(c)
425✔
359
        p < 0 && break
425✔
360
        pos = writechar(buf, pos, c)
407✔
361
    end
382✔
362
    if hash && arg isa AbstractString && p > 0
112✔
363
        buf[pos] = UInt8('"')
×
364
        pos += 1
×
365
    end
366
    if leftalign && width > op
112✔
367
        for _ = 1:(width - op)
42✔
368
            buf[pos] = UInt8(' ')
150✔
369
            pos += 1
150✔
370
        end
279✔
371
    end
372
    return pos
112✔
373
end
374

375
# integers
376
toint(x) = x
2,177✔
377
toint(x::Rational) = Integer(x)
2✔
378

379
fmt(buf, pos, arg::AbstractFloat, spec::Spec{T}) where {T <: Ints} =
11✔
380
    fmt(buf, pos, arg, floatfmt(spec))
381

382
@inline function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Ints}
1,096✔
383
    leftalign, plus, space, zero, hash, width, prec =
1,096✔
384
        spec.leftalign, spec.plus, spec.space, spec.zero, spec.hash, spec.width, spec.precision
385
    bs = base(T)
1,096✔
386
    arg2 = toint(arg)
1,096✔
387
    n = i = ndigits(arg2, base=bs, pad=1)
1,096✔
388
    neg = arg2 < 0
1,096✔
389
    x = arg2 isa Base.BitSigned ? unsigned(abs(arg2)) : abs(arg2)
1,106✔
390
    arglen = n + (neg || (plus | space)) +
1,960✔
391
        (T == Val{'o'} && hash ? 1 : 0) +
392
        (T == Val{'x'} && hash ? 2 : 0) + (T == Val{'X'} && hash ? 2 : 0)
393
    arglen2 = arglen < width && prec > 0 ? arglen + min(max(0, prec - n), width - arglen) : arglen
1,096✔
394
    if !leftalign && !zero && arglen2 < width
1,096✔
395
        # pad left w/ spaces
396
        for _ = 1:(width - arglen2)
668✔
397
            buf[pos] = UInt8(' ')
2,901✔
398
            pos += 1
2,901✔
399
        end
5,468✔
400
    end
401
    if neg
1,096✔
402
        buf[pos] = UInt8('-'); pos += 1
464✔
403
    elseif plus # plus overrides space
864✔
404
        buf[pos] = UInt8('+'); pos += 1
170✔
405
    elseif space
779✔
406
        buf[pos] = UInt8(' '); pos += 1
138✔
407
    end
408
    if T == Val{'o'} && hash
1,096✔
409
        buf[pos] = UInt8('0')
48✔
410
        pos += 1
48✔
411
    elseif T == Val{'x'} && hash
1,048✔
412
        buf[pos] = UInt8('0')
67✔
413
        buf[pos + 1] = UInt8('x')
67✔
414
        pos += 2
67✔
415
    elseif T == Val{'X'} && hash
981✔
416
        buf[pos] = UInt8('0')
36✔
417
        buf[pos + 1] = UInt8('X')
36✔
418
        pos += 2
36✔
419
    end
420
    if zero && arglen2 < width
1,096✔
421
        for _ = 1:(width - arglen2)
266✔
422
            buf[pos] = UInt8('0')
890✔
423
            pos += 1
890✔
424
        end
1,647✔
425
    elseif n < prec
963✔
426
        for _ = 1:(prec - n)
382✔
427
            buf[pos] = UInt8('0')
1,747✔
428
            pos += 1
1,747✔
429
        end
1,747✔
430
    elseif arglen < arglen2
772✔
431
        for _ = 1:(arglen2 - arglen)
×
432
            buf[pos] = UInt8('0')
×
433
            pos += 1
×
434
        end
×
435
    end
436
    while i > 0
5,539✔
437
        @inbounds buf[pos + i - 1] = bs == 16 ?
4,443✔
438
            (T == Val{'x'} ? hex[(x & 0x0f) + 1] : HEX[(x & 0x0f) + 1]) :
439
            (48 + (bs == 8 ? (x & 0x07) : rem(x, 10)))
440
        if bs == 8
4,443✔
441
            x >>= 3
619✔
442
        elseif bs == 16
3,824✔
443
            x >>= 4
1,407✔
444
        else
445
            x = oftype(x, div(x, 10))
2,417✔
446
        end
447
        i -= 1
4,443✔
448
    end
4,443✔
449
    pos += n
1,096✔
450
    if leftalign && arglen2 < width
1,096✔
451
        # pad right
452
        for _ = 1:(width - arglen2)
370✔
453
            buf[pos] = UInt8(' ')
1,323✔
454
            pos += 1
1,323✔
455
        end
2,461✔
456
    end
457
    return pos
1,096✔
458
end
459

460
# floats
461
"""
462
    Printf.tofloat(x)
463

464
Convert an argument to a Base float type for printf formatting.
465
By default, arguments are converted to `Float64` via `Float64(x)`.
466
Custom numeric types that have a conversion to a Base float type
467
that wish to hook into printf formatting can extend this method like:
468

469
```julia
470
Printf.tofloat(x::MyCustomType) = convert_my_custom_type_to_float(x)
471
```
472

473
For arbitrary precision numerics, you might extend the method like:
474

475
```julia
476
Printf.tofloat(x::MyArbitraryPrecisionType) = BigFloat(x)
477
```
478

479
!!! compat "Julia 1.6"
480
    This function requires Julia 1.6 or later.
481
"""
482
tofloat(x) = Float64(x)
58✔
483
tofloat(x::Base.IEEEFloat) = x
1✔
484
tofloat(x::BigFloat) = x
57✔
485

486
_snprintf(ptr, siz, str, arg) =
50✔
487
    @ccall "libmpfr".mpfr_snprintf(ptr::Ptr{UInt8}, siz::Csize_t, str::Ptr{UInt8};
488
                                   arg::Ref{BigFloat})::Cint
489

490
# Arbitrary constant for a maximum number of bytes we want to output for a BigFloat.
491
# 8KiB seems like a reasonable default. Larger BigFloat representations should probably
492
# use a custom printing routine. Printing values with results larger than this ourselves
493
# seems like a dangerous thing to do.
494
const __BIG_FLOAT_MAX__ = 8192
495

496
@inline function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Floats}
494✔
497
    leftalign, plus, space, zero, hash, width, prec =
475✔
498
        spec.leftalign, spec.plus, spec.space, spec.zero, spec.hash, spec.width, spec.precision
499
    x = tofloat(arg)
475✔
500
    if x isa BigFloat
474✔
501
        if isfinite(x)
61✔
502
            GC.@preserve buf begin
49✔
503
                siz = length(buf) - pos + 1
49✔
504
                str = string(spec; modifier="R")
79✔
505
                required_length = _snprintf(pointer(buf, pos), siz, str, x)
49✔
506
                if required_length > siz
49✔
507
                    required_length > __BIG_FLOAT_MAX__ &&
2✔
508
                        throw(ArgumentError("The given BigFloat requires $required_length bytes to be printed, which is more than the maximum of $__BIG_FLOAT_MAX__ bytes supported."))
509
                    resize!(buf, required_length + 1)
1✔
510
                    required_length = _snprintf(pointer(buf, pos), required_length + 1, str, x)
1✔
511
                end
512
                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."))
48✔
513
                return pos + required_length
48✔
514
            end
515
        end
516
        x = Float64(x)
8✔
517
    end
518
    if T == Val{'e'} || T == Val{'E'}
425✔
519
        newpos = Ryu.writeexp(buf, pos, x, prec, plus, space, hash, char(T), UInt8('.'))
198✔
520
    elseif T == Val{'f'} || T == Val{'F'}
227✔
521
        newpos = Ryu.writefixed(buf, pos, x, prec, plus, space, hash, UInt8('.'))
1,210✔
522
    elseif T == Val{'g'} || T == Val{'G'}
136✔
523
        if isinf(x) || isnan(x)
130✔
524
            newpos = Ryu.writeshortest(buf, pos, x, plus, space)
8✔
525
        else
526
            # C11-compliant general format
527
            prec = prec == 0 ? 1 : prec
59✔
528
            # format the value in scientific notation and parse the exponent part
529
            exp = let p = Ryu.writeexp(buf, pos, x, prec)
59✔
530
                b1, b2, b3, b4 = buf[p-4], buf[p-3], buf[p-2], buf[p-1]
59✔
531
                Z = UInt8('0')
59✔
532
                if b1 == UInt8('e')
59✔
533
                    # two-digit exponent
534
                    sign = b2 == UInt8('+') ? 1 : -1
56✔
535
                    exp = 10 * (b3 - Z) + (b4 - Z)
56✔
536
                else
537
                    # three-digit exponent
538
                    sign = b1 == UInt8('+') ? 1 : -1
3✔
539
                    exp = 100 * (b2 - Z) + 10 * (b3 - Z) + (b4 - Z)
3✔
540
                end
541
                flipsign(exp, sign)
59✔
542
            end
543
            if -4 ≤ exp < prec
59✔
544
                newpos = Ryu.writefixed(buf, pos, x, prec - (exp + 1), plus, space, hash, UInt8('.'), !hash)
45✔
545
            else
546
                newpos = Ryu.writeexp(buf, pos, x, prec - 1, plus, space, hash, T == Val{'g'} ? UInt8('e') : UInt8('E'), UInt8('.'), !hash)
81✔
547
            end
548
        end
549
    elseif T == Val{'a'} || T == Val{'A'}
69✔
550
        x, neg = x < 0 || x === -Base.zero(x) ? (-x, true) : (x, false)
120✔
551
        newpos = pos
69✔
552
        if neg
69✔
553
            buf[newpos] = UInt8('-')
19✔
554
            newpos += 1
19✔
555
        elseif plus
50✔
556
            buf[newpos] = UInt8('+')
7✔
557
            newpos += 1
7✔
558
        elseif space
43✔
559
            buf[newpos] = UInt8(' ')
6✔
560
            newpos += 1
6✔
561
        end
562
        if isnan(x)
69✔
563
            buf[newpos] = UInt8('N')
×
564
            buf[newpos + 1] = UInt8('a')
×
565
            buf[newpos + 2] = UInt8('N')
×
566
            newpos += 3
×
567
        elseif !isfinite(x)
69✔
568
            buf[newpos] = UInt8('I')
×
569
            buf[newpos + 1] = UInt8('n')
×
570
            buf[newpos + 2] = UInt8('f')
×
571
            newpos += 3
×
572
        else
573
            buf[newpos] = UInt8('0')
69✔
574
            newpos += 1
69✔
575
            buf[newpos] = T <: Val{'a'} ? UInt8('x') : UInt8('X')
69✔
576
            newpos += 1
69✔
577
            if arg == 0
69✔
578
                buf[newpos] = UInt8('0')
3✔
579
                newpos += 1
3✔
580
                if prec > 0
3✔
581
                    buf[newpos] = UInt8('.')
1✔
582
                    newpos += 1
1✔
583
                    while prec > 0
4✔
584
                        buf[newpos] = UInt8('0')
3✔
585
                        newpos += 1
3✔
586
                        prec -= 1
3✔
587
                    end
3✔
588
                end
589
                buf[newpos] = T <: Val{'a'} ? UInt8('p') : UInt8('P')
3✔
590
                buf[newpos + 1] = UInt8('+')
3✔
591
                buf[newpos + 2] = UInt8('0')
3✔
592
                newpos += 3
3✔
593
            else
594
                if prec > -1
66✔
595
                    s, p = frexp(x)
120✔
596
                    sigbits = 4 * min(prec, 13)
60✔
597
                    s = 0.25 * round(ldexp(s, 1 + sigbits))
120✔
598
                    # ensure last 2 exponent bits either 01 or 10
599
                    u = (reinterpret(UInt64, s) & 0x003f_ffff_ffff_ffff) >> (52 - sigbits)
60✔
600
                    i = n = (sizeof(u) << 1) - (leading_zeros(u) >> 2)
60✔
601
                else
602
                    s, p = frexp(x)
12✔
603
                    s *= 2.0
6✔
604
                    u = (reinterpret(UInt64, s) & 0x001f_ffff_ffff_ffff)
6✔
605
                    t = (trailing_zeros(u) >> 2)
6✔
606
                    u >>= (t << 2)
6✔
607
                    i = n = 14 - t
6✔
608
                end
609
                frac = u > 9 || hash || prec > 0
73✔
610
                while i > 1
220✔
611
                    buf[newpos + i] = T == Val{'a'} ? hex[(u & 0x0f) + 1] : HEX[(u & 0x0f) + 1]
154✔
612
                    u >>= 4
154✔
613
                    i -= 1
154✔
614
                    prec -= 1
154✔
615
                end
154✔
616
                if frac
66✔
617
                    buf[newpos + 1] = UInt8('.')
63✔
618
                end
619
                buf[newpos] = T == Val{'a'} ? hex[(u & 0x0f) + 1] : HEX[(u & 0x0f) + 1]
66✔
620
                newpos += n + frac
66✔
621
                while prec > 0
66✔
622
                    buf[newpos] = UInt8('0')
×
623
                    newpos += 1
×
624
                    prec -= 1
×
625
                end
×
626
                buf[newpos] = T <: Val{'a'} ? UInt8('p') : UInt8('P')
66✔
627
                newpos += 1
66✔
628
                p -= 1
66✔
629
                buf[newpos] = p < 0 ? UInt8('-') : UInt8('+')
66✔
630
                p = p < 0 ? -p : p
66✔
631
                newpos += 1
66✔
632
                n = i = ndigits(p, base=10, pad=1)
66✔
633
                while i > 0
132✔
634
                    buf[newpos + i - 1] = 48 + rem(p, 10)
66✔
635
                    p = oftype(p, div(p, 10))
66✔
636
                    i -= 1
66✔
637
                end
66✔
638
                newpos += n
66✔
639
            end
640
        end
641
    end
642
    if newpos - pos < width
1,544✔
643
        # need to pad
644
        if leftalign
883✔
645
            # easy case, just pad spaces after number
646
            for _ = 1:(width - (newpos - pos))
132✔
647
                buf[newpos] = UInt8(' ')
155✔
648
                newpos += 1
155✔
649
            end
244✔
650
        else
651
            # right aligned
652
            n = width - (newpos - pos)
817✔
653
            if zero
817✔
654
                ex = (arg < 0 || (plus | space)) + (T <: Union{Val{'a'}, Val{'A'}} ? 2 : 0)
190✔
655
                so = pos + ex
113✔
656
                len = (newpos - pos) - ex
113✔
657
                copyto!(buf, so + n, buf, so, len)
113✔
658
                for i = so:(so + n - 1)
226✔
659
                    buf[i] = UInt8('0')
334✔
660
                end
555✔
661
                newpos += n
113✔
662
            else
663
                copyto!(buf, pos + n, buf, pos, newpos - pos)
704✔
664
                for i = pos:(pos + n - 1)
1,408✔
665
                    buf[i] = UInt8(' ')
1,092✔
666
                end
1,480✔
667
                newpos += n
704✔
668
            end
669
        end
670
    end
671
    return newpos
1,544✔
672
end
673

674
# pointers
675
fmt(buf, pos, arg, spec::Spec{Pointer}) = fmt(buf, pos, UInt64(arg), ptrfmt(spec, arg))
13✔
676

677
# position counters
678
function fmt(buf, pos, arg::Ref{<:Integer}, ::Spec{PositionCounter})
7✔
679
    arg[] = pos - 1
7✔
680
    pos
7✔
681
end
682

683
# old Printf compat
684
function fix_dec end
685
function ini_dec end
686

687
# generic fallback
688
function fmtfallback(buf, pos, arg, spec::Spec{T}) where {T}
×
689
    leftalign, plus, space, zero, hash, width, prec =
×
690
        spec.leftalign, spec.plus, spec.space, spec.zero, spec.hash, spec.width, spec.precision
691
    buf2 = Base.StringVector(
×
692
        MAX_INTEGER_PART_WIDTH + MAX_FRACTIONAL_PART_WIDTH + MAX_FMT_CHARS_WIDTH
693
    )
694
    ise = T <: Union{Val{'e'}, Val{'E'}}
×
695
    isg = T <: Union{Val{'g'}, Val{'G'}}
×
696
    isf = T <: Val{'f'}
×
697
    if isg
×
698
        prec = prec == 0 ? 1 : prec
×
699
        arg = round(arg, sigdigits=prec)
×
700
    end
701
    n, pt, neg = isf ? fix_dec(arg, prec, buf2) : ini_dec(arg, min(prec + ise, length(buf2) - 1), buf2)
×
702
    if isg && !hash
×
703
        while buf2[n] == UInt8('0')
×
704
            n -= 1
×
705
        end
×
706
    end
707
    expform = ise || (isg && !(-4 < pt <= prec))
×
708
    n2 = n + (expform ? 4 : 0) + (prec > 0 || hash) + (neg || (plus | space)) +
×
709
        (isf && pt >= n ? prec + 1 : 0)
710
    if !leftalign && !zero && n2 < width
×
711
        # pad left w/ spaces
712
        for _ = 1:(width - n2)
×
713
            buf[pos] = UInt8(' ')
×
714
            pos += 1
×
715
        end
×
716
    end
717
    if neg
×
718
        buf[pos] = UInt8('-'); pos += 1
×
719
    elseif plus # plus overrides space
×
720
        buf[pos] = UInt8('+'); pos += 1
×
721
    elseif space
×
722
        buf[pos] = UInt8(' '); pos += 1
×
723
    end
724
    if zero && n2 < width
×
725
        for _ = 1:(width - n2)
×
726
            buf[pos] = UInt8('0')
×
727
            pos += 1
×
728
        end
×
729
    end
730
    if expform
×
731
        buf[pos] = buf2[1]
×
732
        pos += 1
×
733
        if n > 1 || hash
×
734
            buf[pos] = UInt8('.')
×
735
            pos += 1
×
736
            for i = 2:n
×
737
                buf[pos] = buf2[i]
×
738
                pos += 1
×
739
            end
×
740
        end
741
        buf[pos] = T <: Val{'e'} || T <: Val{'g'} ? UInt8('e') : UInt8('E')
×
742
        pos += 1
×
743
        exp = pt - 1
×
744
        buf[pos] = exp < 0 ? UInt8('-') : UInt8('+')
×
745
        pos += 1
×
746
        exp = abs(exp)
×
747
        if exp < 10
×
748
            buf[pos] = UInt8('0')
×
749
            buf[pos + 1] = 48 + exp
×
750
            pos += 2
×
751
        else
752
            buf[pos] = 48 + div(exp, 10)
×
753
            buf[pos + 1] = 48 + rem(exp, 10)
×
754
            pos += 2
×
755
        end
756
    elseif pt <= 0
×
757
        buf[pos] = UInt8('0')
×
758
        buf[pos + 1] = UInt8('.')
×
759
        pos += 2
×
760
        while pt < 0
×
761
            buf[pos] = UInt8('0')
×
762
            pos += 1
×
763
            pt += 1
×
764
        end
×
765
        for i = 1:n
×
766
            buf[pos] = buf2[i]
×
767
            pos += 1
×
768
        end
×
769
    elseif pt >= n
×
770
        for i = 1:n
×
771
            buf[pos] = buf2[i]
×
772
            pos += 1
×
773
        end
×
774
        while pt > n
×
775
            buf[pos] = UInt8('0')
×
776
            pos += 1
×
777
            n += 1
×
778
        end
×
779
        if hash || (isf && prec > 0)
×
780
            buf[pos] = UInt8('.')
×
781
            pos += 1
×
782
            while prec > 0
×
783
                buf[pos] = UInt8('0')
×
784
                pos += 1
×
785
                prec -= 1
×
786
            end
×
787
        end
788
    else
789
        for i = 1:pt
×
790
            buf[pos] = buf2[i]
×
791
            pos += 1
×
792
        end
×
793
        buf[pos] = UInt8('.')
×
794
        pos += 1
×
795
        for i = pt+1:n
×
796
            buf[pos] = buf2[i]
×
797
            pos += 1
×
798
        end
×
799
    end
800

801
    if leftalign && n2 < width
×
802
        # pad right
803
        for _ = 1:(width - n2)
×
804
            buf[pos] = UInt8(' ')
×
805
            pos += 1
×
806
        end
×
807
    end
808
    return pos
×
809
end
810

811
const UNROLL_UPTO = 16
812
# if you have your own buffer + pos, write formatted args directly to it
813
@inline function format(buf::Vector{UInt8}, pos::Integer, f::Format, args...)
2,663✔
814
    # write out first substring
815
    escapechar = false
1,545✔
816
    for i in f.substringranges[1]
2,708✔
817
        b = f.str[i]
417✔
818
        if !escapechar
417✔
819
            buf[pos] = b
411✔
820
            pos += 1
411✔
821
            escapechar = b === UInt8('%')
411✔
822
        else
823
            escapechar = false
6✔
824
        end
825
    end
789✔
826
    # for each format, write out arg and next substring
827
    # unroll up to 16 formats
828
    N = length(f.formats)
2,663✔
829
    argp = 1
1,545✔
830
    Base.@nexprs 16 i -> begin
×
831
        if N >= i
25,823✔
832
            pos, argp = fmt(buf, pos, args, argp, f.formats[i])
4,183✔
833
            for j in f.substringranges[i + 1]
3,080✔
834
                b = f.str[j]
1,361✔
835
                if !escapechar
1,361✔
836
                    buf[pos] = b
1,360✔
837
                    pos += 1
1,360✔
838
                    escapechar = b === UInt8('%')
1,360✔
839
                else
840
                    escapechar = false
1✔
841
                end
842
            end
2,482✔
843
        end
844
    end
845
    if N > 16
1,544✔
846
        for i = 17:length(f.formats)
3✔
847
            pos, argp = fmt(buf, pos, args, argp, f.formats[i])
6✔
848
            for j in f.substringranges[i + 1]
9✔
849
                b = f.str[j]
3✔
850
                if !escapechar
3✔
851
                    buf[pos] = b
3✔
852
                    pos += 1
3✔
853
                    escapechar = b === UInt8('%')
3✔
854
                else
855
                    escapechar = false
×
856
                end
857
            end
3✔
858
        end
9✔
859
    end
860
    return pos
2,662✔
861
end
862

863
@inline function plength(f::Spec{T}, args, argp) where {T}
1,729✔
864
    f, argp = rmdynamic(f, args, argp)
2,901✔
865
    (plength(f, args[argp]), argp+1)
2,955✔
866
end
867

868
function plength(f::Spec{T}, x) where {T <: Chars}
39✔
869
    c = Char(first(x))
40✔
870
    w = textwidth(c)
39✔
871
    return max(f.width, w) + (ncodeunits(c) - w)
51✔
872
end
873

874
plength(f::Spec{Pointer}, x) = max(f.width, 2 * sizeof(x) + 2)
13✔
875

876
function plength(f::Spec{T}, x) where {T <: Strings}
112✔
877
    str = string(x)
112✔
878
    sw = textwidth(str)
112✔
879
    p = f.precision == -1 ? (sw + (f.hash ? (x isa Symbol ? 1 : 2) : 0)) : f.precision
161✔
880
    return max(f.width, p) + (sizeof(str) - sw)
112✔
881
end
882

883
function plength(f::Spec{T}, x) where {T <: Ints}
1,083✔
884
    x2 = toint(x)
1,083✔
885
    return max(
1,083✔
886
        f.width,
887
        f.precision + ndigits(x2, base=base(T), pad=1) + MAX_FMT_CHARS_WIDTH
888
    )
889
end
890

891
plength(f::Spec{T}, x::AbstractFloat) where {T <: Ints} =
11✔
892
    max(f.width, f.hash + MAX_INTEGER_PART_WIDTH + 0 + MAX_FMT_CHARS_WIDTH)
893
plength(f::Spec{T}, x) where {T <: Floats} =
1,582✔
894
    max(f.width, f.hash + MAX_INTEGER_PART_WIDTH + f.precision + MAX_FMT_CHARS_WIDTH)
895
plength(::Spec{PositionCounter}, x) = 0
7✔
896

897
@inline function computelen(substringranges, formats, args)
1,545✔
898
    len = sum(length, substringranges)
5,316✔
899
    N = length(formats)
1,545✔
900
    # unroll up to 16 formats
901
    argp = 1
1,545✔
902
    Base.@nexprs 16 i -> begin
×
903
        if N >= i
24,720✔
904
            l, argp = plength(formats[i], args, argp)
2,979✔
905
            len += l
2,841✔
906
        end
907
    end
908
    if N > 16
1,545✔
909
        for i = 17:length(formats)
3✔
910
            l, argp = plength(formats[i], args, argp)
8✔
911
            len += l
6✔
912
        end
9✔
913
    end
914
    return len
2,663✔
915
end
916

917
@noinline argmismatch(a, b) =
1✔
918
    throw(ArgumentError("Number of format specifiers and number of provided args differ: $a != $b"))
919

920
"""
921
    Printf.format(f::Printf.Format, args...) => String
922
    Printf.format(io::IO, f::Printf.Format, args...)
923

924
Apply a printf format object `f` to provided `args` and return the formatted string
925
(1st method), or print directly to an `io` object (2nd method). See [`@printf`](@ref)
926
for more details on C `printf` support.
927
"""
928
function format end
929

930
function format(io::IO, f::Format, args...) # => Nothing
16✔
931
    f.numarguments == length(args) || argmismatch(f.numarguments, length(args))
16✔
932
    buf = Base.StringVector(computelen(f.substringranges, f.formats, args))
32✔
933
    pos = format(buf, 1, f, args...)
16✔
934
    write(io, resize!(buf, pos - 1))
32✔
935
    return
16✔
936
end
937

938
function format(f::Format, args...) # => String
2,648✔
939
    f.numarguments == length(args) || argmismatch(f.numarguments, length(args))
2,649✔
940
    buf = Base.StringVector(computelen(f.substringranges, f.formats, args))
3,508✔
941
    pos = format(buf, 1, f, args...)
2,692✔
942
    return String(resize!(buf, pos - 1))
2,646✔
943
end
944

945
"""
946
    @printf([io::IO], "%Fmt", args...)
947

948
Print `args` using C `printf` style format specification string.
949
Optionally, an `IO` may be passed as the first argument to redirect output.
950

951
# Examples
952
```jldoctest
953
julia> @printf "Hello %s" "world"
954
Hello world
955

956
julia> @printf "Scientific notation %e" 1.234
957
Scientific notation 1.234000e+00
958

959
julia> @printf "Scientific notation three digits %.3e" 1.23456
960
Scientific notation three digits 1.235e+00
961

962
julia> @printf "Decimal two digits %.2f" 1.23456
963
Decimal two digits 1.23
964

965
julia> @printf "Padded to length 5 %5i" 123
966
Padded to length 5   123
967

968
julia> @printf "Padded with zeros to length 6 %06i" 123
969
Padded with zeros to length 6 000123
970

971
julia> @printf "Use shorter of decimal or scientific %g %g" 1.23 12300000.0
972
Use shorter of decimal or scientific 1.23 1.23e+07
973

974
julia> @printf "Use dynamic width and precision  %*.*f" 10 2 0.12345
975
Use dynamic width and precision        0.12
976
```
977
For a systematic specification of the format, see [here](https://en.cppreference.com/w/c/io/fprintf).
978
See also [`@sprintf`](@ref) to get the result as a `String` instead of it being printed.
979

980
# Caveats
981
`Inf` and `NaN` are printed consistently as `Inf` and `NaN` for flags `%a`, `%A`,
982
`%e`, `%E`, `%f`, `%F`, `%g`, and `%G`. Furthermore, if a floating point number is
983
equally close to the numeric values of two possible output strings, the output
984
string further away from zero is chosen.
985

986
# Examples
987
```jldoctest
988
julia> @printf("%f %F %f %F", Inf, Inf, NaN, NaN)
989
Inf Inf NaN NaN
990

991
julia> @printf "%.0f %.1f %f" 0.5 0.025 -0.0078125
992
0 0.0 -0.007812
993
```
994

995
!!! compat "Julia 1.8"
996
    Starting in Julia 1.8, `%s` (string) and `%c` (character) widths are computed
997
    using [`textwidth`](@ref), which e.g. ignores zero-width characters
998
    (such as combining characters for diacritical marks) and treats certain
999
    "wide" characters (e.g. emoji) as width `2`.
1000

1001
!!! compat "Julia 1.10"
1002
    Dynamic width specifiers like `%*s` and `%0*.*f` require Julia 1.10.
1003
"""
1004
macro printf(io_or_fmt, args...)
12✔
1005
    if io_or_fmt isa String
12✔
1006
        fmt = Format(io_or_fmt)
×
1007
        return esc(:($Printf.format(stdout, $fmt, $(args...))))
×
1008
    else
1009
        io = io_or_fmt
12✔
1010
        isempty(args) && throw(ArgumentError("No format string provided to `@printf` - use like `@printf [io] <format string> [<args...>]."))
12✔
1011
        fmt_str = first(args)
12✔
1012
        fmt_str isa String || throw(ArgumentError("First argument to `@printf` after `io` must be a format string"))
12✔
1013
        fmt = Format(fmt_str)
12✔
1014
        return esc(:($Printf.format($io, $fmt, $(Base.tail(args)...))))
12✔
1015
    end
1016
end
1017

1018
"""
1019
    @sprintf("%Fmt", args...)
1020

1021
Return [`@printf`](@ref) formatted output as string.
1022

1023
# Examples
1024
```jldoctest
1025
julia> @sprintf "this is a %s %15.1f" "test" 34.567
1026
"this is a test            34.6"
1027
```
1028
"""
1029
macro sprintf(fmt, args...)
1,143✔
1030
    fmt isa String || throw(ArgumentError("First argument to `@sprintf` must be a format string."))
1,143✔
1031
    f = Format(fmt)
1,143✔
1032
    return esc(:($Printf.format($f, $(args...))))
1,143✔
1033
end
1034

1035
end # module
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc