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

JuliaLang / julia / #37919

29 Sep 2024 09:41AM UTC coverage: 86.232% (-0.3%) from 86.484%
#37919

push

local

web-flow
fix rawbigints OOB issues (#55917)

Fixes issues introduced in #50691 and found in #55906:
* use `@inbounds` and `@boundscheck` macros in rawbigints, for catching
OOB with `--check-bounds=yes`
* fix OOB in `truncate`

12 of 13 new or added lines in 1 file covered. (92.31%)

1287 existing lines in 41 files now uncovered.

77245 of 89578 relevant lines covered (86.23%)

15686161.83 hits per line

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

29.67
/stdlib/Printf/src/Printf.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2
"""
3
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.
4
"""
5
module Printf
6

7
using Base.Ryu
8

9
export @printf, @sprintf
10

11
public format, Format
12

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

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

26
"""
27
Typed representation of a format specifier.
28

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

31
Fields are the various modifiers allowed for various format specifiers.
32
"""
33
struct Spec{T} # T => %type => Val{'type'}
34
    leftalign::Bool
65✔
35
    plus::Bool
36
    space::Bool
37
    zero::Bool
38
    hash::Bool
39
    width::Int
40
    precision::Int
41
    dynamic_width::Bool
42
    dynamic_precision::Bool
43
end
44

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

58
Base.show(io::IO, f::Spec) = print(io, string(f))
×
59

UNCOV
60
floatfmt(s::Spec{T}) where {T} =
×
61
    Spec{Val{'f'}}(s.leftalign, s.plus, s.space, s.zero, s.hash, s.width, 0, s.dynamic_width, s.dynamic_precision)
UNCOV
62
ptrfmt(s::Spec{T}, x) where {T} =
×
63
    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)
64

65
"""
66
    Printf.Format(format_str)
67

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

70
The input `format_str` can include any valid format specifier character and modifiers.
71

72
A `Format` object can be passed to `Printf.format(f::Format, args...)` to produce a
73
formatted string, or `Printf.format(io::IO, f::Format, args...)` to print the
74
formatted string directly to `io`.
75

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

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

95
# what number base should be used for a given format specifier?
96
base(T) = T <: HexBases ? 16 : T <: Val{'o'} ? 8 : 10
22✔
UNCOV
97
char(::Type{Val{c}}) where {c} = c
×
98

99
struct InvalidFormatStringError <: Exception
UNCOV
100
    message::String
×
101
    format::String
102
    start_color::Int
103
    end_color::Int
104
end
105

106
function Base.showerror(io::IO, err::InvalidFormatStringError)
×
107
    io_has_color = get(io, :color, false)::Bool
×
108

109
    println(io, "InvalidFormatStringError: ", err.message)
×
110
    print(io, "    \"", @view(err.format[begin:prevind(err.format, err.start_color)]))
×
111
    invalid_text = @view err.format[err.start_color:err.end_color]
×
112

113
    printstyled(io, invalid_text, color=:red)
×
114

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

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

127
# parse format string
128
function Format(f::AbstractString)
11✔
129
    bytes = codeunits(f)
11✔
130
    len = length(bytes)
11✔
131
    pos = 1
11✔
132
    numarguments = 0
11✔
133

134
    b = 0x00
11✔
135
    local last_percent_pos
136

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

277
macro format_str(str)
278
    Format(str)
279
end
280

281
const hex = b"0123456789abcdef"
282
const HEX = b"0123456789ABCDEF"
283

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

296

297
@inline function rmdynamic(spec::Spec{T}, args, argp) where {T}
298
    zero, width, precision = spec.zero, spec.width, spec.precision
46✔
299
    if spec.dynamic_width
46✔
UNCOV
300
        width = args[argp]::Integer
×
UNCOV
301
        argp += 1
×
302
    end
303
    if spec.dynamic_precision
46✔
UNCOV
304
        precision = args[argp]::Integer
×
UNCOV
305
        if zero && T <: Ints && precision > 0
×
UNCOV
306
            zero = false
×
307
        end
UNCOV
308
        argp += 1
×
309
    end
310
    (Spec{T}(spec.leftalign, spec.plus, spec.space, zero, spec.hash, width, precision, false, false), argp)
46✔
311
end
312

313
Base.@constprop :aggressive function fmt(buf, pos, args, argp, spec::Spec{T}) where {T}
314
    spec, argp = rmdynamic(spec, args, argp)
23✔
315
    (fmt(buf, pos, args[argp], spec), argp+1)
23✔
316
end
317

UNCOV
318
function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Chars}
×
UNCOV
319
    leftalign, width = spec.leftalign, spec.width
×
UNCOV
320
    c = Char(first(arg))
×
UNCOV
321
    w = textwidth(c)
×
UNCOV
322
    if !leftalign && width > w
×
UNCOV
323
        for _ = 1:(width - w)
×
UNCOV
324
            buf[pos] = UInt8(' ')
×
UNCOV
325
            pos += 1
×
UNCOV
326
        end
×
327
    end
UNCOV
328
    pos = writechar(buf, pos, c)
×
UNCOV
329
    if leftalign && width > w
×
UNCOV
330
        for _ = 1:(width - w)
×
UNCOV
331
            buf[pos] = UInt8(' ')
×
UNCOV
332
            pos += 1
×
UNCOV
333
        end
×
334
    end
UNCOV
335
    return pos
×
336
end
337

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

379
# integers
380
toint(x) = x
22✔
UNCOV
381
toint(x::Rational) = Integer(x)
×
382

UNCOV
383
fmt(buf, pos, arg::AbstractFloat, spec::Spec{T}) where {T <: Ints} =
×
384
    fmt(buf, pos, arg, floatfmt(spec))
385

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

464
# floats
465
"""
466
    Printf.tofloat(x)
