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

JuliaLang / julia / #37474

pending completion
#37474

push

local

web-flow
irinterp: Allow setting all IR flags (#48993)

Currently, `IR_FLAG_NOTHROW` is the only flag that irinterp is allowed to
set on statements, under the assumption that in order for a call to
be irinterp-eligible, it must have been proven `:foldable`, thus `:effect_free`,
and thus `IR_FLAG_EFFECT_FREE` was assumed to have been set. That reasoning
was sound at the time this code was written, but have since introduced
`EFFECT_FREE_IF_INACCESSIBLEMEMONLY`, which breaks the reasoning that
an `:effect_free` inference for the whole function implies the flag on
every statement. As a result, we were failing to DCE otherwise dead
statements if the IR came from irinterp.

3 of 3 new or added lines in 1 file covered. (100.0%)

70258 of 82316 relevant lines covered (85.35%)

32461773.51 hits per line

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

73.18
/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
4,643✔
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
3,188✔
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
1,863✔
93
char(::Type{Val{c}}) where {c} = c
249✔
94

95
struct InvalidFormatStringError <: Exception
96
    message::String
13✔
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,607✔
125
    isempty(f) && throw(InvalidFormatStringError("Format string must not be empty", f, 1, 1))
1,607✔
126
    bytes = codeunits(f)
1,606✔
127
    len = length(bytes)
1,606✔
128
    pos = 1
×
129
    numarguments = 0
×
130

131
    b = 0x00
×
132
    local last_percent_pos
×
133

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

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

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

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

293

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

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

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

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

376
# integers
377
toint(x) = x
1,861✔
378
toint(x::Rational) = Integer(x)
2✔
379

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

864
@inline function plength(f::Spec{T}, args, argp) where {T}
1,571✔
865
    f, argp = rmdynamic(f, args, argp)
2,763✔
866
    (plength(f, args[argp]), argp+1)
2,816✔
867
end
868

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

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

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

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

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

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

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

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

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

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

939
function format(f::Format, args...) # => String
2,624✔
940
    f.numarguments == length(args) || argmismatch(f.numarguments, length(args))
2,625✔
941
    buf = Base.StringVector(computelen(f.substringranges, f.formats, args))
2,759✔
942
    pos = format(buf, 1, f, args...)
2,668✔
943
    return String(resize!(buf, pos - 1))
2,622✔
944
end
945

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1036
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