467

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

473
```julia
474
Printf.tofloat(x::MyCustomType) = convert_my_custom_type_to_float(x)
475
```
476

477
For arbitrary precision numerics, you might extend the method like:
478

479
```julia
480
Printf.tofloat(x::MyArbitraryPrecisionType) = BigFloat(x)
481
```
482

483
!!! compat "Julia 1.6"
484
    This function requires Julia 1.6 or later.
485
"""
UNCOV
486
tofloat(x) = Float64(x)
×
487
tofloat(x::Base.IEEEFloat) = x
12✔
UNCOV
488
tofloat(x::BigFloat) = x
×
489

UNCOV
490
_snprintf(ptr, siz, str, arg) =
×
UNCOV
491
    @ccall "libmpfr".mpfr_snprintf(ptr::Ptr{UInt8}, siz::Csize_t, str::Ptr{UInt8};
×
492
                                   arg::Ref{BigFloat})::Cint
493

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

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

678
# pointers
UNCOV
679
fmt(buf, pos, arg, spec::Spec{Pointer}) = fmt(buf, pos, UInt64(arg), ptrfmt(spec, arg))
×
680

681
# position counters
UNCOV
682
function fmt(buf, pos, arg::Ref{<:Integer}, ::Spec{PositionCounter})
×
UNCOV
683
    arg[] = pos - 1
×
UNCOV
684
    pos
×
685
end
686

687
# old Printf compat
688
function fix_dec end
689
function ini_dec end
690

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

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

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

867
@inline function plength(f::Spec{T}, args, argp) where {T}
868
    f, argp = rmdynamic(f, args, argp)
23✔
869
    (plength(f, args[argp]), argp+1)
23✔
870
end
871

UNCOV
872
function plength(f::Spec{T}, x) where {T <: Chars}
×
UNCOV
873
    c = Char(first(x))
×
UNCOV
874
    w = textwidth(c)
×
UNCOV
875
    return max(f.width, w) + (ncodeunits(c) - w)
×
876
end
877

UNCOV
878
plength(f::Spec{Pointer}, x) = max(f.width, 2 * sizeof(x) + 2)
×
879

UNCOV
880
function plength(f::Spec{T}, x) where {T <: Strings}
×
UNCOV
881
    str = string(x)
×
UNCOV
882
    sw = textwidth(str)
×
UNCOV
883
    p = f.precision == -1 ? (sw + (f.hash ? (x isa Symbol ? 1 : 2) : 0)) : f.precision
×
UNCOV
884
    return max(f.width, p) + (sizeof(str) - sw)
×
885
end
886

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

UNCOV
895
plength(f::Spec{T}, x::AbstractFloat) where {T <: Ints} =
×
896
    max(f.width, f.hash + MAX_INTEGER_PART_WIDTH + 0 + MAX_FMT_CHARS_WIDTH)
897
plength(f::Spec{T}, x) where {T <: Floats} =
12✔
898
    max(f.width, f.hash + MAX_INTEGER_PART_WIDTH + f.precision + MAX_FMT_CHARS_WIDTH)
899
plength(::Spec{PositionCounter}, x) = 0
×
900

901
@inline function computelen(substringranges, formats, args)
902
    len = sum(length, substringranges)
19✔
903
    N = length(formats)
19✔
904
    # unroll up to 16 formats
905
    argp = 1
19✔
906
    Base.@nexprs 16 i -> begin
907
        if N >= i
42✔
908
            l, argp = plength(formats[i], args, argp)
23✔
909
            len += l
23✔
910
        end
911
    end
912
    if N > 16
19✔
UNCOV
913
        for i = 17:length(formats)
×
UNCOV
914
            l, argp = plength(formats[i], args, argp)
×
UNCOV
915
            len += l
×
UNCOV
916
        end
×
917
    end
918
    return len
19✔
919
end
920

UNCOV
921
@noinline argmismatch(a, b) =
×
922
    throw(ArgumentError("Number of format specifiers and number of provided args differ: $a != $b"))
923

924
"""
925
    Printf.format(f::Printf.Format, args...) => String
926
    Printf.format(io::IO, f::Printf.Format, args...)
927

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

934
# Since it will specialize on `f`, which has a Tuple-type often of length(args), we might as well specialize on `args` too.
935
function format(io::IO, f::Format, args::Vararg{Any,N}) where N # => Nothing
4✔
936
    f.numarguments == length(args) || argmismatch(f.numarguments, length(args))
4✔
937
    buf = Base.StringVector(computelen(f.substringranges, f.formats, args))
4✔
938
    pos = format(buf, 1, f, args...)
4✔
939
    write(io, resize!(buf, pos - 1))
4✔
940
    return
4✔
941
end
942

943
function format(f::Format, args::Vararg{Any,N}) where N # => String
15✔
944
    f.numarguments == length(args) || argmismatch(f.numarguments, length(args))
15✔
945
    buf = Base.StringVector(computelen(f.substringranges, f.formats, args))
15✔
946
    pos = format(buf, 1, f, args...)
15✔
947
    return String(resize!(buf, pos - 1))
15✔
948
end
949

950
"""
951
    @printf([io::IO], "%Fmt", args...)
952

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

956
# Examples
957
```jldoctest
958
julia> @printf "Hello %s" "world"
959
Hello world
960

961
julia> @printf "Scientific notation %e" 1.234
962
Scientific notation 1.234000e+00
963

964
julia> @printf "Scientific notation three digits %.3e" 1.23456
965
Scientific notation three digits 1.235e+00
966

967
julia> @printf "Decimal two digits %.2f" 1.23456
968
Decimal two digits 1.23
969

970
julia> @printf "Padded to length 5 %5i" 123
971
Padded to length 5   123
972

973
julia> @printf "Padded with zeros to length 6 %06i" 123
974
Padded with zeros to length 6 000123
975

976
julia> @printf "Use shorter of decimal or scientific %g %g" 1.23 12300000.0
977
Use shorter of decimal or scientific 1.23 1.23e+07
978

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

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

991
# Examples
992
```jldoctest
993
julia> @printf("%f %F %f %F", Inf, Inf, NaN, NaN)
994
Inf Inf NaN NaN
995

996
julia> @printf "%.0f %.1f %f" 0.5 0.025 -0.0078125
997
0 0.0 -0.007812
998
```
999

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

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

1023
"""
1024
    @sprintf("%Fmt", args...)
1025

1026
Return [`@printf`](@ref) formatted output as string.
1027

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

1040
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