diff --git a/NEWS.md b/NEWS.md index d8f54fd219921..12df64f107a96 100644 --- a/NEWS.md +++ b/NEWS.md @@ -52,6 +52,12 @@ Build system changes `./julia-installer.exe /TASKS="desktopicon,startmenu,addtopath"`, adds a desktop icon, a startmenu group icon, and adds Julia to system PATH. + +Library functions +----------------- +* The `Base.Grisu` code has been officially removed (float printing was switched to the ryu algorithm code in 1.4) + + New library functions --------------------- @@ -94,6 +100,13 @@ Standard library changes #### Markdown +#### Printf + +* Complete overhaul of internal code to use the ryu float printing algorithms (from Julia 1.4); leads to consistent 2-5x performance improvements +* New `Printf.tofloat` function allowing custom float types to more easily integrate with Printf formatting by converting their type to `Float16`, `Float32`, `Float64`, or `BigFloat` +* New `Printf.format"..."` and `Printf.Format(...)` functions that allow creating `Printf.Format` objects that can be passed to `Printf.format` for easier dynamic printf formatting +* `Printf.format(f::Printf.Format, args...)` as a non-macro function that applies a printf format `f` to provided `args` + #### Random diff --git a/base/Base.jl b/base/Base.jl index b95ac8732ed09..735f06ef8f0cd 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -250,7 +250,6 @@ using .Filesystem include("cmd.jl") include("process.jl") include("ttyhascolor.jl") -include("grisu/grisu.jl") include("secretbuffer.jl") # core math functions diff --git a/base/grisu/bignum.jl b/base/grisu/bignum.jl deleted file mode 100644 index 2f1d67ce292ed..0000000000000 --- a/base/grisu/bignum.jl +++ /dev/null @@ -1,256 +0,0 @@ -# This file is a part of Julia, but is derived from -# https://github.com/google/double-conversion which has the following license -# -# Copyright 2006-2014, the V8 project authors. All rights reserved. -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -function normalizedexponent(significand, exponent::Int32) - significand = UInt64(significand) - while (significand & HiddenBit(Float64)) == 0 - significand <<= UInt64(1) - exponent -= Int32(1) - end - return exponent -end - -function bignumdtoa(v,mode,requested_digits::Int,buffer,bignums) - significand = _significand(v) - exponent = _exponent(v) - lower_boundary_is_closer = lowerboundaryiscloser(v) - need_boundary_deltas = mode == SHORTEST - - is_even = (significand & 1) == 0 - normalized_exponent = normalizedexponent(significand, exponent) - estimated_power = estimatepower(Int(normalized_exponent)) - - if mode == FIXED && -estimated_power - 1 > requested_digits - buffer[1] = 0 - len = 1 - decimal_point = -requested_digits - return true, len, decimal_point - end - num, den, minus, plus = bignums[1], bignums[2], bignums[3], bignums[4] - initialscaledstartvalues!(significand,exponent,lower_boundary_is_closer, - estimated_power,need_boundary_deltas, - num,den,minus,plus) - decimal_point = fixupmultiply10!(estimated_power,is_even,num,den,minus,plus) - if mode == SHORTEST - len = generateshortestdigits!(num,den,minus,plus,is_even,buffer) - elseif mode == FIXED - len, decimal_point = bignumtofixed!(requested_digits,num,den,buffer,decimal_point) - elseif mode == PRECISION - len, decimal_point = generatecounteddigits!(requested_digits,num,den,buffer,decimal_point) - end - buffer[len] = 0 - return true, len, decimal_point -end - -function generateshortestdigits!(num,den,minus,plus,is_even,buffer) - minus == plus && (plus = minus) - len = 1 - while true - digit = Bignums.dividemodulointbignum!(num,den) - buffer[len] = 0x30 + (digit % UInt8) - len += 1 - in_delta_room_minus = is_even ? - Bignums.lessequal(num,minus) : Bignums.less(num,minus) - in_delta_room_plus = is_even ? - Bignums.pluscompare(num,plus,den) >= 0 : Bignums.pluscompare(num,plus,den) > 0 - if !in_delta_room_minus && !in_delta_room_plus - Bignums.times10!(num) - Bignums.times10!(minus) - minus != plus && Bignums.times10!(plus) - elseif in_delta_room_minus && in_delta_room_plus - compare = Bignums.pluscompare(num,num,den) - if compare < 0 - elseif compare > 0 - buffer[len - 1] += 1 - else - if (buffer[len - 1] - 0x30) % 2 == 0 - else - buffer[len - 1] += 1 - end - end - return len - elseif in_delta_room_minus - return len - else - buffer[len - 1] += 1 - return len - end - end -end - -function generatecounteddigits!(count,num,den,buffer,decimal_point) - for i = 1:(count-1) - digit = Bignums.dividemodulointbignum!(num,den) - buffer[i] = 0x30 + (digit % UInt8) - Bignums.times10!(num) - end - digit = Bignums.dividemodulointbignum!(num,den) - if Bignums.pluscompare(num,num,den) >= 0 - digit += 1 - end - buffer[count] = 0x30 + (digit % UInt8) - for i = count:-1:2 - buffer[i] != 0x30 + 10 && break - buffer[i] = 0x30 - buffer[i - 1] += 1 - end - if buffer[1] == 0x30 + 10 - buffer[1] = 0x31 - decimal_point += 1 - end - len = count+1 - return len, decimal_point -end - -function bignumtofixed!(requested_digits,num,den,buffer,decimal_point) - if -decimal_point > requested_digits - decimal_point = -requested_digits - len = 1 - return len, decimal_point - elseif -decimal_point == requested_digits - Bignums.times10!(den) - if Bignums.pluscompare(num,num,den) >= 0 - buffer[1] = 0x31 - len = 2 - decimal_point += 1 - else - len = 1 - end - return len, decimal_point - else - needed_digits = decimal_point + requested_digits - len, decimal_point = generatecounteddigits!( - needed_digits,num,den,buffer,decimal_point) - end - return len, decimal_point -end - - -const k1Log10 = 0.30102999566398114 -const kSignificandSize = SignificandSize(Float64) -estimatepower(exponent::Int) = ceil(Int,(exponent + kSignificandSize - 1) * k1Log10 - 1e-10) - -function init3!( - significand,exponent,estimated_power,need_boundary_deltas, - num,den,minus,plus) - Bignums.assignuint64!(num,UInt64(significand)) - Bignums.shiftleft!(num,exponent) - Bignums.assignpoweruint16!(den,UInt16(10),estimated_power) - if need_boundary_deltas - Bignums.shiftleft!(den,1) - Bignums.shiftleft!(num,1) - Bignums.assignuint16!(plus,UInt16(1)) - Bignums.shiftleft!(plus,exponent) - Bignums.assignuint16!(minus,UInt16(1)) - Bignums.shiftleft!(minus,exponent) - else - Bignums.zero!(plus) - Bignums.zero!(minus) - end - return -end - - -function init1!( - significand,exponent,estimated_power,need_boundary_deltas, - num,den,minus,plus) - Bignums.assignuint64!(num,UInt64(significand)) - Bignums.assignpoweruint16!(den,UInt16(10),estimated_power) - Bignums.shiftleft!(den,-exponent) - if need_boundary_deltas - Bignums.shiftleft!(den,1) - Bignums.shiftleft!(num,1) - Bignums.assignuint16!(plus,UInt16(1)) - Bignums.assignuint16!(minus,UInt16(1)) - else - Bignums.zero!(plus) - Bignums.zero!(minus) - end - return -end - -function init2!( - significand,exponent,estimated_power,need_boundary_deltas, - num,den,minus,plus) - power_ten = num - Bignums.assignpoweruint16!(power_ten,UInt16(10),-estimated_power) - if need_boundary_deltas - Bignums.assignbignum!(plus,power_ten) - Bignums.assignbignum!(minus,power_ten) - else - Bignums.zero!(plus) - Bignums.zero!(minus) - end - Bignums.multiplybyuint64!(num,UInt64(significand)) - Bignums.assignuint16!(den,UInt16(1)) - Bignums.shiftleft!(den,-exponent) - if need_boundary_deltas - Bignums.shiftleft!(num,1) - Bignums.shiftleft!(den,1) - end - return -end - -function initialscaledstartvalues!(significand, - exponent,lower_boundary_is_closer,estimated_power, - need_boundary_deltas,num,den,minus,plus) - if exponent >= 0 - init3!(significand, exponent, estimated_power, need_boundary_deltas,num,den,minus,plus) - elseif estimated_power >= 0 - init1!(significand, exponent, estimated_power, need_boundary_deltas,num,den,minus,plus) - else - init2!(significand, exponent, estimated_power, need_boundary_deltas,num,den,minus,plus) - end - if need_boundary_deltas && lower_boundary_is_closer - Bignums.shiftleft!(den,1) - Bignums.shiftleft!(num,1) - Bignums.shiftleft!(plus,1) - end - return -end - -function fixupmultiply10!(estimated_power,is_even,num,den,minus,plus) - in_range = is_even ? Bignums.pluscompare(num,plus,den) >= 0 : - Bignums.pluscompare(num,plus,den) > 0 - if in_range - decimal_point = estimated_power + 1 - else - decimal_point = estimated_power - Bignums.times10!(num) - if minus == plus - Bignums.times10!(minus) - Bignums.assignbignum!(plus,minus) - else - Bignums.times10!(minus) - Bignums.times10!(plus) - end - end - return decimal_point -end diff --git a/base/grisu/bignums.jl b/base/grisu/bignums.jl deleted file mode 100644 index 8898549c3cc41..0000000000000 --- a/base/grisu/bignums.jl +++ /dev/null @@ -1,495 +0,0 @@ -# This file is a part of Julia, but is derived from -# https://github.com/google/double-conversion which has the following license -# -# Copyright 2006-2014, the V8 project authors. All rights reserved. -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -module Bignums - -import Base: ==, < - -export Bignum - -const kMaxSignificantBits = 3584 - -const Chunk = UInt32 -const DoubleChunk = UInt64 - -const kChunkSize = sizeof(Chunk) * 8 -const kDoubleChunkSize = sizeof(DoubleChunk) * 8 -# With bigit size of 28 we loose some bits, but a double still fits easily -# into two chunks, and more importantly we can use the Comba multiplication. -const kBigitSize = 28 -const kBigitMask = Chunk((1 << kBigitSize) - 1) -# Every instance allocates kBigitLength chunks on the stack. Bignums cannot -# grow. There are no checks if the stack-allocated space is sufficient. -const kBigitCapacity = div(kMaxSignificantBits, kBigitSize) - -mutable struct Bignum - bigits::Vector{UInt32} - used_digits::Int32 - exponent::Int32 - function Bignum() - bigits = Vector{UInt32}(undef, kBigitCapacity) - @inbounds for i = 1:kBigitCapacity - bigits[i] = 0 - end - new(bigits,0,0) - end -end - -==(a::Bignum,b::Bignum) = compare(a,b) == 0 -<(a::Bignum,b::Bignum) = compare(a,b) < 0 - -times10!(x::Bignum) = multiplybyuint32!(x,UInt32(10)) - -plusequal(a,b,c) = pluscompare(a,b,c) == 0 -pluslessequal(a,b,c) = pluscompare(a,b,c) <= 0 -plusless(a,b,c) = pluscompare(a,b,c) < 0 -lessequal(a::Bignum,b::Bignum) = compare(a,b) <= 0 -less(a::Bignum,b::Bignum) = compare(a,b) < 0 - -bigitlength(x::Bignum) = x.used_digits + x.exponent - -bitsize(value) = 8 * sizeof(value) - -function zero!(x::Bignum) - for i = 1:x.used_digits - @inbounds x.bigits[i] = 0 - end - x.used_digits = 0 - x.exponent = 0 - return -end - -function clamp!(x::Bignum) - @inbounds while (x.used_digits > 0 && x.bigits[x.used_digits] == 0) - x.used_digits -= 1 - end - x.used_digits == 0 && (x.exponent = 0) - return -end - -isclamped(x::Bignum) = x.used_digits == 0 || x.bigits[x.used_digits] != 0 - -function align!(x::Bignum,other::Bignum) - @inbounds if x.exponent > other.exponent - zero_digits = x.exponent - other.exponent - for i = x.used_digits:-1:1 - x.bigits[i + zero_digits] = x.bigits[i] - end - for i = 1:zero_digits - x.bigits[i] = 0 - end - x.used_digits += zero_digits - x.exponent -= zero_digits - end - return -end - -function bigitshiftleft!(x::Bignum,shift_amount) - carry::UInt32 = 0 - @inbounds begin - for i = 1:x.used_digits - new_carry::Chunk = x.bigits[i] >> (kBigitSize - shift_amount) - x.bigits[i] = ((x.bigits[i] << shift_amount) + carry) & kBigitMask - carry = new_carry - end - if carry != 0 - x.bigits[x.used_digits+1] = carry - x.used_digits += 1 - end - end - return -end - -function subtracttimes!(x::Bignum,other::Bignum,factor) - if factor < 3 - for i = 1:factor - subtractbignum!(x,other) - end - return - end - borrow::Chunk = 0 - exponent_diff = other.exponent - x.exponent - @inbounds begin - for i = 1:other.used_digits - product::DoubleChunk = DoubleChunk(factor) * other.bigits[i] - remove::DoubleChunk = borrow + product - difference::Chunk = (x.bigits[i+exponent_diff] - (remove & kBigitMask)) % Chunk - x.bigits[i+exponent_diff] = difference & kBigitMask - borrow = ((difference >> (kChunkSize - 1)) + (remove >> kBigitSize)) % Chunk - end - for i = (other.used_digits + exponent_diff + 1):x.used_digits - borrow == 0 && return - difference::Chunk = x.bigits[i] - borrow - x.bigits[i] = difference & kBigitMask - borrow = difference >> (kChunkSize - 1) - end - end - clamp!(x) -end - -function assignuint16!(x::Bignum,value::UInt16) - zero!(x) - value == 0 && return - x.bigits[1] = value - x.used_digits = 1 - return -end - -const kUInt64Size = 64 -function assignuint64!(x::Bignum,value::UInt64) - zero!(x) - value == 0 && return - needed_bigits = div(kUInt64Size,kBigitSize) + 1 - @inbounds for i = 1:needed_bigits - x.bigits[i] = value & kBigitMask - value >>= kBigitSize - end - x.used_digits = needed_bigits - clamp!(x) -end - -function assignbignum!(x::Bignum,other::Bignum) - x.exponent = other.exponent - @inbounds begin - for i = 1:other.used_digits - x.bigits[i] = other.bigits[i] - end - for i = (other.used_digits+1):x.used_digits - x.bigits[i] = 0 - end - end - x.used_digits = other.used_digits - return -end - -function adduint64!(x::Bignum,operand::UInt64) - operand == 0 && return - other = Bignum() - assignuint64!(other,operand) - addbignum!(x,other) -end - -function addbignum!(x::Bignum,other::Bignum) - align!(x,other) - carry::Chunk = 0 - bigit_pos = other.exponent - x.exponent - @inbounds for i = 1:other.used_digits - sum::Chunk = x.bigits[bigit_pos+1] + other.bigits[i] + carry - x.bigits[bigit_pos+1] = sum & kBigitMask - carry = sum >> kBigitSize - bigit_pos += 1 - end - @inbounds while carry != 0 - sum = x.bigits[bigit_pos+1] + carry - x.bigits[bigit_pos+1] = sum & kBigitMask - carry = sum >> kBigitSize - bigit_pos += 1 - end - x.used_digits = max(bigit_pos,x.used_digits) - return -end - -function subtractbignum!(x::Bignum,other::Bignum) - align!(x,other) - offset = other.exponent - x.exponent - borrow = Chunk(0) - @inbounds begin - for i = 1:other.used_digits - difference = x.bigits[i+offset] - other.bigits[i] - borrow - x.bigits[i+offset] = difference & kBigitMask - borrow = difference >> (kChunkSize - 1) - end - i = other.used_digits+1 - while borrow != 0 - difference = x.bigits[i+offset] - borrow - x.bigits[i+offset] = difference & kBigitMask - borrow = difference >> (kChunkSize - 1) - i += 1 - end - end - clamp!(x) -end - -function shiftleft!(x::Bignum,shift_amount) - x.used_digits == 0 && return - x.exponent += div(shift_amount,kBigitSize) - local_shift = shift_amount % kBigitSize - bigitshiftleft!(x,local_shift) -end - -function multiplybyuint32!(x::Bignum,factor::UInt32) - factor == 1 && return - if factor == 0 - zero!(x) - return - end - x.used_digits == 0 && return - carry::DoubleChunk = 0 - @inbounds begin - for i = 1:x.used_digits - product::DoubleChunk = (factor % DoubleChunk) * x.bigits[i] + carry - x.bigits[i] = (product & kBigitMask) % Chunk - carry = product >> kBigitSize - end - while carry != 0 - x.bigits[x.used_digits+1] = carry & kBigitMask - x.used_digits += 1 - carry >>= kBigitSize - end - end - return -end - -function multiplybyuint64!(x::Bignum,factor::UInt64) - factor == 1 && return - if factor == 0 - zero!(x) - return - end - carry::UInt64 = 0 - low::UInt64 = factor & 0xFFFFFFFF - high::UInt64 = factor >> 32 - @inbounds begin - for i = 1:x.used_digits - product_low::UInt64 = low * x.bigits[i] - product_high::UInt64 = high * x.bigits[i] - tmp::UInt64 = (carry & kBigitMask) + product_low - x.bigits[i] = tmp & kBigitMask - carry = (carry >> kBigitSize) + (tmp >> kBigitSize) + - (product_high << (32 - kBigitSize)) - end - while carry != 0 - x.bigits[x.used_digits+1] = carry & kBigitMask - x.used_digits += 1 - carry >>= kBigitSize - end - end - return -end - -const kFive27 = UInt64(0x6765c793fa10079d) -const kFive1 = UInt16(5) -const kFive2 = UInt16(kFive1 * 5) -const kFive3 = UInt16(kFive2 * 5) -const kFive4 = UInt16(kFive3 * 5) -const kFive5 = UInt16(kFive4 * 5) -const kFive6 = UInt16(kFive5 * 5) -const kFive7 = UInt32(kFive6 * 5) -const kFive8 = UInt32(kFive7 * 5) -const kFive9 = UInt32(kFive8 * 5) -const kFive10 = UInt32(kFive9 * 5) -const kFive11 = UInt32(kFive10 * 5) -const kFive12 = UInt32(kFive11 * 5) -const kFive13 = UInt32(kFive12 * 5) -const kFive1_to_12 = UInt32[kFive1, kFive2, kFive3, kFive4, kFive5, kFive6, - kFive7, kFive8, kFive9, kFive10, kFive11, kFive12] -function multiplybypoweroften!(x::Bignum,exponent) - exponent == 0 && return - x.used_digits == 0 && return - remaining_exponent = exponent - while remaining_exponent >= 27 - multiplybyuint64!(x,kFive27) - remaining_exponent -= 27 - end - while remaining_exponent >= 13 - multiplybyuint32!(x,kFive13) - remaining_exponent -= 13 - end - remaining_exponent > 0 && multiplybyuint32!(x, - kFive1_to_12[remaining_exponent]) - shiftleft!(x,exponent) -end - -function square!(x::Bignum) - product_length = 2 * x.used_digits - (1 << (2 * (kChunkSize - kBigitSize))) <= x.used_digits && error("unimplemented") - accumulator::DoubleChunk = 0 - copy_offset = x.used_digits - @inbounds begin - for i = 1:x.used_digits - x.bigits[copy_offset + i] = x.bigits[i] - end - for i = 1:x.used_digits - bigit_index1 = i-1 - bigit_index2 = 0 - while bigit_index1 >= 0 - chunk1::Chunk = x.bigits[copy_offset + bigit_index1 + 1] - chunk2::Chunk = x.bigits[copy_offset + bigit_index2 + 1] - accumulator += (chunk1 % DoubleChunk) * chunk2 - bigit_index1 -= 1 - bigit_index2 += 1 - end - x.bigits[i] = (accumulator % Chunk) & kBigitMask - accumulator >>= kBigitSize - end - for i = x.used_digits+1:product_length - bigit_index1 = x.used_digits - 1 - bigit_index2 = i - bigit_index1 - 1 - while bigit_index2 < x.used_digits - chunk1::Chunk = x.bigits[copy_offset + bigit_index1 + 1] - chunk2::Chunk = x.bigits[copy_offset + bigit_index2 + 1] - accumulator += (chunk1 % DoubleChunk) * chunk2 - bigit_index1 -= 1 - bigit_index2 += 1 - end - x.bigits[i] = (accumulator % Chunk) & kBigitMask - accumulator >>= kBigitSize - end - end - x.used_digits = product_length - x.exponent *= 2 - clamp!(x) -end - -function assignpoweruint16!(x::Bignum,base::UInt16,power_exponent::Int) - if power_exponent == 0 - assignuint16!(x,UInt16(1)) - return - end - zero!(x) - shifts::Int = 0 - while base & UInt16(1) == UInt16(0) - base >>= UInt16(1) - shifts += 1 - end - bit_size::Int = 0 - tmp_base::Int= base - while tmp_base != 0 - tmp_base >>= 1 - bit_size += 1 - end - final_size = bit_size * power_exponent - mask::Int = 1 - while power_exponent >= mask - mask <<= 1 - end - mask >>= 2 - this_value::UInt64 = base - delayed_multiplication = false - max_32bits::UInt64 = 0xFFFFFFFF - while mask != 0 && this_value <= max_32bits - this_value *= this_value - if (power_exponent & mask) != 0 - base_bits_mask::UInt64 = ~(UInt64(1) << (64 - bit_size) - 1) - high_bits_zero = (this_value & base_bits_mask) == 0 - if high_bits_zero - this_value *= base - else - delayed_multiplication = true - end - end - mask >>= 1 - end - assignuint64!(x,this_value) - delayed_multiplication && multiplybyuint32!(x,UInt32(base)) - while mask != 0 - square!(x) - (power_exponent & mask) != 0 && multiplybyuint32!(x,UInt32(base)) - mask >>= 1 - end - shiftleft!(x,shifts * power_exponent) -end - -function dividemodulointbignum!(x::Bignum,other::Bignum) - bigitlength(x) < bigitlength(other) && return UInt16(0) - align!(x,other) - result::UInt16 = 0 - @inbounds begin - while bigitlength(x) > bigitlength(other) - result += x.bigits[x.used_digits] % UInt16 - subtracttimes!(x,other,x.bigits[x.used_digits]) - end - this_bigit::Chunk = x.bigits[x.used_digits] - other_bigit::Chunk = other.bigits[other.used_digits] - if other.used_digits == 1 - quotient = reinterpret(Int32,div(this_bigit,other_bigit)) - x.bigits[x.used_digits] = this_bigit - other_bigit * reinterpret(UInt32,quotient) - result += quotient % UInt16 - clamp!(x) - return result - end - end - division_estimate = reinterpret(Int32,div(this_bigit,other_bigit+Chunk(1))) - result += division_estimate % UInt16 - subtracttimes!(x,other,division_estimate) - other_bigit * (division_estimate+1) > this_bigit && return result - while lessequal(other, x) - subtractbignum!(x,other) - result += UInt16(1) - end - return result -end - -function pluscompare(a::Bignum,b::Bignum,c::Bignum) - bigitlength(a) < bigitlength(b) && return pluscompare(b,a,c) - bigitlength(a) + 1 < bigitlength(c) && return -1 - bigitlength(a) > bigitlength(c) && return 1 - a.exponent >= bigitlength(b) && bigitlength(a) < bigitlength(c) && return -1 - borrow::Chunk = 0 - min_exponent = min(a.exponent,b.exponent,c.exponent) - for i = (bigitlength(c)-1):-1:min_exponent - chunk_a::Chunk = bigitat(a,i) - chunk_b::Chunk = bigitat(b,i) - chunk_c::Chunk = bigitat(c,i) - sum::Chunk = chunk_a + chunk_b - if sum > chunk_c + borrow - return 1 - else - borrow = chunk_c + borrow - sum - borrow > 1 && return -1 - borrow <<= kBigitSize - end - end - borrow == 0 && return 0 - return -1 -end - -function compare(a::Bignum,b::Bignum) - bigit_length_a = bigitlength(a) - bigit_length_b = bigitlength(b) - bigit_length_a < bigit_length_b && return -1 - bigit_length_a > bigit_length_b && return 1 - for i = (bigit_length_a-1):-1:min(a.exponent,b.exponent) - bigit_a::Chunk = bigitat(a,i) - bigit_b::Chunk = bigitat(b,i) - bigit_a < bigit_b && return -1 - bigit_a > bigit_b && return 1 - end - return 0 -end - -function bigitat(x::Bignum,index) - index >= bigitlength(x) && return Chunk(0) - index < x.exponent && return Chunk(0) - @inbounds ret = x.bigits[index - x.exponent+1]::Chunk - return ret -end - -end # module diff --git a/base/grisu/fastfixed.jl b/base/grisu/fastfixed.jl deleted file mode 100644 index 014806b6531ea..0000000000000 --- a/base/grisu/fastfixed.jl +++ /dev/null @@ -1,252 +0,0 @@ -# This file is a part of Julia, but is derived from -# https://github.com/google/double-conversion which has the following license -# -# Copyright 2006-2014, the V8 project authors. All rights reserved. -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -const kDoubleSignificandSize = 53 - -function filldigits32fixedlength(n1,requested_len,buffer,len) - for i = (requested_len-1):-1:0 - buffer[len+i] = 0x30 + n1 % 10 - n1 = div(n1,10) - end - return len + requested_len -end - -function filldigits32(n,buffer,len) - n_len = 0 - while n != 0 - digit = n % 10 - n = div(n,10) - buffer[len+n_len] = 0x30 + digit - n_len += 1 - end - i,j = len, len + n_len - 1 - while i < j - buffer[i], buffer[j] = buffer[j], buffer[i] - i += 1 - j -= 1 - end - return len + n_len -end - -function filldigits64fixedlength(n2,buffer,len) - kTen7 = 10000000 - part2 = n2 % kTen7 - n2 = div(n2,kTen7) - part0, part1 = divrem(n2,kTen7) - len = filldigits32fixedlength(part0, 3, buffer, len) - len = filldigits32fixedlength(part1, 7, buffer, len) - len = filldigits32fixedlength(part2, 7, buffer, len) - return len -end - -function filldigits64(n3,buffer,len) - kTen7 = 10000000 - part2 = n3 % kTen7 - n3 = div(n3,kTen7) - part0, part1 = divrem(n3,kTen7) - if part0 != 0 - len = filldigits32(part0, buffer, len) - len = filldigits32fixedlength(part1, 7, buffer, len) - len = filldigits32fixedlength(part2, 7, buffer, len) - elseif part1 != 0 - len = filldigits32(part1, buffer, len) - len = filldigits32fixedlength(part2, 7, buffer, len) - else - len = filldigits32(part2, buffer, len) - end - return len -end - -function roundup(buffer, len, decimal_point) - if len == 1 - buffer[1] = 0x31 - decimal_point = 1 - len = 2 - return len, decimal_point - end - buffer[len - 1] += 1 - for i = (len-1):-1:2 - buffer[i] != 0x30 + 10 && return len, decimal_point - buffer[i] = 0x30 - buffer[i - 1] += 1 - end - if buffer[1] == 0x30 + 10 - buffer[1] = 0x31 - decimal_point += 1 - end - return len, decimal_point -end - -function fillfractionals(fractionals, exponent, - fractional_count, buffer, - len, decimal_point) - if -exponent <= 64 - point = -exponent - for i = 1:fractional_count - fractionals == 0 && break - fractionals *= 5 - point -= 1 - digit = fractionals >> point - buffer[len] = 0x30 + digit - len += 1 - fractionals -= UInt64(digit) << point - end - if ((fractionals >> (point - 1)) & 1) == 1 - len, decimal_point = roundup(buffer, len, decimal_point) - end - else - fract128 = UInt128(fractionals) << 64 - fract128 = shift(fract128,-exponent - 64) - point = 128 - for i = 1:fractional_count - fract128 == 0 && break - fract128 *= 5 - point -= 1 - digit, fract128 = divrem2(fract128,point) - buffer[len] = 0x30 + digit - len += 1 - end - if bitat(fract128,point - 1) == 1 - len, decimal_point = roundup(buffer, len, decimal_point) - end - end - return len, decimal_point -end - -low(x) = UInt64(x&0xffffffffffffffff) -high(x) = UInt64(x >>> 64) -bitat(x::UInt128,y) = y >= 64 ? (Int32(high(x) >> (y-64)) & 1) : (Int32(low(x) >> y) & 1) -function divrem2(x,power) - h = high(x) - l = low(x) - if power >= 64 - result = Int32(h >> (power - 64)) - h -= UInt64(result) << (power - 64) - return result, (UInt128(h) << 64) + l - else - part_low::UInt64 = l >> power - part_high::UInt64 = h << (64 - power) - result = Int32(part_low + part_high) - return result, UInt128(l - (part_low << power)) - end -end -function shift(x::UInt128,amt) - if amt == 0 - return x - elseif amt == -64 - return x << 64 - elseif amt == 64 - return x >> 64 - elseif amt <= 0 - h = high(x); l = low(x) - h <<= -amt - h += l >> (64 + amt) - l <<= -amt - return (UInt128(h) << 64) + l - else - h = high(x); l = low(x) - l >>= amt - l += h << (64 - amt) - h >>= amt - return (UInt128(h) << 64) + l - end -end - -function trimzeros(buffer, len, decimal_point) - while len > 1 && buffer[len - 1] == 0x30 - len -= 1 - end - first_non_zero::Int32 = 1 - while first_non_zero < len && buffer[first_non_zero] == 0x30 - first_non_zero += 1 - end - if first_non_zero != 1 - for i = first_non_zero:(len-1) - buffer[i - first_non_zero + 1] = buffer[i] - end - len -= first_non_zero-1 - decimal_point -= first_non_zero-1 - end - return len, decimal_point -end - -function fastfixedtoa(v,mode,fractional_count,buffer) - v = Float64(v) - significand::UInt64 = _significand(v) - exponent = _exponent(v) - exponent > 20 && return false, 0, 0 - fractional_count > 20 && return false, 0, 0 - len = 1 - if exponent + kDoubleSignificandSize > 64 - kFive17 = divisor = Int64(5)^17 - divisor_power = 17 - dividend = significand - if exponent > divisor_power - dividend <<= exponent - divisor_power - quotient = div(dividend,divisor) - remainder = (dividend % divisor) << divisor_power - else - divisor <<= divisor_power - exponent - quotient = div(dividend,divisor) - remainder = (dividend % divisor) << exponent - end - len = filldigits32(quotient, buffer, len) - len = filldigits64fixedlength(remainder, buffer, len) - decimal_point = len-1 - elseif exponent >= 0 - significand <<= exponent - len = filldigits64(significand, buffer, len) - decimal_point = len-1 - elseif exponent > -kDoubleSignificandSize - integrals = significand >> -exponent - fractionals = significand - (integrals << -exponent) - if integrals > 0xFFFFFFFF - len = filldigits64(integrals,buffer,len) - else - len = filldigits32(integrals%UInt32,buffer,len) - end - decimal_point = len-1 - len, decimal_point = fillfractionals(fractionals,exponent,fractional_count, - buffer,len, decimal_point) - elseif exponent < -128 - len = 1 - decimal_point = -fractional_count - else - decimal_point = 0 - len, decimal_point = fillfractionals(significand,exponent,fractional_count, - buffer,len, decimal_point) - end - len, decimal_point = trimzeros(buffer,len,decimal_point) - buffer[len] = 0 - if (len-1) == 0 - decimal_point = -fractional_count - end - return true, len, decimal_point -end diff --git a/base/grisu/fastprecision.jl b/base/grisu/fastprecision.jl deleted file mode 100644 index dfb7a0c46a88a..0000000000000 --- a/base/grisu/fastprecision.jl +++ /dev/null @@ -1,99 +0,0 @@ -# This file is a part of Julia, but is derived from -# https://github.com/google/double-conversion which has the following license -# -# Copyright 2006-2014, the V8 project authors. All rights reserved. -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -function roundweed(buffer,len,rest,tk,unit,kappa) - unit >= tk && return false, kappa - tk - unit <= unit && return false, kappa - tk - rest > rest && (tk - 2 * rest >= 2 * unit) && return true, kappa - if rest > unit && (tk - (rest - unit) <= (rest - unit)) - buffer[len-1] += 1 - for i = (len-1):-1:2 - buffer[i] != 0x30 + 10 && break - buffer[i] = 0x30 - buffer[i-1] += 1 - end - if buffer[1] == 0x30 + 10 - buffer[1] = 0x31 - kappa += 1 - end - return true, kappa - end - return false, kappa -end - -function digitgen(w,buffer,requested_digits=1000) - unit::UInt64 = 1 - one = Float(unit << -w.e, w.e) - integrals = w.s >> -one.e - fractionals = w.s & (one.s-1) - divisor, kappa = bigpowten(integrals, 64 + one.e) - len = 1 - rest = 0 - while kappa > 0 - digit = div(integrals,divisor) - buffer[len] = 0x30 + digit - len += 1 - requested_digits -= 1 - integrals %= divisor - kappa -= 1 - if requested_digits == 0 - rest = (UInt64(integrals) << -one.e) + fractionals - r, kappa = roundweed(buffer, len, rest, UInt64(divisor) << -one.e, - unit,kappa) - return r, kappa, len - end - divisor = div(divisor,10) - end - while requested_digits > 0 && fractionals > unit - fractionals *= 10 - unit *= 10 - digit = fractionals >> -one.e - buffer[len] = 0x30 + digit - len += 1 - requested_digits -= 1 - fractionals &= one.s - 1 - kappa -= 1 - end - requested_digits != 0 && return false, kappa, len - r, kappa = roundweed(buffer,len,fractionals,one.s, - unit,kappa) - return r, kappa, len -end - -function fastprecision(v, requested_digits, buffer = Vector{UInt8}(undef, 100)) - f = normalize(Float64(v)) - ten_mk_min_exp = kMinExp - (f.e + FloatSignificandSize) - ten_mk_max_exp = kMaxExp - (f.e + FloatSignificandSize) - cp = binexp_cache(ten_mk_min_exp,ten_mk_max_exp) - scaled_w = f * cp - r, kappa, len = digitgen(scaled_w,buffer,requested_digits) - decimal_exponent = -cp.de + kappa - return r, len, decimal_exponent+len-1 -end diff --git a/base/grisu/fastshortest.jl b/base/grisu/fastshortest.jl deleted file mode 100644 index acd810e9d8ca7..0000000000000 --- a/base/grisu/fastshortest.jl +++ /dev/null @@ -1,118 +0,0 @@ -# This file is a part of Julia, but is derived from -# https://github.com/google/double-conversion which has the following license -# -# Copyright 2006-2014, the V8 project authors. All rights reserved. -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -const kMinExp = -60 -const kMaxExp = -32 - -function roundweed(buffer,len,rest,tk,unit,kappa,too_high::UInt64,unsafe_interval::UInt64) - small = too_high - unit - big = too_high + unit - while rest < small && - unsafe_interval - rest >= tk && - (rest + tk < small || - small - rest >= rest + tk - small) - buffer[len-1] -= 1 - rest += tk - end - if rest < big && - unsafe_interval - rest >= tk && - (rest + tk < big || - big - rest > rest + tk - big) - return false, kappa - end - return (2 * unit <= rest) && (rest <= unsafe_interval - 4 * unit), kappa -end - -const SmallPowersOfTen = [ - 0, 1, 10, 100, 1000, 10000, 100000, - 1000000, 10000000, 100000000, 1000000000] - -function bigpowten(n,n_bits) - guess = ((n_bits + 1) * 1233) >> 12 - guess += 1 - i = SmallPowersOfTen[guess+1] - return n < i ? (SmallPowersOfTen[guess], guess-1) : (i,guess) -end - -function digitgen(low,w,high,buffer) - unit::UInt64 = 1 - one = Float(unit << -w.e, w.e) - too_high = Float(high.s+unit,high.e) - unsafe_interval = too_high - Float(low.s-unit,low.e) - integrals = too_high.s >> -one.e - fractionals = too_high.s & (one.s-1) - divisor, kappa = bigpowten(integrals, 64 + one.e) - len = 1 - rest = UInt64(0) - while kappa > 0 - digit = div(integrals,divisor) - buffer[len] = 0x30 + digit - len += 1 - integrals %= divisor - kappa -= 1 - rest = (UInt64(integrals) << -one.e) + fractionals - if rest < unsafe_interval.s - r, kappa = roundweed(buffer, len, rest, UInt64(divisor) << -one.e, - unit,kappa,(too_high - w).s,unsafe_interval.s) - return r, kappa, len - end - divisor = div(divisor,10) - end - while true - fractionals *= 10 - unit *= 10 - unsafe_interval = Float(unsafe_interval.s*10,unsafe_interval.e) - digit = fractionals >> -one.e - buffer[len] = 0x30 + digit - len += 1 - fractionals &= one.s - 1 - kappa -= 1 - if fractionals < unsafe_interval.s - r, kappa = roundweed(buffer,len,fractionals,one.s, - unit,kappa,(too_high - w).s*unit,unsafe_interval.s) - return r, kappa, len - end - end -end - -function fastshortest(v, buffer = Vector{UInt8}(undef, 17)) - f = normalize(Float64(v)) - bound_minus, bound_plus = normalizedbound(v) - ten_mk_min_exp = kMinExp - (f.e + FloatSignificandSize) - ten_mk_max_exp = kMaxExp - (f.e + FloatSignificandSize) - cp = binexp_cache(ten_mk_min_exp,ten_mk_max_exp) - scaled_w = f * cp - scaled_bound_minus = bound_minus * cp - scaled_bound_plus = bound_plus * cp - r, kappa, len = digitgen(scaled_bound_minus,scaled_w, - scaled_bound_plus,buffer) - decimal_exponent = -cp.de + kappa - return r, len, decimal_exponent+len-1 -end diff --git a/base/grisu/float.jl b/base/grisu/float.jl deleted file mode 100644 index a92b94ccd4b51..0000000000000 --- a/base/grisu/float.jl +++ /dev/null @@ -1,258 +0,0 @@ -# This file is a part of Julia, but is derived from -# https://github.com/google/double-conversion which has the following license -# -# Copyright 2006-2014, the V8 project authors. All rights reserved. -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following -# disclaimer in the documentation and/or other materials provided -# with the distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import Base: -, * - -struct Float - s::UInt64 - e::Int32 - de::Int32 -end - -Float() = Float(0,0,0) -Float(x,y) = Float(x,y,Int32(0)) -Float(d::AbstractFloat) = Float(_significand(d), _exponent(d)) - -# Consts -const Float10MSBits = 0xFFC0000000000000 # used normalize(Float) -const FloatSignMask = 0x8000000000000000 # used in normalize(Float) -const FloatSignificandSize = Int32(64) - -function normalize(v::Float) - f = v.s - e::Int32 = v.e - while (f & Float10MSBits) == 0 - f <<= 10 - e -= 10 - end - while (f & FloatSignMask) == 0 - f <<= 1 - e -= 1 - end - return Float(f,e) -end -function normalize(v::Float64) - s = _significand(v); e = _exponent(v) - while (s & HiddenBit(Float64)) == 0 - s <<= UInt64(1) - e -= Int32(1) - end - s <<= UInt64(FloatSignificandSize - SignificandSize(Float64)) - e -= Int32( FloatSignificandSize - SignificandSize(Float64)) - return Float(s, e) -end - -# Float128 -#DenormalExponent(::Type{Float128}) = Int32(-ExponentBias(Float128) + 1) -#ExponentMask(::Type{Float128}) = 0x7fff0000000000000000000000000000 -#PhysicalSignificandSize(::Type{Float128}) = Int32(112) -#SignificandSize(::Type{Float128}) = Int32(113) -#ExponentBias(::Type{Float128}) = Int32(0x00003fff + PhysicalSignificandSize(Float128)) -#SignificandMask(::Type{Float128}) = 0x0000ffffffffffffffffffffffffffff -#HiddenBit(::Type{Float128}) = 0x00010000000000000000000000000000 -#uint_t(d::Float128) = reinterpret(UInt128,d) -# Float64 -DenormalExponent(::Type{Float64}) = Int32(-ExponentBias(Float64) + 1) -ExponentMask(::Type{Float64}) = 0x7FF0000000000000 -PhysicalSignificandSize(::Type{Float64}) = Int32(52) -SignificandSize(::Type{Float64}) = Int32(53) -ExponentBias(::Type{Float64}) = Int32(0x3FF + PhysicalSignificandSize(Float64)) -SignificandMask(::Type{Float64}) = 0x000FFFFFFFFFFFFF -HiddenBit(::Type{Float64}) = 0x0010000000000000 -uint_t(d::Float64) = reinterpret(UInt64,d) -# Float32 -DenormalExponent(::Type{Float32}) = Int32(-ExponentBias(Float32) + 1) -ExponentMask(::Type{Float32}) = 0x7F800000 -PhysicalSignificandSize(::Type{Float32}) = Int32(23) -SignificandSize(::Type{Float32}) = Int32(24) -ExponentBias(::Type{Float32}) = Int32(0x7F + PhysicalSignificandSize(Float32)) -SignificandMask(::Type{Float32}) = 0x007FFFFF -HiddenBit(::Type{Float32}) = 0x00800000 -uint_t(d::Float32) = reinterpret(UInt32,d) -# Float16 -DenormalExponent(::Type{Float16}) = Int32(-ExponentBias(Float16) + 1) -ExponentMask(::Type{Float16}) = 0x7c00 -PhysicalSignificandSize(::Type{Float16}) = Int32(10) -SignificandSize(::Type{Float16}) = Int32(11) -ExponentBias(::Type{Float16}) = Int32(0x000f + PhysicalSignificandSize(Float16)) -SignificandMask(::Type{Float16}) = 0x03ff -HiddenBit(::Type{Float16}) = 0x0400 -uint_t(d::Float16) = reinterpret(UInt16,d) - -function _exponent(d::T) where T<:AbstractFloat - isdenormal(d) && return DenormalExponent(T) - biased_e::Int32 = Int32((uint_t(d) & ExponentMask(T)) >> PhysicalSignificandSize(T)) - return Int32(biased_e - ExponentBias(T)) -end -function _significand(d::T) where T<:AbstractFloat - s = uint_t(d) & SignificandMask(T) - return !isdenormal(d) ? s + HiddenBit(T) : s -end -isdenormal(d::T) where {T<:AbstractFloat} = (uint_t(d) & ExponentMask(T)) == 0 - -function normalizedbound(f::AbstractFloat) - v = Float(_significand(f),_exponent(f)) - m_plus = normalize(Float((v.s << 1) + 1, v.e - 1)) - if lowerboundaryiscloser(f) - m_minus = Float((v.s << 2) - 1, v.e - 2) - else - m_minus = Float((v.s << 1) - 1, v.e - 1) - end - return Float(m_minus.s << (m_minus.e - m_plus.e), m_plus.e), m_plus -end -function lowerboundaryiscloser(f::T) where T<:AbstractFloat - physical_significand_is_zero = (uint_t(f) & SignificandMask(T)) == 0 - return physical_significand_is_zero && (_exponent(f) != DenormalExponent(T)) -end - -(-)(a::Float,b::Float) = Float(a.s - b.s,a.e,a.de) - -const FloatM32 = 0xFFFFFFFF - -function (*)(this::Float,other::Float) - a::UInt64 = this.s >> 32 - b::UInt64 = this.s & FloatM32 - c::UInt64 = other.s >> 32 - d::UInt64 = other.s & FloatM32 - ac::UInt64 = a * c - bc::UInt64 = b * c - ad::UInt64 = a * d - bd::UInt64 = b * d - tmp::UInt64 = (bd >> 32) + (ad & FloatM32) + (bc & FloatM32) - # By adding 1U << 31 to tmp we round the final result. - # Halfway cases will be round up. - tmp += UInt64(1) << 31 - result_f::UInt64 = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32) - return Float(result_f,this.e + other.e + 64,this.de) -end - -const CachedPowers = Float[ - Float(0xfa8fd5a0081c0288, -1220, -348), - Float(0xbaaee17fa23ebf76, -1193, -340), - Float(0x8b16fb203055ac76, -1166, -332), - Float(0xcf42894a5dce35ea, -1140, -324), - Float(0x9a6bb0aa55653b2d, -1113, -316), - Float(0xe61acf033d1a45df, -1087, -308), - Float(0xab70fe17c79ac6ca, -1060, -300), - Float(0xff77b1fcbebcdc4f, -1034, -292), - Float(0xbe5691ef416bd60c, -1007, -284), - Float(0x8dd01fad907ffc3c, -980, -276), - Float(0xd3515c2831559a83, -954, -268), - Float(0x9d71ac8fada6c9b5, -927, -260), - Float(0xea9c227723ee8bcb, -901, -252), - Float(0xaecc49914078536d, -874, -244), - Float(0x823c12795db6ce57, -847, -236), - Float(0xc21094364dfb5637, -821, -228), - Float(0x9096ea6f3848984f, -794, -220), - Float(0xd77485cb25823ac7, -768, -212), - Float(0xa086cfcd97bf97f4, -741, -204), - Float(0xef340a98172aace5, -715, -196), - Float(0xb23867fb2a35b28e, -688, -188), - Float(0x84c8d4dfd2c63f3b, -661, -180), - Float(0xc5dd44271ad3cdba, -635, -172), - Float(0x936b9fcebb25c996, -608, -164), - Float(0xdbac6c247d62a584, -582, -156), - Float(0xa3ab66580d5fdaf6, -555, -148), - Float(0xf3e2f893dec3f126, -529, -140), - Float(0xb5b5ada8aaff80b8, -502, -132), - Float(0x87625f056c7c4a8b, -475, -124), - Float(0xc9bcff6034c13053, -449, -116), - Float(0x964e858c91ba2655, -422, -108), - Float(0xdff9772470297ebd, -396, -100), - Float(0xa6dfbd9fb8e5b88f, -369, -92), - Float(0xf8a95fcf88747d94, -343, -84), - Float(0xb94470938fa89bcf, -316, -76), - Float(0x8a08f0f8bf0f156b, -289, -68), - Float(0xcdb02555653131b6, -263, -60), - Float(0x993fe2c6d07b7fac, -236, -52), - Float(0xe45c10c42a2b3b06, -210, -44), - Float(0xaa242499697392d3, -183, -36), - Float(0xfd87b5f28300ca0e, -157, -28), - Float(0xbce5086492111aeb, -130, -20), - Float(0x8cbccc096f5088cc, -103, -12), - Float(0xd1b71758e219652c, -77, -4), - Float(0x9c40000000000000, -50, 4), - Float(0xe8d4a51000000000, -24, 12), - Float(0xad78ebc5ac620000, 3, 20), - Float(0x813f3978f8940984, 30, 28), - Float(0xc097ce7bc90715b3, 56, 36), - Float(0x8f7e32ce7bea5c70, 83, 44), - Float(0xd5d238a4abe98068, 109, 52), - Float(0x9f4f2726179a2245, 136, 60), - Float(0xed63a231d4c4fb27, 162, 68), - Float(0xb0de65388cc8ada8, 189, 76), - Float(0x83c7088e1aab65db, 216, 84), - Float(0xc45d1df942711d9a, 242, 92), - Float(0x924d692ca61be758, 269, 100), - Float(0xda01ee641a708dea, 295, 108), - Float(0xa26da3999aef774a, 322, 116), - Float(0xf209787bb47d6b85, 348, 124), - Float(0xb454e4a179dd1877, 375, 132), - Float(0x865b86925b9bc5c2, 402, 140), - Float(0xc83553c5c8965d3d, 428, 148), - Float(0x952ab45cfa97a0b3, 455, 156), - Float(0xde469fbd99a05fe3, 481, 164), - Float(0xa59bc234db398c25, 508, 172), - Float(0xf6c69a72a3989f5c, 534, 180), - Float(0xb7dcbf5354e9bece, 561, 188), - Float(0x88fcf317f22241e2, 588, 196), - Float(0xcc20ce9bd35c78a5, 614, 204), - Float(0x98165af37b2153df, 641, 212), - Float(0xe2a0b5dc971f303a, 667, 220), - Float(0xa8d9d1535ce3b396, 694, 228), - Float(0xfb9b7cd9a4a7443c, 720, 236), - Float(0xbb764c4ca7a44410, 747, 244), - Float(0x8bab8eefb6409c1a, 774, 252), - Float(0xd01fef10a657842c, 800, 260), - Float(0x9b10a4e5e9913129, 827, 268), - Float(0xe7109bfba19c0c9d, 853, 276), - Float(0xac2820d9623bf429, 880, 284), - Float(0x80444b5e7aa7cf85, 907, 292), - Float(0xbf21e44003acdd2d, 933, 300), - Float(0x8e679c2f5e44ff8f, 960, 308), - Float(0xd433179d9c8cb841, 986, 316), - Float(0x9e19db92b4e31ba9, 1013, 324), - Float(0xeb96bf6ebadf77d9, 1039, 332), - Float(0xaf87023b9bf0ee6b, 1066, 340)] - -const CachedPowersLength = length(CachedPowers) -const CachedPowersOffset = 348 # -1 * the first decimal_exponent. -const D_1_LOG2_10 = 0.30102999566398114 # 1 / lg(10) -# Difference between the decimal exponents in the table above. -const DecimalExponentDistance = 8 -const MinDecimalExponent = -348 -const MaxDecimalExponent = 340 - -function binexp_cache(min_exponent,max_exponent) - k = ceil(Integer,(min_exponent+63)*D_1_LOG2_10) - index = div(CachedPowersOffset+k-1,DecimalExponentDistance) + 1 - cp = CachedPowers[index+1] - return cp -end diff --git a/stdlib/Printf/src/Printf.jl b/stdlib/Printf/src/Printf.jl index ac4d3d4e7dff3..7a1800c87051f 100644 --- a/stdlib/Printf/src/Printf.jl +++ b/stdlib/Printf/src/Printf.jl @@ -2,1274 +2,763 @@ module Printf +using Base.Ryu + export @printf, @sprintf -using Base.Grisu -using Base.GMP -using Unicode: textwidth -### printf formatter generation ### -const SmallFloatingPoint = Union{Float64,Float32,Float16} -const SmallNumber = Union{SmallFloatingPoint,Base.BitInteger} +# format specifier categories +const Ints = Union{Val{'d'}, Val{'i'}, Val{'u'}, Val{'x'}, Val{'X'}, Val{'o'}} +const Floats = Union{Val{'e'}, Val{'E'}, Val{'f'}, Val{'F'}, Val{'g'}, Val{'G'}, Val{'a'}, Val{'A'}} +const Chars = Union{Val{'c'}, Val{'C'}} +const Strings = Union{Val{'s'}, Val{'S'}} +const Pointer = Val{'p'} +const HexBases = Union{Val{'x'}, Val{'X'}, Val{'a'}, Val{'A'}} -function gen(s::AbstractString) - args = [] - blk = Expr(:block, :(local neg, pt, len, exp, do_out, args, buf)) - gotbuf = false - for x in parse(s) - if isa(x,AbstractString) - push!(blk.args, :(print(out, $(length(x)==1 ? x[1] : x)))) - else - c = lowercase(x[end]) - f = c=='f' ? gen_f : - c=='e' ? gen_e : - c=='a' ? gen_a : - c=='g' ? gen_g : - c=='c' ? gen_c : - c=='s' ? gen_s : - c=='p' ? gen_p : - gen_d - if !gotbuf && c != 'c' && c != 's' && c != 'p' - push!(blk.args, :(buf = $Grisu.getbuf())) - gotbuf = true - end - arg, ex = f(x...) - push!(args, arg) - push!(blk.args, ex) - end - end - push!(blk.args, :nothing) - return args, blk -end +""" +Typed representation of a format specifier. -### printf format string parsing ### +`T` is a `Val{'_'}`, where `_` is a valid format specifier character. -function parse(s::AbstractString) - # parse format string into strings and format tuples - list = [] - a = Iterators.Stateful(pairs(s)) - lastparse = firstindex(s) - lastidx = 0 # invariant: lastidx == prevind(s, idx) - for (idx, c) in a - if c == '%' - lastparse > lastidx || push!(list, s[lastparse:lastidx]) - flags, width, precision, conversion = parse1!(s, a) - '\'' in flags && error("printf format flag ' not yet supported") - conversion == 'n' && error("printf feature %n not supported") - push!(list, conversion == '%' ? "%" : (flags,width,precision,conversion)) - lastparse = isempty(a) ? lastindex(s)+1 : peek(a)[1] - end - lastidx = idx - end - lastparse > lastindex(s) || push!(list, s[lastparse:end]) - # coalesce adjacent strings - i = j = 1 - while i < length(list) - if isa(list[i],AbstractString) - for outer j = i+1:length(list) - if !isa(list[j],AbstractString) - j -= 1 - break - end - list[i] *= list[j] - end - deleteat!(list,i+1:j) - end - i += 1 - end - return list -end +Fields are the various modifiers allowed for various format specifiers. +""" +struct Spec{T} # T => %type => Val{'type'} + leftalign::Bool + plus::Bool + space::Bool + zero::Bool + hash::Bool + width::Int + precision::Int +end + +# recreate the format specifier string from a typed Spec +Base.string(f::Spec{T}; modifier::String="") where {T} = + string("%", f.leftalign ? "-" : "", f.plus ? "+" : "", f.space ? " " : "", + f.zero ? "0" : "", f.hash ? "#" : "", f.width > 0 ? f.width : "", + f.precision == 0 ? ".0" : f.precision > 0 ? ".$(f.precision)" : "", modifier, char(T)) +Base.show(io::IO, f::Spec) = print(io, string(f)) + +ptrfmt(s::Spec{T}, x) where {T} = + Spec{Val{'x'}}(s.leftalign, s.plus, s.space, s.zero, true, s.width, sizeof(x) == 8 ? 16 : 8) -## parse a single printf specifier ## +""" + Printf.Format(format_str) -# printf specifiers: -# % # start -# (\d+\$)? # arg (not supported) -# [\-\+#0' ]* # flags -# (\d+)? # width -# (\.\d*)? # precision -# (h|hh|l|ll|L|j|t|z|q)? # modifier (ignored) -# [diouxXeEfFgGaAcCsSp%] # conversion +Create a C printf-compatible format object that can be used for formatting values. -pop_or_die!(s, a) = !isempty(a) ? popfirst!(a) : - throw(ArgumentError("invalid printf format string: $(repr(s))")) +The input `format_str` can include any valid format specifier character and modifiers. -function parse1!(s, a) - width = 0 - precision = -1 - k, c = pop_or_die!(s, a) - j = k - # handle %% - if c == '%' - return "", width, precision, c - end - # parse flags - while c in "#0- + '" - k, c = pop_or_die!(s, a) - end - flags = String(s[j:k-1]) # All flags are 1 byte - # parse width - while '0' <= c <= '9' - width = 10*width + c-'0' - _, c = pop_or_die!(s, a) - end - # parse precision - if c == '.' - _, c = pop_or_die!(s, a) - if '0' <= c <= '9' - precision = 0 - while '0' <= c <= '9' - precision = 10*precision + c-'0' - _, c = pop_or_die!(s, a) +A `Format` object can be passed to `Printf.format(f::Format, args...)` to produce a +formatted string, or `Printf.format(io::IO, f::Format, args...)` to print the +formatted string directly to `io`. + +For convenience, the `Printf.format"..."` string macro form can be used for building +a `Printf.Format` object at macro-expansion-time. +""" +struct Format{S, T} + str::S # original full format string as CodeUnits + # keep track of non-format specifier strings to print + # length(substringranges) == length(formats) + 1 + # so when printing, we start with printing + # str[substringranges[1]], then formats[1] + args[1] + # then str[substringranges[2]], then formats[2] + args[2] + # and so on, then at the end, str[substringranges[end]] + substringranges::Vector{UnitRange{Int}} + formats::T # Tuple of Specs +end + +# what number base should be used for a given format specifier? +base(T) = T <: HexBases ? 16 : T <: Val{'o'} ? 8 : 10 +char(::Type{Val{c}}) where {c} = c + +# parse format string +function Format(f::AbstractString) + isempty(f) && throw(ArgumentError("empty format string")) + bytes = codeunits(f) + len = length(bytes) + pos = 1 + b = 0x00 + while true + b = bytes[pos] + pos += 1 + (pos > len || (b == UInt8('%') && pos <= len && bytes[pos] != UInt8('%'))) && break + end + strs = [1:pos - 1 - (b == UInt8('%'))] + fmts = [] + while pos <= len + b = bytes[pos] + pos += 1 + # positioned at start of first format str % + # parse flags + leftalign = plus = space = zero = hash = false + while true + if b == UInt8('-') + leftalign = true + elseif b == UInt8('+') + plus = true + elseif b == UInt8(' ') + space = true + elseif b == UInt8('0') + zero = true + elseif b == UInt8('#') + hash = true + else + break + end + pos > len && throw(ArgumentError("incomplete format string: '$f'")) + b = bytes[pos] + pos += 1 + end + if leftalign + zero = false + end + # parse width + width = 0 + while b - UInt8('0') < 0x0a + width = 10 * width + (b - UInt8('0')) + b = bytes[pos] + pos += 1 + pos > len && break + end + # parse precision + precision = 0 + parsedprecdigits = false + if b == UInt8('.') + pos > len && throw(ArgumentError("incomplete format string: '$f'")) + parsedprecdigits = true + b = bytes[pos] + pos += 1 + if pos <= len + while b - UInt8('0') < 0x0a + precision = 10precision + (b - UInt8('0')) + b = bytes[pos] + pos += 1 + pos > len && break + end end end - end - # parse length modifer (ignored) - if c == 'h' || c == 'l' - prev = c - _, c = pop_or_die!(s, a) - if c == prev - _, c = pop_or_die!(s, a) + # parse length modifier (ignored) + if b == UInt8('h') || b == UInt8('l') + prev = b + b = bytes[pos] + pos += 1 + if b == prev + pos > len && throw(ArgumentError("invalid format string: '$f'")) + b = bytes[pos] + pos += 1 + end + elseif b in b"Ljqtz" + b = bytes[pos] + pos += 1 + end + # parse type + !(b in b"diouxXDOUeEfFgGaAcCsSpn") && throw(ArgumentError("invalid format string: '$f', invalid type specifier: '$(Char(b))'")) + type = Val{Char(b)} + if type <: Ints && precision > 0 + zero = false + elseif (type <: Strings || type <: Chars) && !parsedprecdigits + precision = -1 + elseif type <: Union{Val{'a'}, Val{'A'}} && !parsedprecdigits + precision = -1 + elseif type <: Floats && !parsedprecdigits + precision = 6 + end + push!(fmts, Spec{type}(leftalign, plus, space, zero, hash, width, precision)) + start = pos + prevperc = false + while pos <= len + b = bytes[pos] + pos += 1 + if b == UInt8('%') + pos > len && throw(ArgumentError("invalid format string: '$f'")) + if bytes[pos] == UInt8('%') + pos += 1 + pos > len && break + b = bytes[pos] + pos += 1 + else + break + end + end end - elseif c in "Ljqtz" - _, c = pop_or_die!(s, a) - end - # validate conversion - if !(c in "diouxXDOUeEfFgGaAcCsSpn") - throw(ArgumentError("invalid printf format string: $(repr(s))")) + push!(strs, start:pos - 1 - (b == UInt8('%'))) end - # TODO: warn about silly flag/conversion combinations - flags, width, precision, c + return Format(bytes, strs, Tuple(fmts)) end -### printf formatter generation ### - -function special_handler(flags::String, width::Int) - @gensym x - blk = Expr(:block) - pad = '-' in flags ? rpad : lpad - pos = '+' in flags ? "+" : - ' ' in flags ? " " : "" - abn = quote - isnan($x) ? $(pad("NaN", width)) : - $x < 0 ? $(pad("-Inf", width)) : - $(pad("$(pos)Inf", width)) - end - ex = :(isfinite($x) ? $blk : print(out, $abn)) - x, ex, blk +macro format_str(str) + Format(str) end -function pad(m::Int, n, c::Char) - if m <= 1 - :($n > 0 && print(out,$c)) - else - @gensym i - quote - $i = $n - while $i > 0 - print(out,$c) - $i -= 1 - end - end - end -end +const hex = b"0123456789abcdef" +const HEX = b"0123456789ABCDEF" -function dynamic_pad(m, val, c::Char) - @gensym i - quote - if $m <= 1 - $val > 0 && print(out,$c) - else - $i = $val - while $i > 0 - print(out,$c) - $i -= 1 - end +# write out a single arg according to format options +# char +@inline function writechar(buf, pos, c) + u = bswap(reinterpret(UInt32, c)) + while true + buf[pos] = u % UInt8 + pos += 1 + (u >>= 8) == 0 && break + end + return pos +end + +@inline function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Chars} + leftalign, width = spec.leftalign, spec.width + if !leftalign && width > 1 + for _ = 1:(width - 1) + buf[pos] = UInt8(' ') + pos += 1 + end + end + pos = writechar(buf, pos, arg isa String ? arg[1] : Char(arg)) + if leftalign && width > 1 + for _ = 1:(width - 1) + buf[pos] = UInt8(' ') + pos += 1 end end + return pos end -# returns the number of (ASCII) chars output by print_fixed -function print_fixed_width(precision, pt, ndigits, trailingzeros=true) - count = 0 - if pt <= 0 - # 0.0dddd0 - count += 2 - precision += pt - if pt < 0 - count -= pt +# strings +@inline function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Strings} + leftalign, hash, width, prec = spec.leftalign, spec.hash, spec.width, spec.precision + str = string(arg) + op = p = prec == -1 ? (length(str) + (hash ? arg isa AbstractString ? 2 : 1 : 0)) : prec + if !leftalign && width > p + for _ = 1:(width - p) + buf[pos] = UInt8(' ') + pos += 1 end - count += ndigits - precision -= ndigits - elseif ndigits <= pt - # dddd000.000000 - count += ndigits - if ndigits < pt - count += pt - ndigits + end + if hash + if arg isa Symbol + buf[pos] = UInt8(':') + pos += 1 + p -= 1 + elseif arg isa AbstractString + buf[pos] = UInt8('"') + pos += 1 + p -= 1 end - count += trailingzeros - else # 0 < pt < ndigits - # dd.dd0000 - ndigits -= pt - count += pt + 1 + ndigits - precision -= ndigits end - if trailingzeros && precision > 0 - count += precision + for c in str + p == 0 && break + pos = writechar(buf, pos, c) + p -= 1 + end + if hash && arg isa AbstractString && p > 0 + buf[pos] = UInt8('"') + pos += 1 + end + if leftalign && width > op + for _ = 1:(width - op) + buf[pos] = UInt8(' ') + pos += 1 + end end - return count + return pos end -# note: if print_fixed is changed, print_fixed_width should be changed accordingly -function print_fixed(out, precision, pt, ndigits, trailingzeros=true, buf = Grisu.getbuf()) - pdigits = pointer(buf) - if pt <= 0 - # 0.0dddd0 - print(out, '0') - print(out, '.') - precision += pt - while pt < 0 - print(out, '0') - pt += 1 +# integers +@inline function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Ints} + leftalign, plus, space, zero, hash, width, prec = + spec.leftalign, spec.plus, spec.space, spec.zero, spec.hash, spec.width, spec.precision + bs = base(T) + arg2 = arg isa AbstractFloat ? Integer(trunc(arg)) : arg + n = i = ndigits(arg2, base=bs, pad=1) + x, neg = arg2 < 0 ? (-arg2, true) : (arg2, false) + arglen = n + (neg || (plus | space)) + + (T == Val{'o'} && hash ? 1 : 0) + + (T == Val{'x'} && hash ? 2 : 0) + (T == Val{'X'} && hash ? 2 : 0) + arglen2 = arglen < width && prec > 0 ? arglen + min(max(0, prec - n), width - arglen) : arglen + if !leftalign && !zero && arglen2 < width + # pad left w/ spaces + for _ = 1:(width - arglen2) + buf[pos] = UInt8(' ') + pos += 1 end - unsafe_write(out, pdigits, ndigits) - precision -= ndigits - elseif ndigits <= pt - # dddd000.000000 - unsafe_write(out, pdigits, ndigits) - while ndigits < pt - print(out, '0') - ndigits += 1 + end + if neg + buf[pos] = UInt8('-'); pos += 1 + elseif plus # plus overrides space + buf[pos] = UInt8('+'); pos += 1 + elseif space + buf[pos] = UInt8(' '); pos += 1 + end + if T == Val{'o'} && hash + buf[pos] = UInt8('0') + pos += 1 + elseif T == Val{'x'} && hash + buf[pos] = UInt8('0') + buf[pos + 1] = UInt8('x') + pos += 2 + elseif T == Val{'X'} && hash + buf[pos] = UInt8('0') + buf[pos + 1] = UInt8('X') + pos += 2 + end + if zero && arglen2 < width + for _ = 1:(width - arglen2) + buf[pos] = UInt8('0') + pos += 1 + end + elseif n < prec + for _ = 1:(prec - n) + buf[pos] = UInt8('0') + pos += 1 + end + elseif arglen < arglen2 + for _ = 1:(arglen2 - arglen) + buf[pos] = UInt8('0') + pos += 1 end - if trailingzeros - print(out, '.') - end - else # 0 < pt < ndigits - # dd.dd0000 - ndigits -= pt - unsafe_write(out, pdigits, pt) - print(out, '.') - unsafe_write(out, pdigits+pt, ndigits) - precision -= ndigits end - if trailingzeros - while precision > 0 - print(out, '0') - precision -= 1 + while i > 0 + @inbounds buf[pos + i - 1] = bs == 16 ? + (T == Val{'x'} ? hex[(x & 0x0f) + 1] : HEX[(x & 0x0f) + 1]) : + (48 + (bs == 8 ? (x & 0x07) : rem(x, 10))) + if bs == 8 + x >>= 3 + elseif bs == 16 + x >>= 4 + else + x = oftype(x, div(x, 10)) end + i -= 1 end -end - -function print_exp_e(out, exp::Integer) - print(out, exp < 0 ? '-' : '+') - exp = abs(exp) - d = div(exp,100) - if d > 0 - if d >= 10 - print(out, exp) - return + pos += n + if leftalign && arglen2 < width + # pad right + for _ = 1:(width - arglen2) + buf[pos] = UInt8(' ') + pos += 1 end - print(out, Char('0'+d)) end - exp = rem(exp,100) - print(out, Char('0'+div(exp,10))) - print(out, Char('0'+rem(exp,10))) + return pos end -function print_exp_a(out, exp::Integer) - print(out, exp < 0 ? '-' : '+') - exp = abs(exp) - print(out, exp) -end +# floats +""" + Printf.tofloat(x) +Convert an argument to a Base float type for printf formatting. +By default, arguments are converted to `Float64` via `Float64(x)`. +Custom numeric types that have a conversion to a Base float type +that wish to hook into printf formatting can extend this method like: -function gen_d(flags::String, width::Int, precision::Int, c::Char) - # print integer: - # [dDiu]: print decimal digits - # [o]: print octal digits - # [x]: print hex digits, lowercase - # [X]: print hex digits, uppercase - # - # flags: - # (#): prefix hex with 0x/0X; octal leads with 0 - # (0): pad left with zeros - # (-): left justify - # ( ): precede non-negative values with " " - # (+): precede non-negative values with "+" - # - x, ex, blk = special_handler(flags,width) - # interpret the number - prefix = "" - if lowercase(c)=='o' - fn = '#' in flags ? :decode_0ct : :decode_oct - elseif c=='x' - '#' in flags && (prefix = "0x") - fn = :decode_hex - elseif c=='X' - '#' in flags && (prefix = "0X") - fn = :decode_HEX - else - fn = :decode_dec - end - push!(blk.args, :((do_out, args) = $fn(out, $x, $flags, $width, $precision, $c, buf))) - ifblk = Expr(:if, :do_out, Expr(:block)) - push!(blk.args, ifblk) - blk = ifblk.args[2] - push!(blk.args, :((len, pt, neg) = args)) - # calculate padding - width -= length(prefix) - space_pad = width > max(1,precision) && '-' in flags || - precision < 0 && width > 1 && !('0' in flags) || - precision >= 0 && width > precision - padding = nothing - if precision < 1; precision = 1; end - if space_pad - if '+' in flags || ' ' in flags - width -= 1 - if width > precision - padding = :($width-(pt > $precision ? pt : $precision)) - end - else - if width > precision - padding = :($width-neg-(pt > $precision ? pt : $precision)) - end - end - end - # print space padding - if padding !== nothing && !('-' in flags) - push!(blk.args, pad(width-precision, padding, ' ')) - end - # print sign - '+' in flags ? push!(blk.args, :(print(out, neg ? '-' : '+'))) : - ' ' in flags ? push!(blk.args, :(print(out, neg ? '-' : ' '))) : - push!(blk.args, :(neg && print(out, '-'))) - # print prefix - for ch in prefix - push!(blk.args, :(print(out, $ch))) - end - # print zero padding & leading zeros - if space_pad && precision > 1 - push!(blk.args, pad(precision-1, :($precision-pt), '0')) - elseif !space_pad && width > 1 - zeros = '+' in flags || ' ' in flags ? :($(width-1)-pt) : :($width-neg-pt) - push!(blk.args, pad(width-1, zeros, '0')) - end - # print integer - push!(blk.args, :(unsafe_write(out, pointer(buf), pt))) - # print padding - if padding !== nothing && '-' in flags - push!(blk.args, pad(width-precision, padding, ' ')) - end - # return arg, expr - :(($x)::Real), ex -end +```julia +Printf.tofloat(x::MyCustomType) = convert_my_custom_type_to_float(x) +``` -function gen_f(flags::String, width::Int, precision::Int, c::Char) - # print to fixed trailing precision - # [fF]: the only choice - # - # flags - # (#): always print a decimal point - # (0): pad left with zeros - # (-): left justify - # ( ): precede non-negative values with " " - # (+): precede non-negative values with "+" - # - x, ex, blk = special_handler(flags,width) - # interpret the number - if precision < 0; precision = 6; end - push!(blk.args, :((do_out, args) = fix_dec(out, $x, $flags, $width, $precision, $c, buf))) - ifblk = Expr(:if, :do_out, Expr(:block)) - push!(blk.args, ifblk) - blk = ifblk.args[2] - push!(blk.args, :((len, pt, neg) = args)) - # calculate padding - padding = nothing - if precision > 0 || '#' in flags - width -= precision+1 - end - if '+' in flags || ' ' in flags - width -= 1 - if width > 1 - padding = :($width-(pt > 0 ? pt : 1)) - end - else - if width > 1 - padding = :($width-(pt > 0 ? pt : 1)-neg) - end - end - # print space padding - if padding !== nothing && !('-' in flags) && !('0' in flags) - push!(blk.args, pad(width-1, padding, ' ')) - end - # print sign - '+' in flags ? push!(blk.args, :(print(out, neg ? '-' : '+'))) : - ' ' in flags ? push!(blk.args, :(print(out, neg ? '-' : ' '))) : - push!(blk.args, :(neg && print(out, '-'))) - # print zero padding - if padding !== nothing && !('-' in flags) && '0' in flags - push!(blk.args, pad(width-1, padding, '0')) - end - # print digits - if precision > 0 - push!(blk.args, :(print_fixed(out,$precision,pt,len,true,buf))) - else - push!(blk.args, :(unsafe_write(out, pointer(buf), len))) - push!(blk.args, :(while pt >= (len+=1) print(out,'0') end)) - '#' in flags && push!(blk.args, :(print(out, '.'))) - end - # print space padding - if padding !== nothing && '-' in flags - push!(blk.args, pad(width-1, padding, ' ')) - end - # return arg, expr - :(($x)::Real), ex -end +For arbitrary precision numerics, you might extend the method like: -function gen_e(flags::String, width::Int, precision::Int, c::Char, inside_g::Bool=false) - # print float in scientific form: - # [e]: use 'e' to introduce exponent - # [E]: use 'E' to introduce exponent - # - # flags: - # (#): always print a decimal point - # (0): pad left with zeros - # (-): left justify - # ( ): precede non-negative values with " " - # (+): precede non-negative values with "+" - # - x, ex, blk = if inside_g - @gensym x - blk = Expr(:block) - x, blk, blk - else - special_handler(flags,width) - end - # interpret the number - if precision < 0; precision = 6; end - ndigits = min(precision+1,length(Grisu.getbuf())-1) - push!(blk.args, :((do_out, args) = ini_dec(out,$x,$ndigits, $flags, $width, $precision, $c, buf))) - push!(blk.args, :(digits = buf)) - ifblk = Expr(:if, :do_out, Expr(:block)) - push!(blk.args, ifblk) - blk = ifblk.args[2] - push!(blk.args, :((len, pt, neg) = args)) - push!(blk.args, :(exp = pt-1)) - expmark = isuppercase(c) ? "E" : "e" - if precision==0 && '#' in flags - expmark = string(".",expmark) - end - # calculate padding - padding = nothing - width -= precision+length(expmark)+(precision>0)+4 - # 4 = leading + expsign + 2 exp digits - if '+' in flags || ' ' in flags - width -= 1 # for the sign indicator - if width > 0 - padding = quote - padn=$width - if (exp<=-100)|(100<=exp) - if isa($x,SmallNumber) - padn -= 1 - else - padn -= Base.ndigits0z(exp) - 2 +```julia +Printf.tofloat(x::MyArbitraryPrecisionType) = BigFloat(x) +``` +""" +tofloat(x) = Float64(x) +tofloat(x::Base.IEEEFloat) = x +tofloat(x::BigFloat) = x + +@inline function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Floats} + leftalign, plus, space, zero, hash, width, prec = + spec.leftalign, spec.plus, spec.space, spec.zero, spec.hash, spec.width, spec.precision + x = tofloat(arg) + if x isa BigFloat && isfinite(x) + ptr = pointer(buf, pos) + newpos = @ccall "libmpfr".mpfr_snprintf(ptr::Ptr{UInt8}, (length(buf) - pos + 1)::Csize_t, string(spec; modifier="R")::Ptr{UInt8}; arg::Ref{BigFloat})::Cint + newpos > 0 || error("invalid printf formatting for BigFloat") + return pos + newpos + elseif x isa BigFloat + x = Float64(x) + end + if T == Val{'e'} || T == Val{'E'} + newpos = Ryu.writeexp(buf, pos, x, prec, plus, space, hash, char(T), UInt8('.')) + elseif T == Val{'f'} || T == Val{'F'} + newpos = Ryu.writefixed(buf, pos, x, prec, plus, space, hash, UInt8('.')) + elseif T == Val{'g'} || T == Val{'G'} + prec = prec == 0 ? 1 : prec + x = round(x, sigdigits=prec) + newpos = Ryu.writeshortest(buf, pos, x, plus, space, hash, prec, T == Val{'g'} ? UInt8('e') : UInt8('E'), true, UInt8('.')) + elseif T == Val{'a'} || T == Val{'A'} + x, neg = x < 0 ? (-x, true) : (x, false) + newpos = pos + if neg + buf[newpos] = UInt8('-') + newpos += 1 + elseif plus + buf[newpos] = UInt8('+') + newpos += 1 + elseif space + buf[newpos] = UInt8(' ') + newpos += 1 + end + if isnan(x) + buf[newpos] = UInt8('N') + buf[newpos + 1] = UInt8('a') + buf[newpos + 2] = UInt8('N') + newpos += 3 + elseif !isfinite(x) + buf[newpos] = UInt8('I') + buf[newpos + 1] = UInt8('n') + buf[newpos + 2] = UInt8('f') + newpos += 3 + else + buf[newpos] = UInt8('0') + newpos += 1 + buf[newpos] = T <: Val{'a'} ? UInt8('x') : UInt8('X') + newpos += 1 + if arg == 0 + buf[newpos] = UInt8('0') + newpos += 1 + if prec > 0 + while prec > 0 + buf[newpos] = UInt8('0') + newpos += 1 + prec -= 1 end end - padn - end - end - else - if width > 0 - padding = quote - padn=$width-neg - if (exp<=-100)|(100<=exp) - if isa($x,SmallNumber) - padn -= 1 - else - padn -= Base.ndigits0z(exp) - 2 - end + buf[newpos] = T <: Val{'a'} ? UInt8('p') : UInt8('P') + buf[newpos + 1] = UInt8('+') + buf[newpos + 2] = UInt8('0') + else + if prec > -1 + s, p = frexp(x) + sigbits = 4 * min(prec, 13) + s = 0.25 * round(ldexp(s, 1 + sigbits)) + # ensure last 2 exponent bits either 01 or 10 + u = (reinterpret(UInt64, s) & 0x003f_ffff_ffff_ffff) >> (52 - sigbits) + i = n = (sizeof(u) << 1) - (leading_zeros(u) >> 2) + else + s, p = frexp(x) + s *= 2.0 + u = (reinterpret(UInt64, s) & 0x001f_ffff_ffff_ffff) + t = (trailing_zeros(u) >> 2) + u >>= (t << 2) + i = n = 14 - t end - padn + frac = u > 9 || hash || prec > 0 + while i > 1 + buf[newpos + i] = T == Val{'a'} ? hex[(u & 0x0f) + 1] : HEX[(u & 0x0f) + 1] + u >>= 4 + i -= 1 + prec -= 1 + end + if frac + buf[newpos + 1] = UInt8('.') + end + buf[newpos] = T == Val{'a'} ? hex[(u & 0x0f) + 1] : HEX[(u & 0x0f) + 1] + newpos += n + frac + while prec > 0 + buf[newpos] = UInt8('0') + newpos += 1 + prec -= 1 + end + buf[newpos] = T <: Val{'a'} ? UInt8('p') : UInt8('P') + newpos += 1 + p -= 1 + buf[newpos] = p < 0 ? UInt8('-') : UInt8('+') + p = p < 0 ? -p : p + newpos += 1 + n = i = ndigits(p, base=10, pad=1) + while i > 0 + buf[newpos + i - 1] = 48 + rem(p, 10) + p = oftype(p, div(p, 10)) + i -= 1 + end + newpos += n end end end - # print space padding - if padding !== nothing && !('-' in flags) && !('0' in flags) - push!(blk.args, pad(width, padding, ' ')) - end - # print sign - '+' in flags ? push!(blk.args, :(print(out, neg ? '-' : '+'))) : - ' ' in flags ? push!(blk.args, :(print(out, neg ? '-' : ' '))) : - push!(blk.args, :(neg && print(out, '-'))) - # print zero padding - if padding !== nothing && !('-' in flags) && '0' in flags - push!(blk.args, pad(width, padding, '0')) - end - # print digits - push!(blk.args, :(write(out, digits[1]))) - if precision > 0 - if inside_g && !('#' in flags) - push!(blk.args, :(endidx = $ndigits; - while endidx > 1 && digits[endidx] == UInt8('0') - endidx -= 1 - end; - if endidx > 1 - print(out, '.') - unsafe_write(out, pointer(digits)+1, endidx-1) - end - )) - else - push!(blk.args, :(print(out, '.'))) - push!(blk.args, :(unsafe_write(out, pointer(digits)+1, $(ndigits-1)))) - if ndigits < precision+1 - n = precision+1-ndigits - push!(blk.args, pad(n, n, '0')) + if newpos - pos < width + # need to pad + if leftalign + # easy case, just pad spaces after number + for _ = 1:(width - (newpos - pos)) + buf[newpos] = UInt8(' ') + newpos += 1 end - end - end - for ch in expmark - push!(blk.args, :(print(out, $ch))) - end - push!(blk.args, :(print_exp_e(out, exp))) - # print space padding - if padding !== nothing && '-' in flags - push!(blk.args, pad(width, padding, ' ')) - end - # return arg, expr - :(($x)::Real), ex -end - -function gen_a(flags::String, width::Int, precision::Int, c::Char) - # print float in hexadecimal format - # [a]: lowercase hex float, e.g. -0x1.cfp-2 - # [A]: uppercase hex float, e.g. -0X1.CFP-2 - # - # flags: - # (#): always print a decimal point - # (0): pad left with zeros - # (-): left justify - # ( ): precede non-negative values with " " - # (+): precede non-negative values with "+" - # - x, ex, blk = special_handler(flags,width) - if c == 'A' - hexmark, expmark = "0X", "P" - fn = :ini_HEX - else - hexmark, expmark = "0x", "p" - fn = :ini_hex - end - # if no precision, print max non-zero - if precision < 0 - push!(blk.args, :((do_out, args) = $fn(out,$x, $flags, $width, $precision, $c, buf))) - else - ndigits = min(precision+1,length(Grisu.getbuf())-1) - push!(blk.args, :((do_out, args) = $fn(out,$x,$ndigits, $flags, $width, $precision, $c, buf))) - end - push!(blk.args, :(digits = buf)) - ifblk = Expr(:if, :do_out, Expr(:block)) - push!(blk.args, ifblk) - blk = ifblk.args[2] - push!(blk.args, :((len, exp, neg) = args)) - if precision==0 && '#' in flags - expmark = string(".",expmark) - end - # calculate padding - padding = nothing - if precision > 0 - width -= precision+length(hexmark)+length(expmark)+4 - # 4 = leading + expsign + 1 exp digit + decimal - else - width -= length(hexmark)+length(expmark)+3+(precision<0 && '#' in flags) - # 3 = leading + expsign + 1 exp digit - end - if '+' in flags || ' ' in flags - width -= 1 # for the sign indicator - if width > 0 - padding = :($(width+1) - Base.ndigits(exp)) - end - else - if width > 0 - padding = :($(width+1) - neg - Base.ndigits(exp)) - end - end - if precision < 0 && width > 0 - if '#' in flags - padding = :($padding - (len-1)) else - padding = :($padding - (len>1 ? len : 0)) + # right aligned + n = width - (newpos - pos) + if zero + ex = (arg < 0 || (plus | space)) + (T <: Union{Val{'a'}, Val{'A'}} ? 2 : 0) + so = pos + ex + len = (newpos - pos) - ex + copyto!(buf, so + n, buf, so, len) + for i = so:(so + n - 1) + buf[i] = UInt8('0') + end + newpos += n + else + copyto!(buf, pos + n, buf, pos, newpos - pos) + for i = pos:(pos + n - 1) + buf[i] = UInt8(' ') + end + newpos += n + end end end - # print space padding - if padding !== nothing && !('-' in flags) && !('0' in flags) - push!(blk.args, pad(width, padding, ' ')) - end - # print sign - '+' in flags ? push!(blk.args, :(print(out, neg ? '-' : '+'))) : - ' ' in flags ? push!(blk.args, :(print(out, neg ? '-' : ' '))) : - push!(blk.args, :(neg && print(out, '-'))) - # hex prefix - for ch in hexmark - push!(blk.args, :(print(out, $ch))) - end - # print zero padding - if padding !== nothing && !('-' in flags) && '0' in flags - push!(blk.args, pad(width, padding, '0')) - end - # print digits: assumes ASCII/UTF8 encoding of digits is okay for `out` - push!(blk.args, :(write(out, digits[1]))) - if precision > 0 - push!(blk.args, :(print(out, '.'))) - push!(blk.args, :(unsafe_write(out, pointer(digits)+1, $(ndigits-1)))) - if ndigits < precision+1 - n = precision+1-ndigits - push!(blk.args, pad(n, n, '0')) + return newpos +end + +# pointers +fmt(buf, pos, arg, spec::Spec{Pointer}) = fmt(buf, pos, Int(arg), ptrfmt(spec, arg)) + +# old Printf compat +function fix_dec end +function ini_dec end + +# generic fallback +function fmtfallback(buf, pos, arg, spec::Spec{T}) where {T} + leftalign, plus, space, zero, hash, width, prec = + spec.leftalign, spec.plus, spec.space, spec.zero, spec.hash, spec.width, spec.precision + buf2 = Base.StringVector(309 + 17 + 5) + ise = T <: Union{Val{'e'}, Val{'E'}} + isg = T <: Union{Val{'g'}, Val{'G'}} + isf = T <: Val{'f'} + if isg + prec = prec == 0 ? 1 : prec + arg = round(arg, sigdigits=prec) + end + n, pt, neg = isf ? fix_dec(arg, prec, buf2) : ini_dec(arg, min(prec + ise, length(buf2) - 1), buf2) + if isg && !hash + while buf2[n] == UInt8('0') + n -= 1 + end + end + expform = ise || (isg && !(-4 < pt <= prec)) + n2 = n + (expform ? 4 : 0) + (prec > 0 || hash) + (neg || (plus | space)) + + (isf && pt >= n ? prec + 1 : 0) + if !leftalign && !zero && n2 < width + # pad left w/ spaces + for _ = 1:(width - n2) + buf[pos] = UInt8(' ') + pos += 1 + end + end + if neg + buf[pos] = UInt8('-'); pos += 1 + elseif plus # plus overrides space + buf[pos] = UInt8('+'); pos += 1 + elseif space + buf[pos] = UInt8(' '); pos += 1 + end + if zero && n2 < width + for _ = 1:(width - n2) + buf[pos] = UInt8('0') + pos += 1 + end + end + if expform + buf[pos] = buf2[1] + pos += 1 + if n > 1 || hash + buf[pos] = UInt8('.') + pos += 1 + for i = 2:n + buf[pos] = buf2[i] + pos += 1 + end end - elseif precision < 0 - ifvpblk = Expr(:if, :(len > 1), Expr(:block)) - vpblk = ifvpblk.args[2] - if '#' in flags - push!(blk.args, :(print(out, '.'))) + buf[pos] = T <: Val{'e'} || T <: Val{'g'} ? UInt8('e') : UInt8('E') + pos += 1 + exp = pt - 1 + buf[pos] = exp < 0 ? UInt8('-') : UInt8('+') + pos += 1 + exp = abs(exp) + if exp < 10 + buf[pos] = UInt8('0') + buf[pos + 1] = 48 + exp + pos += 2 else - push!(vpblk.args, :(print(out, '.'))) - end - push!(vpblk.args, :(unsafe_write(out, pointer(digits)+1, len-1))) - push!(blk.args, ifvpblk) - end - for ch in expmark - push!(blk.args, :(print(out, $ch))) - end - push!(blk.args, :(print_exp_a(out, exp))) - # print space padding - if padding !== nothing && '-' in flags - push!(blk.args, pad(width, padding, ' ')) - end - # return arg, expr - :(($x)::Real), ex -end - -function gen_c(flags::String, width::Int, precision::Int, c::Char) - # print a character: - # [cC]: both the same for us (Unicode) - # - # flags: - # (0): pad left with zeros - # (-): left justify - # - @gensym x - blk = Expr(:block, :($x = Char($x))) - if width > 1 && !('-' in flags) - p = '0' in flags ? '0' : ' ' - push!(blk.args, pad(width-1, :($width-textwidth($x)), p)) - end - push!(blk.args, :(print(out, $x))) - if width > 1 && '-' in flags - push!(blk.args, pad(width-1, :($width-textwidth($x)), ' ')) - end - :(($x)::Integer), blk -end - -function _limit(s, prec) - prec >= sizeof(s) && return s - p = prevind(s, prec+1) - n = nextind(s, p)-1 - s[1:(prec>=n ? n : prevind(s,p))] -end - -function gen_s(flags::String, width::Int, precision::Int, c::Char) - # print a string: - # [sS]: both the same for us (Unicode) - # - # flags: - # (-): left justify - # (#): use `show`/`repr` instead of `print`/`string` - # - @gensym x - blk = Expr(:block) - if width > 0 - if !('#' in flags) - push!(blk.args, :($x = string($x))) - else - push!(blk.args, :($x = repr($x))) - end - if precision!=-1 - push!(blk.args, :($x = _limit($x, $precision))) - end - if !('-' in flags) - push!(blk.args, pad(width, :($width-textwidth($x)), ' ')) + buf[pos] = 48 + div(exp, 10) + buf[pos + 1] = 48 + rem(exp, 10) + pos += 2 + end + elseif pt <= 0 + buf[pos] = UInt8('0') + buf[pos + 1] = UInt8('.') + pos += 2 + while pt < 0 + buf[pos] = UInt8('0') + pos += 1 + pt += 1 end - push!(blk.args, :(print(out, $x))) - if '-' in flags - push!(blk.args, pad(width, :($width-textwidth($x)), ' ')) + for i = 1:n + buf[pos] = buf2[i] + pos += 1 + end + elseif pt >= n + for i = 1:n + buf[pos] = buf2[i] + pos += 1 + end + while pt > n + buf[pos] = UInt8('0') + pos += 1 + n += 1 + end + if hash || (isf && prec > 0) + buf[pos] = UInt8('.') + pos += 1 + while prec > 0 + buf[pos] = UInt8('0') + pos += 1 + prec -= 1 + end end else - if precision!=-1 - push!(blk.args, :(io = IOBuffer())) - else - push!(blk.args, :(io = out)) - end - if !('#' in flags) - push!(blk.args, :(print(io, $x))) - else - push!(blk.args, :(show(io, $x))) + for i = 1:pt + buf[pos] = buf2[i] + pos += 1 end - if precision!=-1 - push!(blk.args, :(print(out, _limit(String(take!(io)), $precision)))) + buf[pos] = UInt8('.') + pos += 1 + for i = pt+1:n + buf[pos] = buf2[i] + pos += 1 end end - :(($x)::Any), blk -end - -# TODO: faster pointer printing. - -function gen_p(flags::String, width::Int, precision::Int, c::Char) - # print pointer: - # [p]: the only option - # - # flags: - # (-): left justify - # - @gensym x - blk = Expr(:block) - ptrwidth = Sys.WORD_SIZE>>2 - width -= ptrwidth+2 - if width > 0 && !('-' in flags) - push!(blk.args, pad(width, width, ' ')) - end - push!(blk.args, :(print(out, '0'))) - push!(blk.args, :(print(out, 'x'))) - push!(blk.args, :(print(out, String(string(unsigned($x), pad = $ptrwidth, base = 16))))) - if width > 0 && '-' in flags - push!(blk.args, pad(width, width, ' ')) - end - :(($x)::Ptr), blk -end - -function gen_g(flags::String, width::Int, precision::Int, c::Char) - # print to fixed trailing precision - # [g]: lower case e on scientific - # [G]: Upper case e on scientific - # - # flags - # (#): always print a decimal point - # (0): pad left with zeros - # (-): left justify - # ( ): precede non-negative values with " " - # (+): precede non-negative values with "+" - # - x, ex, blk = special_handler(flags,width) - if precision < 0; precision = 6; end - ndigits = min(precision+1,length(Grisu.getbuf())-1) - # See if anyone else wants to handle it - push!(blk.args, :((do_out, args) = ini_dec(out,$x,$ndigits, $flags, $width, $precision, $c, buf))) - ifblk = Expr(:if, :do_out, Expr(:block)) - push!(blk.args, ifblk) - blk = ifblk.args[2] - push!(blk.args, :((len, pt, neg) = args)) - push!(blk.args, :(exp = pt-1)) - push!(blk.args, :(do_f = $precision > exp >= -4)) # Should we interpret like %f or %e? - feblk = Expr(:if, :do_f, Expr(:block), Expr(:block)) - push!(blk.args, feblk) - fblk = feblk.args[2] - eblk = feblk.args[3] - - ### %f branch - # Follow the same logic as gen_f() but more work has to be deferred until runtime - # because precision is unknown until then. - push!(fblk.args, :(fprec = $precision - (exp+1))) - push!(fblk.args, :((do_out, args) = fix_dec(out, $x, $flags, $width, fprec, $c - 1, buf))) - fifblk = Expr(:if, :do_out, Expr(:block)) - push!(fblk.args, fifblk) - blk = fifblk.args[2] - push!(blk.args, :((len, pt, neg) = args)) - push!(blk.args, :(padding = 0)) - push!(blk.args, :(width = $width)) - # need to compute value before left-padding since trailing zeros are elided - push!(blk.args, :(width -= print_fixed_width(fprec,pt,len,$('#' in flags)))) - if '+' in flags || ' ' in flags - push!(blk.args, :(width -= 1)) - else - push!(blk.args, :(if neg width -= 1; end)) - end - push!(blk.args, :(if width >= 1 padding = width; end)) - # print space padding - if !('-' in flags) && !('0' in flags) - padexpr = dynamic_pad(:width, :padding, ' ') - push!(blk.args, :(if padding > 0 - $padexpr; end)) - end - # print sign - '+' in flags ? push!(blk.args, :(print(out, neg ? '-' : '+'))) : - ' ' in flags ? push!(blk.args, :(print(out, neg ? '-' : ' '))) : - push!(blk.args, :(neg && print(out, '-'))) - # print zero padding - if !('-' in flags) && '0' in flags - padexpr = dynamic_pad(:width, :padding, '0') - push!(blk.args, :(if padding > 0 - $padexpr; end)) - end - # finally print value - push!(blk.args, :(print_fixed(out,fprec,pt,len,$('#' in flags),buf))) - # print space padding - if '-' in flags - padexpr = dynamic_pad(:width, :padding, ' ') - push!(blk.args, :(if padding > 0 - $padexpr; end)) - end - - ### %e branch - # Here we can do all the work at macro expansion time - var, eex = gen_e(flags, width, precision-1, c, true) - push!(eblk.args, :($(var.args[1]) = $x)) - push!(eblk.args, eex) - - :(($x)::Real), ex -end -### core unsigned integer decoding functions ### - -macro handle_zero(ex, digits) - quote - if $(esc(ex)) == 0 - $(esc(digits))[1] = '0' - return Int32(1), Int32(1), $(esc(:neg)) + if leftalign && n2 < width + # pad right + for _ = 1:(width - n2) + buf[pos] = UInt8(' ') + pos += 1 end end + return pos end -decode_oct(out, d, flags::String, width::Int, precision::Int, c::Char, digits) = (true, decode_oct(d, digits)) -decode_0ct(out, d, flags::String, width::Int, precision::Int, c::Char, digits) = (true, decode_0ct(d, digits)) -decode_dec(out, d, flags::String, width::Int, precision::Int, c::Char, digits) = (true, decode_dec(d, digits)) -decode_hex(out, d, flags::String, width::Int, precision::Int, c::Char, digits) = (true, decode_hex(d, digits)) -decode_HEX(out, d, flags::String, width::Int, precision::Int, c::Char, digits) = (true, decode_HEX(d, digits)) -fix_dec(out, d, flags::String, width::Int, precision::Int, c::Char, digits) = (true, fix_dec(d, precision, digits)) -ini_dec(out, d, ndigits::Int, flags::String, width::Int, precision::Int, c::Char, digits) = (true, ini_dec(d, ndigits, digits)) -ini_hex(out, d, ndigits::Int, flags::String, width::Int, precision::Int, c::Char, digits) = (true, ini_hex(d, ndigits, digits)) -ini_HEX(out, d, ndigits::Int, flags::String, width::Int, precision::Int, c::Char, digits) = (true, ini_HEX(d, ndigits, digits)) -ini_hex(out, d, flags::String, width::Int, precision::Int, c::Char, digits) = (true, ini_hex(d, digits)) -ini_HEX(out, d, flags::String, width::Int, precision::Int, c::Char, digits) = (true, ini_HEX(d, digits)) - - -# fallbacks for Real types without explicit decode_* implementation -decode_oct(d::Real, digits) = decode_oct(Integer(d), digits) -decode_0ct(d::Real, digits) = decode_0ct(Integer(d), digits) -decode_dec(d::Real, digits) = decode_dec(Integer(d), digits) -decode_hex(d::Real, digits) = decode_hex(Integer(d), digits) -decode_HEX(d::Real, digits) = decode_HEX(Integer(d), digits) - -handlenegative(d::Unsigned) = (false, d) -function handlenegative(d::Integer) - if d < 0 - return true, unsigned(oftype(d,-d)) - else - return false, unsigned(d) - end -end - -function decode_oct(d::Integer, digits) - neg, x = handlenegative(d) - @handle_zero x digits - pt = i = div((sizeof(x)<<3)-leading_zeros(x)+2,3) - while i > 0 - digits[i] = 48+(x&0x7) - x >>= 3 - i -= 1 - end - return Int32(pt), Int32(pt), neg -end - -function decode_0ct(d::Integer, digits) - neg, x = handlenegative(d) - # doesn't need special handling for zero - pt = i = div((sizeof(x)<<3)-leading_zeros(x)+5,3) - while i > 0 - digits[i] = 48+(x&0x7) - x >>= 3 - i -= 1 - end - return Int32(pt), Int32(pt), neg -end - -function decode_dec(d::Integer, digits) - neg, x = handlenegative(d) - @handle_zero x digits - pt = i = Base.ndigits0z(x) - while i > 0 - digits[i] = 48+rem(x,10) - x = div(x,10) - i -= 1 - end - return Int32(pt), Int32(pt), neg -end - -function decode_hex(d::Integer, symbols::AbstractArray{UInt8,1}, digits) - neg, x = handlenegative(d) - @handle_zero x digits - pt = i = (sizeof(x)<<1)-(leading_zeros(x)>>2) - while i > 0 - digits[i] = symbols[(x&0xf)+1] - x >>= 4 - i -= 1 - end - return Int32(pt), Int32(pt), neg -end - -const hex_symbols = b"0123456789abcdef" -const HEX_symbols = b"0123456789ABCDEF" - -decode_hex(x::Integer, digits) = decode_hex(x,hex_symbols,digits) -decode_HEX(x::Integer, digits) = decode_hex(x,HEX_symbols,digits) - -function decode(b::Int, x::BigInt, digits) - neg = x.size < 0 - pt = Base.ndigits(x, base=abs(b)) - length(digits) < pt+1 && resize!(digits, pt+1) - neg && (x.size = -x.size) - GMP.MPZ.get_str!(digits, b, x) - neg && (x.size = -x.size) - return Int32(pt), Int32(pt), neg -end -decode_oct(x::BigInt, digits) = decode(8, x, digits) -decode_dec(x::BigInt, digits) = decode(10, x, digits) -decode_hex(x::BigInt, digits) = decode(16, x, digits) -decode_HEX(x::BigInt, digits) = decode(-16, x, digits) - -function decode_0ct(x::BigInt, digits) - neg = x.size < 0 - digits[1] = '0' - if x.size == 0 - return Int32(1), Int32(1), neg +const UNROLL_UPTO = 16 +# if you have your own buffer + pos, write formatted args directly to it +@inline function format(buf::Vector{UInt8}, pos::Integer, f::Format, args...) + # write out first substring + for i in f.substringranges[1] + buf[pos] = f.str[i] + pos += 1 end - pt = Base.ndigits0z(x, 8) + 1 - length(digits) < pt+1 && resize!(digits, pt+1) - neg && (x.size = -x.size) - GC.@preserve digits begin - p = pointer(digits,2) - GMP.MPZ.get_str!(p, 8, x) - end - neg && (x.size = -x.size) - return Int32(pt), Int32(pt), neg -end - -### decoding functions directly used by printf generated code ### - -# decode_*(x)=> fixed precision, to 0th place, filled out -# fix_*(x,n) => fixed precision, to nth place, not filled out -# ini_*(x,n) => n initial digits, filled out - -# alternate versions: -# *_0ct(x,n) => ensure that the first octal digits is zero -# *_HEX(x,n) => use uppercase digits for hexadecimal - -# - returns (len, point, neg) -# - implies len = point -# - -function decode_dec(x::SmallFloatingPoint, digits) - if x == 0.0 - digits[1] = '0' - return (Int32(1), Int32(1), false) + # for each format, write out arg and next substring + # unroll up to 16 formats + N = length(f.formats) + Base.@nexprs 16 i -> begin + if N >= i + pos = fmt(buf, pos, args[i], f.formats[i]) + for j in f.substringranges[i + 1] + buf[pos] = f.str[j] + pos += 1 + end + end end - len,pt,neg = grisu(x,Grisu.FIXED,0,digits) - if len == 0 - digits[1] = '0' - return (Int32(1), Int32(1), false) - else - for i = len+1:pt - digits[i] = '0' + if N > 16 + for i = 17:length(f.formats) + pos = fmt(buf, pos, args[i], f.formats[i]) + for j in f.substringranges[i + 1] + buf[pos] = f.str[j] + pos += 1 + end end end - return Int32(len), Int32(pt), neg + return pos end -# TODO: implement decode_oct, decode_0ct, decode_hex, decode_HEX for SmallFloatingPoint - -## fix decoding functions ## -# -# - returns (neg, point, len) -# - if len less than point, trailing zeros implied -# -# fallback for Real types without explicit fix_dec implementation -fix_dec(x::Real, n::Int, digits) = fix_dec(float(x),n,digits) +plength(f::Spec{T}, x) where {T <: Chars} = max(f.width, 1) + (ncodeunits(x isa AbstractString ? x[1] : Char(x)) - 1) +plength(f::Spec{Pointer}, x) = max(f.width, 2 * sizeof(x) + 2) -fix_dec(x::Integer, n::Int, digits) = decode_dec(x, digits) - -function fix_dec(x::SmallFloatingPoint, n::Int, digits) - if n > length(digits)-1; n = length(digits)-1; end - len,pt,neg = grisu(x,Grisu.FIXED,n,digits) - if len == 0 - digits[1] = '0' - return (Int32(1), Int32(1), neg) - end - return Int32(len), Int32(pt), neg +function plength(f::Spec{T}, x) where {T <: Strings} + str = string(x) + p = f.precision == -1 ? (length(str) + (f.hash ? (x isa Symbol ? 1 : 2) : 0)) : f.precision + return max(f.width, p) + (sizeof(str) - length(str)) end -## ini decoding functions ## -# -# - returns (neg, point, len) -# - implies len = n (requested digits) -# - -# fallback for Real types without explicit fix_dec implementation -ini_dec(x::Real, n::Int, digits) = ini_dec(float(x),n,digits) - -function ini_dec(d::Integer, n::Int, digits) - neg, x = handlenegative(d) - k = ndigits(x) - if k <= n - pt = k - for i = k:-1:1 - digits[i] = '0'+rem(x,10) - x = div(x,10) - end - for i = k+1:n - digits[i] = '0' - end - else - p = Base.powers_of_ten[k-n+1] - r = rem(x,p) - if r >= (p>>1) - x += p - if x >= Base.powers_of_ten[k+1] - p *= 10 - k += 1 - end - end - pt = k - x = div(x,p) - for i = n:-1:1 - digits[i] = '0'+rem(x,10) - x = div(x,10) - end - end - return n, pt, neg +function plength(f::Spec{T}, x) where {T <: Ints} + x2 = x isa AbstractFloat ? Integer(trunc(x)) : x + return max(f.width, f.precision + ndigits(x2, base=base(T), pad=1) + 5) end -function ini_dec(x::SmallFloatingPoint, n::Int, digits) - if x == 0.0 - ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), digits, '0', n) - return Int32(1), Int32(1), signbit(x) - else - len,pt,neg = grisu(x,Grisu.PRECISION,n,digits) - end - return Int32(len), Int32(pt), neg +function plength(f::Spec{T}, x) where {T <: Floats} + return max(f.width, f.precision + 309 + 17 + f.hash + 5) end -function ini_dec(x::BigInt, n::Int, digits) - if x.size == 0 - ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), digits, '0', n) - return Int32(1), Int32(1), false - end - d = Base.ndigits0z(x) - if d <= n - len,pt,neg = decode_dec(x, digits) - d == n && return (len,pt,neg) - - GC.@preserve digits begin - ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), pointer(digits, pt+1), '0', n - pt) +@inline function computelen(substringranges, formats, args) + len = sum(length, substringranges) + N = length(formats) + # unroll up to 16 formats + Base.@nexprs 16 i -> begin + if N >= i + len += plength(formats[i], args[i]) end - return (len,pt,neg) end - _, _, neg = decode_dec(round(BigInt,x/big(10)^(d-n)), digits) - return (n, d, neg) -end - - -ini_hex(x::Real, n::Int, digits) = ini_hex(x,n,hex_symbols,digits) -ini_HEX(x::Real, n::Int, digits) = ini_hex(x,n,HEX_symbols,digits) - -ini_hex(x::Real, digits) = ini_hex(x,hex_symbols,digits) -ini_HEX(x::Real, digits) = ini_hex(x,HEX_symbols,digits) - -ini_hex(x::Real, n::Int, symbols::AbstractArray{UInt8,1}, digits) = ini_hex(float(x), n, symbols, digits) -ini_hex(x::Real, symbols::AbstractArray{UInt8,1}, digits) = ini_hex(float(x), symbols, digits) - -function ini_hex(x::SmallFloatingPoint, n::Int, symbols::AbstractArray{UInt8,1}, digits) - x = Float64(x) - if x == 0.0 - ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), digits, '0', n) - return Int32(1), Int32(0), signbit(x) - else - s, p = frexp(x) - sigbits = 4*min(n-1,13) - s = 0.25*round(ldexp(s,1+sigbits)) - # ensure last 2 exponent bits either 01 or 10 - u = (reinterpret(UInt64,s) & 0x003f_ffff_ffff_ffff) >> (52-sigbits) - if n > 14 - ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), digits, '0', n) + if N > 16 + for i = 17:length(formats) + len += plength(formats[i], args[i]) end - i = (sizeof(u)<<1)-(leading_zeros(u)>>2) - while i > 0 - digits[i] = symbols[(u&0xf)+1] - u >>= 4 - i -= 1 - end - # pt is the binary exponent - return Int32(n), Int32(p-1), x < 0.0 end + return len end -function ini_hex(x::SmallFloatingPoint, symbols::AbstractArray{UInt8,1}, digits) - x = Float64(x) - if x == 0.0 - ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), digits, '0', 1) - return Int32(1), Int32(0), signbit(x) - else - s, p = frexp(x) - s *= 2.0 - u = (reinterpret(UInt64,s) & 0x001f_ffff_ffff_ffff) - t = (trailing_zeros(u) >> 2) - u >>= (t<<2) - n = 14-t - for i = n:-1:1 - digits[i] = symbols[(u&0xf)+1] - u >>= 4 - end - # pt is the binary exponent - return Int32(n), Int32(p-1), x < 0.0 - end -end +@noinline argmismatch(a, b) = + throw(ArgumentError("mismatch between # of format specifiers and provided args: $a != $b")) -function ini_hex(x::Integer, digits) - len,pt,neg = decode_hex(x, digits) - pt = (len-1)<<2 - len,pt,neg -end -function ini_HEX(x::Integer, digits) - len,pt,neg = decode_HEX(x, digits) - pt = (len-1)<<2 - len,pt,neg -end +""" + Printf.format(f::Printf.Format, args...) => String + Printf.format(io::IO, f::Printf.Format, args...) -# not implemented -ini_hex(x::Integer,ndigits::Int,digits) = throw(MethodError(ini_hex,(x,ndigits,digits))) +Apply a printf format object `f` to provided `args` and return the formatted string +(1st method), or print directly to an `io` object (2nd method). See [`@printf`](@ref) +for more details on C `printf` support. +""" +function format end -#BigFloat -fix_dec(out, d::BigFloat, flags::String, width::Int, precision::Int, c::Char, digits) = bigfloat_printf(out, d, flags, width, precision, c, digits) -ini_dec(out, d::BigFloat, ndigits::Int, flags::String, width::Int, precision::Int, c::Char, digits) = bigfloat_printf(out, d, flags, width, precision, c, digits) -ini_hex(out, d::BigFloat, ndigits::Int, flags::String, width::Int, precision::Int, c::Char, digits) = bigfloat_printf(out, d, flags, width, precision, c, digits) -ini_HEX(out, d::BigFloat, ndigits::Int, flags::String, width::Int, precision::Int, c::Char, digits) = bigfloat_printf(out, d, flags, width, precision, c, digits) -ini_hex(out, d::BigFloat, flags::String, width::Int, precision::Int, c::Char, digits) = bigfloat_printf(out, d, flags, width, precision, c, digits) -ini_HEX(out, d::BigFloat, flags::String, width::Int, precision::Int, c::Char, digits) = bigfloat_printf(out, d, flags, width, precision, c, digits) -function bigfloat_printf(out, d::BigFloat, flags::String, width::Int, precision::Int, c::Char, digits) - fmt_len = sizeof(flags)+4 - if width > 0 - fmt_len += ndigits(width) - end - if precision >= 0 - fmt_len += ndigits(precision)+1 - end - fmt = IOBuffer(maxsize=fmt_len) - print(fmt, '%') - print(fmt, flags) - if width > 0 - print(fmt, width) - end - if precision == 0 - print(fmt, '.') - print(fmt, '0') - elseif precision > 0 - print(fmt, '.') - print(fmt, precision) - end - print(fmt, 'R') - print(fmt, c) - write(fmt, UInt8(0)) - printf_fmt = take!(fmt) - @assert length(printf_fmt) == fmt_len - bufsiz = length(digits) - lng = ccall((:mpfr_snprintf,:libmpfr), Int32, - (Ptr{UInt8}, Culong, Ptr{UInt8}, Ref{BigFloat}...), - digits, bufsiz, printf_fmt, d) - lng > 0 || error("invalid printf formatting for BigFloat") - unsafe_write(out, pointer(digits), min(lng, bufsiz-1)) - return (false, ()) +function format(io::IO, f::Format, args...) # => Nothing + length(f.formats) == length(args) || argmismatch(length(f.formats), length(args)) + buf = Base.StringVector(computelen(f.substringranges, f.formats, args)) + pos = format(buf, 1, f, args...) + write(io, resize!(buf, pos - 1)) + return end -### external printf interface ### - -is_str_expr(ex) = - isa(ex,Expr) && (ex.head === :string || (ex.head === :macrocall && isa(ex.args[1],Symbol) && - endswith(string(ex.args[1]),"str"))) - -function _printf(macroname, io, fmt, args) - isa(fmt, AbstractString) || throw(ArgumentError("$macroname: format must be a plain static string (no interpolation or prefix)")) - sym_args, blk = gen(fmt) - - has_splatting = false - for arg in args - if isa(arg, Expr) && arg.head === :... - has_splatting = true - break - end - end - - # - # Immediately check for corresponding arguments if there is no splatting - # - if !has_splatting && length(sym_args) != length(args) - throw(ArgumentError("$macroname: wrong number of arguments ($(length(args))) should be ($(length(sym_args)))")) - end - - for i = length(sym_args):-1:1 - var = sym_args[i].args[1] - if has_splatting - pushfirst!(blk.args, :($var = G[$i])) - else - pushfirst!(blk.args, :($var = $(esc(args[i])))) - end - end - - # - # Delay generation of argument list and check until evaluation time instead of macro - # expansion time if there is splatting. - # - if has_splatting - x = Expr(:call,:tuple,args...) - pushfirst!(blk.args, - quote - G = $(esc(x)) - if length(G) != $(length(sym_args)) - throw(ArgumentError(string($macroname,": wrong number of arguments (",length(G),") should be (",$(length(sym_args)),")"))) - end - end - ) - end - - pushfirst!(blk.args, :(out = $io)) - Expr(:let, Expr(:block), blk) +function format(f::Format, args...) # => String + length(f.formats) == length(args) || argmismatch(length(f.formats), length(args)) + buf = Base.StringVector(computelen(f.substringranges, f.formats, args)) + pos = format(buf, 1, f, args...) + return String(resize!(buf, pos - 1)) end """ - @printf([io::IOStream], "%Fmt", args...) + @printf([io::IO], "%Fmt", args...) Print `args` using C `printf` style format specification string, with some caveats: `Inf` and `NaN` are printed consistently as `Inf` and `NaN` for flags `%a`, `%A`, `%e`, `%E`, `%f`, `%F`, `%g`, and `%G`. Furthermore, if a floating point number is equally close to the numeric values of two possible output strings, the output string further away from zero is chosen. - -Optionally, an [`IOStream`](@ref) +Optionally, an `IO` may be passed as the first argument to redirect output. - See also: [`@sprintf`](@ref) - # Examples ```jldoctest julia> @printf("%f %F %f %F\\n", Inf, Inf, NaN, NaN) Inf Inf NaN NaN\n - julia> @printf "%.0f %.1f %f\\n" 0.5 0.025 -0.0078125 -1 0.0 -0.007813 +0 0.0 -0.007812 ``` """ -macro printf(args...) - isempty(args) && throw(ArgumentError("@printf: called with no arguments")) - if isa(args[1], AbstractString) || is_str_expr(args[1]) - _printf("@printf", :stdout, args[1], args[2:end]) +macro printf(io_or_fmt, args...) + if io_or_fmt isa String + io = stdout + fmt = Format(io_or_fmt) + return esc(:($Printf.format($io, $fmt, $(args...)))) else - (length(args) >= 2 && (isa(args[2], AbstractString) || is_str_expr(args[2]))) || - throw(ArgumentError("@printf: first or second argument must be a format string")) - _printf("@printf", esc(args[1]), args[2], args[3:end]) + io = io_or_fmt + isempty(args) && throw(ArgumentError("must provide required format string")) + fmt = Format(args[1]) + return esc(:($Printf.format($io, $fmt, $(Base.tail(args)...)))) end end @@ -1277,22 +766,15 @@ end @sprintf("%Fmt", args...) Return `@printf` formatted output as string. - # Examples ```jldoctest -julia> s = @sprintf "this is a %s %15.1f" "test" 34.567; - -julia> println(s) -this is a test 34.6 +julia> @sprintf "this is a %s %15.1f" "test" 34.567 +"this is a test 34.6" ``` """ -macro sprintf(args...) - isempty(args) && throw(ArgumentError("@sprintf: called with zero arguments")) - isa(args[1], AbstractString) || is_str_expr(args[1]) || - throw(ArgumentError("@sprintf: first argument must be a format string")) - letexpr = _printf("@sprintf", :(IOBuffer()), args[1], args[2:end]) - push!(letexpr.args[2].args, :(String(take!(out)))) - letexpr +macro sprintf(fmt, args...) + f = Format(fmt) + return esc(:($Printf.format($f, $(args...)))) end -end +end # module diff --git a/stdlib/Printf/test/runtests.jl b/stdlib/Printf/test/runtests.jl index 562d478ef4676..4ff60c8aab085 100644 --- a/stdlib/Printf/test/runtests.jl +++ b/stdlib/Printf/test/runtests.jl @@ -2,80 +2,274 @@ using Test, Printf -# this macro tests for exceptions thrown at macro expansion -macro test_me(ty, ex) - return quote - @test_throws $(esc(ty)) try - $(esc(ex)) - catch err - @test err isa LoadError - @test err.file === $(string(__source__.file)) - @test err.line === $(__source__.line) - rethrow(err.error) - end +@testset "Printf" begin + +@testset "%p" begin + + # pointers + if Sys.WORD_SIZE == 64 + @test (Printf.@sprintf "%20p" 0) == " 0x0000000000000000" + @test (Printf.@sprintf "%-20p" 0) == "0x0000000000000000 " + @test (Printf.@sprintf "%20p" C_NULL) == " 0x0000000000000000" + @test (@sprintf "%-20p" C_NULL) == "0x0000000000000000 " + elseif Sys.WORD_SIZE == 32 + @test (Printf.@sprintf "%20p" 0) == " 0x00000000" + @test (Printf.@sprintf "%-20p" 0) == "0x00000000 " + @test (@sprintf "%20p" C_NULL) == " 0x00000000" + @test (@sprintf "%-20p" C_NULL) == "0x00000000 " end + end -# printf -# int -@test (@sprintf "%d" typemax(Int64)) == "9223372036854775807" -@test (@sprintf "%a" typemax(Int64)) == "0x7.fffffffffffffffp+60" -@test (@sprintf "%A" typemax(Int64)) == "0X7.FFFFFFFFFFFFFFFP+60" +@testset "%a" begin + + # hex float + @test (Printf.@sprintf "%a" 1.5) == "0x1.8p+0" + @test (Printf.@sprintf "%a" 1.5f0) == "0x1.8p+0" + @test (Printf.@sprintf "%a" big"1.5") == "0x1.8p+0" + @test (Printf.@sprintf "%#.0a" 1.5) == "0x2.p+0" + @test (Printf.@sprintf "%+30a" 1/3) == " +0x1.5555555555555p-2" + + @test Printf.@sprintf("%a", 1.5) == "0x1.8p+0" + @test Printf.@sprintf("%a", 3.14) == "0x1.91eb851eb851fp+1" + @test Printf.@sprintf("%.0a", 3.14) == "0x2p+1" + @test Printf.@sprintf("%.1a", 3.14) == "0x1.9p+1" + @test Printf.@sprintf("%.2a", 3.14) == "0x1.92p+1" + @test Printf.@sprintf("%#a", 3.14) == "0x1.91eb851eb851fp+1" + @test Printf.@sprintf("%#.0a", 3.14) == "0x2.p+1" + @test Printf.@sprintf("%#.1a", 3.14) == "0x1.9p+1" + @test Printf.@sprintf("%#.2a", 3.14) == "0x1.92p+1" + @test Printf.@sprintf("%.6a", 1.5) == "0x1.800000p+0" -#printing an int value -for (fmt, val) in (("%i", "42"), - ("%u", "42"), - ("Test: %i", "Test: 42"), - ("%#x", "0x2a"), - ("%#o", "052"), - ("%x", "2a"), - ("%X", "2A"), - ("% i", " 42"), - ("%+i", "+42"), - ("%4i", " 42"), - ("%-4i", "42 "), - ("%a", "0x2.ap+4"), - ("%A", "0X2.AP+4"), - ("%20a"," 0x2.ap+4"), - ("%-20a","0x2.ap+4 "), - ("%f", "42.000000"), - ("%g", "42"), - ("%e", "4.200000e+01")), - num in (UInt16(42), UInt32(42), UInt64(42), UInt128(42), - Int16(42), Int32(42), Int64(42), Int128(42), big"42") - @test @eval(@sprintf($fmt, $num) == $val) end -# pointers -if Sys.WORD_SIZE == 64 - @test (@sprintf "%20p" 0) == " 0x0000000000000000" - @test (@sprintf "%-20p" 0) == "0x0000000000000000 " - @test (@sprintf "%20p" C_NULL) == " 0x0000000000000000" - @test (@sprintf "%-20p" C_NULL) == "0x0000000000000000 " -elseif Sys.WORD_SIZE == 32 - @test (@sprintf "%20p" 0) == " 0x00000000" - @test (@sprintf "%-20p" 0) == "0x00000000 " - @test (@sprintf "%20p" C_NULL) == " 0x00000000" - @test (@sprintf "%-20p" C_NULL) == "0x00000000 " -else - @test false +@testset "%g" begin + + # %g + for (val, res) in ((12345678., "1.23457e+07"), + (1234567.8, "1.23457e+06"), + (123456.78, "123457"), + (12345.678, "12345.7"), + (12340000.0, "1.234e+07")) + @test (Printf.@sprintf("%.6g", val) == res) + end + for (val, res) in ((big"12345678.", "1.23457e+07"), + (big"1234567.8", "1.23457e+06"), + (big"123456.78", "123457"), + (big"12345.678", "12345.7")) + @test (Printf.@sprintf("%.6g", val) == res) + end + for (fmt, val) in (("%10.5g", " 123.4"), + ("%+10.5g", " +123.4"), + ("% 10.5g"," 123.4"), + ("%#10.5g", " 123.40"), + ("%-10.5g", "123.4 "), + ("%-+10.5g", "+123.4 "), + ("%010.5g", "00000123.4")), + num in (123.4, big"123.4") + @test Printf.format(Printf.Format(fmt), num) == val + end + @test( Printf.@sprintf( "%10.5g", -123.4 ) == " -123.4") + @test( Printf.@sprintf( "%010.5g", -123.4 ) == "-0000123.4") + @test( Printf.@sprintf( "%.6g", 12340000.0 ) == "1.234e+07") + @test( Printf.@sprintf( "%#.6g", 12340000.0 ) == "1.23400e+07") + @test( Printf.@sprintf( "%10.5g", big"-123.4" ) == " -123.4") + @test( Printf.@sprintf( "%010.5g", big"-123.4" ) == "-0000123.4") + @test( Printf.@sprintf( "%.6g", big"12340000.0" ) == "1.234e+07") + @test( Printf.@sprintf( "%#.6g", big"12340000.0") == "1.23400e+07") + + # %g regression gh #14331 + @test( Printf.@sprintf( "%.5g", 42) == "42") + @test( Printf.@sprintf( "%#.2g", 42) == "42.") + @test( Printf.@sprintf( "%#.5g", 42) == "42.000") + + @test Printf.@sprintf("%g", 0.00012) == "0.00012" + @test Printf.@sprintf("%g", 0.000012) == "1.2e-05" + @test Printf.@sprintf("%g", 123456.7) == "123457" + @test Printf.@sprintf("%g", 1234567.8) == "1.23457e+06" + end -# float / BigFloat -for (fmt, val) in (("%7.2f", " 1.23"), - ("%-7.2f", "1.23 "), - ("%07.2f", "0001.23"), - ("%.0f", "1"), - ("%#.0f", "1."), - ("%.4e", "1.2345e+00"), - ("%.4E", "1.2345E+00"), - ("%.2a", "0x1.3cp+0"), - ("%.2A", "0X1.3CP+0")), - num in (1.2345, big"1.2345") - @test @eval(@sprintf($fmt, $num) == $val) +@testset "%f" begin + + # Inf / NaN handling + @test (Printf.@sprintf "%f" Inf) == "Inf" + @test (Printf.@sprintf "%+f" Inf) == "+Inf" + @test (Printf.@sprintf "% f" Inf) == " Inf" + @test (Printf.@sprintf "% #f" Inf) == " Inf" + @test (Printf.@sprintf "%f" -Inf) == "-Inf" + @test (Printf.@sprintf "%+f" -Inf) == "-Inf" + @test (Printf.@sprintf "%f" NaN) == "NaN" + @test (Printf.@sprintf "%+f" NaN) == "NaN" + @test (Printf.@sprintf "% f" NaN) == "NaN" + @test (Printf.@sprintf "% #f" NaN) == "NaN" + @test (Printf.@sprintf "%e" big"Inf") == "Inf" + @test (Printf.@sprintf "%e" big"NaN") == "NaN" + + @test (Printf.@sprintf "%.0f" 3e142) == "29999999999999997463140672961703247153805615792184250659629251954072073858354858644285983761764971823910371920726635399393477049701891710124032" + + @test Printf.@sprintf("%f", 1.234) == "1.234000" + @test Printf.@sprintf("%F", 1.234) == "1.234000" + @test Printf.@sprintf("%+f", 1.234) == "+1.234000" + @test Printf.@sprintf("% f", 1.234) == " 1.234000" + @test Printf.@sprintf("%f", -1.234) == "-1.234000" + @test Printf.@sprintf("%+f", -1.234) == "-1.234000" + @test Printf.@sprintf("% f", -1.234) == "-1.234000" + @test Printf.@sprintf("%#f", 1.234) == "1.234000" + @test Printf.@sprintf("%.2f", 1.234) == "1.23" + @test Printf.@sprintf("%.2f", 1.235) == "1.24" + @test Printf.@sprintf("%.2f", 0.235) == "0.23" + @test Printf.@sprintf("%4.1f", 1.234) == " 1.2" + @test Printf.@sprintf("%8.1f", 1.234) == " 1.2" + @test Printf.@sprintf("%+8.1f", 1.234) == " +1.2" + @test Printf.@sprintf("% 8.1f", 1.234) == " 1.2" + @test Printf.@sprintf("% 7.1f", 1.234) == " 1.2" + @test Printf.@sprintf("% 08.1f", 1.234) == " 00001.2" + @test Printf.@sprintf("%08.1f", 1.234) == "000001.2" + @test Printf.@sprintf("%-08.1f", 1.234) == "1.2 " + @test Printf.@sprintf("%-8.1f", 1.234) == "1.2 " + @test Printf.@sprintf("%08.1f", -1.234) == "-00001.2" + @test Printf.@sprintf("%09.1f", -1.234) == "-000001.2" + @test Printf.@sprintf("%09.1f", 1.234) == "0000001.2" + @test Printf.@sprintf("%+09.1f", 1.234) == "+000001.2" + @test Printf.@sprintf("% 09.1f", 1.234) == " 000001.2" + @test Printf.@sprintf("%+ 09.1f", 1.234) == "+000001.2" + @test Printf.@sprintf("%+ 09.1f", 1.234) == "+000001.2" + @test Printf.@sprintf("%+ 09.0f", 1.234) == "+00000001" + @test Printf.@sprintf("%+ #09.0f", 1.234) == "+0000001." +end + +@testset "%e" begin + + # Inf / NaN handling + @test (Printf.@sprintf "%e" Inf) == "Inf" + @test (Printf.@sprintf "%+e" Inf) == "+Inf" + @test (Printf.@sprintf "% e" Inf) == " Inf" + @test (Printf.@sprintf "% #e" Inf) == " Inf" + @test (Printf.@sprintf "%e" -Inf) == "-Inf" + @test (Printf.@sprintf "%+e" -Inf) == "-Inf" + @test (Printf.@sprintf "%e" NaN) == "NaN" + @test (Printf.@sprintf "%+e" NaN) == "NaN" + @test (Printf.@sprintf "% e" NaN) == "NaN" + @test (Printf.@sprintf "% #e" NaN) == "NaN" + @test (Printf.@sprintf "%e" big"Inf") == "Inf" + @test (Printf.@sprintf "%e" big"NaN") == "NaN" + + # scientific notation + @test (Printf.@sprintf "%.0e" 3e142) == "3e+142" + @test (Printf.@sprintf "%#.0e" 3e142) == "3.e+142" + @test (Printf.@sprintf "%.0e" big"3e142") == "3e+142" + @test (Printf.@sprintf "%#.0e" big"3e142") == "3.e+142" + + @test (Printf.@sprintf "%.0e" big"3e1042") == "3e+1042" + + @test (Printf.@sprintf "%e" 3e42) == "3.000000e+42" + @test (Printf.@sprintf "%E" 3e42) == "3.000000E+42" + @test (Printf.@sprintf "%e" 3e-42) == "3.000000e-42" + @test (Printf.@sprintf "%E" 3e-42) == "3.000000E-42" + + @test Printf.@sprintf("%e", 1.234) == "1.234000e+00" + @test Printf.@sprintf("%E", 1.234) == "1.234000E+00" + @test Printf.@sprintf("%+e", 1.234) == "+1.234000e+00" + @test Printf.@sprintf("% e", 1.234) == " 1.234000e+00" + @test Printf.@sprintf("%e", -1.234) == "-1.234000e+00" + @test Printf.@sprintf("%+e", -1.234) == "-1.234000e+00" + @test Printf.@sprintf("% e", -1.234) == "-1.234000e+00" + @test Printf.@sprintf("%#e", 1.234) == "1.234000e+00" + @test Printf.@sprintf("%.2e", 1.234) == "1.23e+00" + @test Printf.@sprintf("%.2e", 1.235) == "1.24e+00" + @test Printf.@sprintf("%.2e", 0.235) == "2.35e-01" + @test Printf.@sprintf("%4.1e", 1.234) == "1.2e+00" + @test Printf.@sprintf("%8.1e", 1.234) == " 1.2e+00" + @test Printf.@sprintf("%+8.1e", 1.234) == "+1.2e+00" + @test Printf.@sprintf("% 8.1e", 1.234) == " 1.2e+00" + @test Printf.@sprintf("% 7.1e", 1.234) == " 1.2e+00" + @test Printf.@sprintf("% 08.1e", 1.234) == " 1.2e+00" + @test Printf.@sprintf("%08.1e", 1.234) == "01.2e+00" + @test Printf.@sprintf("%-08.1e", 1.234) == "1.2e+00 " + @test Printf.@sprintf("%-8.1e", 1.234) == "1.2e+00 " + @test Printf.@sprintf("%-8.1e", 1.234) == "1.2e+00 " + @test Printf.@sprintf("%08.1e", -1.234) == "-1.2e+00" + @test Printf.@sprintf("%09.1e", -1.234) == "-01.2e+00" + @test Printf.@sprintf("%09.1e", 1.234) == "001.2e+00" + @test Printf.@sprintf("%+09.1e", 1.234) == "+01.2e+00" + @test Printf.@sprintf("% 09.1e", 1.234) == " 01.2e+00" + @test Printf.@sprintf("%+ 09.1e", 1.234) == "+01.2e+00" + @test Printf.@sprintf("%+ 09.1e", 1.234) == "+01.2e+00" + @test Printf.@sprintf("%+ 09.0e", 1.234) == "+0001e+00" + @test Printf.@sprintf("%+ #09.0e", 1.234) == "+001.e+00" +end + +@testset "strings" begin + + @test Printf.@sprintf("Hallo heimur") == "Hallo heimur" + @test Printf.@sprintf("+%s+", "hello") == "+hello+" + @test Printf.@sprintf("%.1s", "foo") == "f" + @test Printf.@sprintf("%s", "%%%%") == "%%%%" + @test Printf.@sprintf("%s", "Hallo heimur") == "Hallo heimur" + @test Printf.@sprintf("%+s", "Hallo heimur") == "Hallo heimur" + @test Printf.@sprintf("% s", "Hallo heimur") == "Hallo heimur" + @test Printf.@sprintf("%+ s", "Hallo heimur") == "Hallo heimur" + @test Printf.@sprintf("%1s", "Hallo heimur") == "Hallo heimur" + @test Printf.@sprintf("%20s", "Hallo") == " Hallo" + @test Printf.@sprintf("%-20s", "Hallo") == "Hallo " + @test Printf.@sprintf("%0-20s", "Hallo") == "Hallo " + @test Printf.@sprintf("%.20s", "Hallo heimur") == "Hallo heimur" + @test Printf.@sprintf("%20.5s", "Hallo heimur") == " Hallo" + @test Printf.@sprintf("%.0s", "Hallo heimur") == "" + @test Printf.@sprintf("%20.0s", "Hallo heimur") == " " + @test Printf.@sprintf("%.s", "Hallo heimur") == "" + @test Printf.@sprintf("%20.s", "Hallo heimur") == " " + @test (Printf.@sprintf "%s" "test") == "test" + @test (Printf.@sprintf "%s" "tést") == "tést" + @test Printf.@sprintf("ø%sø", "hey") == "øheyø" + @test Printf.@sprintf("%4sø", "ø") == " øø" + @test Printf.@sprintf("%-4sø", "ø") == "ø ø" + + @test (Printf.@sprintf "%8s" "test") == " test" + @test (Printf.@sprintf "%-8s" "test") == "test " + + @test (Printf.@sprintf "%s" :test) == "test" + @test (Printf.@sprintf "%#s" :test) == ":test" + @test (Printf.@sprintf "%#8s" :test) == " :test" + @test (Printf.@sprintf "%#-8s" :test) == ":test " + + @test (Printf.@sprintf "%8.3s" "test") == " tes" + @test (Printf.@sprintf "%#8.3s" "test") == " \"te" + @test (Printf.@sprintf "%-8.3s" "test") == "tes " + @test (Printf.@sprintf "%#-8.3s" "test") == "\"te " + @test (Printf.@sprintf "%.3s" "test") == "tes" + @test (Printf.@sprintf "%#.3s" "test") == "\"te" + @test (Printf.@sprintf "%-.3s" "test") == "tes" + @test (Printf.@sprintf "%#-.3s" "test") == "\"te" + +end + +@testset "chars" begin + + @test Printf.@sprintf("%c", 'a') == "a" + @test Printf.@sprintf("%c", 32) == " " + @test Printf.@sprintf("%c", 36) == "\$" + @test Printf.@sprintf("%3c", 'a') == " a" + @test Printf.@sprintf( "%c", 'x') == "x" + @test Printf.@sprintf("%+c", 'x') == "x" + @test Printf.@sprintf("% c", 'x') == "x" + @test Printf.@sprintf("%+ c", 'x') == "x" + @test Printf.@sprintf("%1c", 'x') == "x" + @test Printf.@sprintf("%20c" , 'x') == " x" + @test Printf.@sprintf("%-20c" , 'x') == "x " + @test Printf.@sprintf("%-020c", 'x') == "x " + @test Printf.@sprintf("%c", 65) == "A" + @test Printf.@sprintf("%c", 'A') == "A" + @test Printf.@sprintf("%3c", 'A') == " A" + @test Printf.@sprintf("%-3c", 'A') == "A " + @test Printf.@sprintf("%c", 248) == "ø" + @test Printf.@sprintf("%c", 'ø') == "ø" + @test Printf.@sprintf("%c", "ø") == "ø" + @test Printf.@sprintf("%c", '𐀀') == "𐀀" + end -# numeric spacing and various flag tests function _test_flags(val, vflag::AbstractString, fmt::AbstractString, res::AbstractString, prefix::AbstractString) vflag = string("%", vflag) space_fmt = string(length(res) + length(prefix) + 3, fmt) @@ -97,206 +291,403 @@ function _test_flags(val, vflag::AbstractString, fmt::AbstractString, res::Abstr ("- ", string(nsign, res, " ")), ) fmt_string = string(vflag, flag, space_fmt) - @test @eval(@sprintf($fmt_string, $val) == $ans) + fmtd = Printf.format(Printf.Format(fmt_string), val) + @test fmtd == ans end end -for i in ( - (42, "", "i", "42", ""), - (42, "", "d", "42", ""), +@testset "basics" begin + + @test Printf.@sprintf("%%") == "%" + @test Printf.@sprintf("hey there") == "hey there" + @test_throws ArgumentError Printf.Format("") + @test_throws ArgumentError Printf.Format("%+") + @test_throws ArgumentError Printf.Format("%.") + @test_throws ArgumentError Printf.Format("%.0") + @test isempty(Printf.Format("%%").formats) + @test Printf.@sprintf("%d%d", 1, 2) == "12" + @test (Printf.@sprintf "%d%d" [1 2]...) == "12" + @test (Printf.@sprintf("X%d", 2)) == "X2" + @test (Printf.@sprintf("\u00d0%d", 2)) == "\u00d02" + @test (Printf.@sprintf("\u0f00%d", 2)) == "\u0f002" + @test (Printf.@sprintf("\U0001ffff%d", 2)) == "\U0001ffff2" + @test (Printf.@sprintf("%dX%d", 1, 2)) == "1X2" + @test (Printf.@sprintf("%d\u00d0%d", 1, 2)) == "1\u00d02" + @test (Printf.@sprintf("%d\u0f00%d", 1, 2)) == "1\u0f002" + @test (Printf.@sprintf("%d\U0001ffff%d", 1, 2)) == "1\U0001ffff2" + @test (Printf.@sprintf("%d\u2203%d\u0203", 1, 2)) == "1\u22032\u0203" + @test_throws ArgumentError Printf.Format("%y%d") + @test_throws ArgumentError Printf.Format("%\u00d0%d") + @test_throws ArgumentError Printf.Format("%\u0f00%d") + @test_throws ArgumentError Printf.Format("%\U0001ffff%d") + @test Printf.@sprintf("%10.5d", 4) == " 00004" + @test (Printf.@sprintf "%d" typemax(Int64)) == "9223372036854775807" + + for (fmt, val) in (("%7.2f", " 1.23"), + ("%-7.2f", "1.23 "), + ("%07.2f", "0001.23"), + ("%.0f", "1"), + ("%#.0f", "1."), + ("%.4e", "1.2345e+00"), + ("%.4E", "1.2345E+00"), + ("%.2a", "0x1.3cp+0"), + ("%.2A", "0X1.3CP+0")), + num in (1.2345, big"1.2345") + @test Printf.format(Printf.Format(fmt), num) == val + end + + for (fmt, val) in (("%i", "42"), + ("%u", "42"), + ("Test: %i", "Test: 42"), + ("%#x", "0x2a"), + ("%x", "2a"), + ("%X", "2A"), + ("% i", " 42"), + ("%+i", "+42"), + ("%4i", " 42"), + ("%-4i", "42 "), + ("%f", "42.000000"), + ("%g", "42"), + ("%e", "4.200000e+01")), + num in (UInt16(42), UInt32(42), UInt64(42), UInt128(42), + Int16(42), Int32(42), Int64(42), Int128(42), big"42") + @test Printf.format(Printf.Format(fmt), num) == val + end - (42, "", "u", "42", ""), - (42, "", "x", "2a", ""), - (42, "", "X", "2A", ""), - (42, "", "o", "52", ""), + for i in ( + (42, "", "i", "42", ""), + (42, "", "d", "42", ""), - (42, "#", "x", "2a", "0x"), - (42, "#", "X", "2A", "0X"), - (42, "#", "o", "052", ""), + (42, "", "u", "42", ""), + (42, "", "x", "2a", ""), + (42, "", "X", "2A", ""), + (42, "", "o", "52", ""), - (1.2345, "", ".2f", "1.23", ""), - (1.2345, "", ".2e", "1.23e+00", ""), - (1.2345, "", ".2E", "1.23E+00", ""), + (42, "#", "x", "2a", "0x"), + (42, "#", "X", "2A", "0X"), + (42, "#", "o", "052", ""), - (1.2345, "#", ".0f", "1.", ""), - (1.2345, "#", ".0e", "1.e+00", ""), - (1.2345, "#", ".0E", "1.E+00", ""), + (1.2345, "", ".2f", "1.23", ""), + (1.2345, "", ".2e", "1.23e+00", ""), + (1.2345, "", ".2E", "1.23E+00", ""), - (1.2345, "", ".2a", "1.3cp+0", "0x"), - (1.2345, "", ".2A", "1.3CP+0", "0X"), - ) - _test_flags(i...) - _test_flags(-i[1], i[2:5]...) -end + (1.2345, "#", ".0f", "1.", ""), + (1.2345, "#", ".0e", "1.e+00", ""), + (1.2345, "#", ".0E", "1.E+00", ""), -# Inf / NaN handling -@test (@sprintf "%f" Inf) == "Inf" -@test (@sprintf "%f" NaN) == "NaN" -@test (@sprintf "%f" big"Inf") == "Inf" -@test (@sprintf "%f" big"NaN") == "NaN" - -# scientific notation -@test (@sprintf "%.0e" 3e142) == "3e+142" -@test (@sprintf "%#.0e" 3e142) == "3.e+142" -@test (@sprintf "%.0e" big"3e142") == "3e+142" -@test (@sprintf "%#.0e" big"3e142") == "3.e+142" - -@test (@sprintf "%.0e" big"3e1042") == "3e+1042" - -@test (@sprintf "%e" 3e42) == "3.000000e+42" -@test (@sprintf "%E" 3e42) == "3.000000E+42" -@test (@sprintf "%e" 3e-42) == "3.000000e-42" -@test (@sprintf "%E" 3e-42) == "3.000000E-42" -@test (@sprintf "%a" 3e4) == "0x1.d4cp+14" -@test (@sprintf "%A" 3e4) == "0X1.D4CP+14" -@test (@sprintf "%.4a" 3e-4) == "0x1.3a93p-12" -@test (@sprintf "%.4A" 3e-4) == "0X1.3A93P-12" - -# %g -for (val, res) in ((12345678., "1.23457e+07"), - (1234567.8, "1.23457e+06"), - (123456.78, "123457"), - (12345.678, "12345.7"), - (12340000.0, "1.234e+07")) - @test (@sprintf("%.6g", val) == res) -end -for (val, res) in ((big"12345678.", "1.23457e+07"), - (big"1234567.8", "1.23457e+06"), - (big"123456.78", "123457"), - (big"12345.678", "12345.7")) - @test (@sprintf("%.6g", val) == res) -end -for (fmt, val) in (("%10.5g", " 123.4"), - ("%+10.5g", " +123.4"), - ("% 10.5g"," 123.4"), - ("%#10.5g", " 123.40"), - ("%-10.5g", "123.4 "), - ("%-+10.5g", "+123.4 "), - ("%010.5g", "00000123.4")), - num in (123.4, big"123.4") - @test @eval(@sprintf($fmt, $num) == $val) + (1.2345, "", ".2a", "1.3cp+0", "0x"), + (1.2345, "", ".2A", "1.3CP+0", "0X"), + ) + _test_flags(i...) + _test_flags(-i[1], i[2:5]...) + end + + # reasonably complex + @test (Printf.@sprintf "Test: %s%c%C%c%#-.0f." "t" 65 66 67 -42) == "Test: tABC-42.." + + # combo + @test (Printf.@sprintf "%f %d %d %f" 1.0 [3 4]... 5) == "1.000000 3 4 5.000000" + + # multi + @test (Printf.@sprintf "%s %f %9.5f %d %d %d %d%d%d%d" [1:6;]... [7,8,9,10]...) == "1 2.000000 3.00000 4 5 6 78910" + + # comprehension + @test (Printf.@sprintf "%s %s %s %d %d %d %f %f %f" Any[10^x+y for x=1:3,y=1:3 ]...) == "11 101 1001 12 102 1002 13.000000 103.000000 1003.000000" + + # more than 16 formats/args + @test (Printf.@sprintf "%s %s %s %d %d %d %f %f %f %s %s %s %d %d %d %f %f %f" Any[10*x+(x+1) for x=1:18 ]...) == + "12 23 34 45 56 67 78.000000 89.000000 100.000000 111 122 133 144 155 166 177.000000 188.000000 199.000000" + + # Check bug with trailing nul printing BigFloat + @test (Printf.@sprintf("%.330f", BigFloat(1)))[end] != '\0' + + # issue #29662 + @test (Printf.@sprintf "%12.3e" pi*1e100) == " 3.142e+100" + + @test string(Printf.Format("%a").formats[1]) == "%a" + @test string(Printf.Format("%a").formats[1]; modifier="R") == "%Ra" + + @test Printf.@sprintf("%d", 3.14) == "3" + @test Printf.@sprintf("%2d", 3.14) == " 3" + @test Printf.@sprintf("%2d", big(3.14)) == " 3" + @test Printf.@sprintf("%s", 1) == "1" + @test Printf.@sprintf("%f", 1) == "1.000000" + @test Printf.@sprintf("%e", 1) == "1.000000e+00" + @test Printf.@sprintf("%g", 1) == "1" end -@test( @sprintf( "%10.5g", -123.4 ) == " -123.4") -@test( @sprintf( "%010.5g", -123.4 ) == "-0000123.4") -@test( @sprintf( "%.6g", 12340000.0 ) == "1.234e+07") -@test( @sprintf( "%#.6g", 12340000.0 ) == "1.23400e+07") -@test( @sprintf( "%10.5g", big"-123.4" ) == " -123.4") -@test( @sprintf( "%010.5g", big"-123.4" ) == "-0000123.4") -@test( @sprintf( "%.6g", big"12340000.0" ) == "1.234e+07") -@test( @sprintf( "%#.6g", big"12340000.0") == "1.23400e+07") - -# %g regression gh #14331 -@test( @sprintf( "%.5g", 42) == "42") -@test( @sprintf( "%#.2g", 42) == "42.") -@test( @sprintf( "%#.5g", 42) == "42.000") - -# hex float -@test (@sprintf "%a" 1.5) == "0x1.8p+0" -@test (@sprintf "%a" 1.5f0) == "0x1.8p+0" -@test (@sprintf "%a" big"1.5") == "0x1.8p+0" -@test (@sprintf "%#.0a" 1.5) == "0x2.p+0" -@test (@sprintf "%+30a" 1/3) == " +0x1.5555555555555p-2" - -# chars -@test (@sprintf "%c" 65) == "A" -@test (@sprintf "%c" 'A') == "A" -@test (@sprintf "%3c" 'A') == " A" -@test (@sprintf "%-3c" 'A') == "A " -@test (@sprintf "%c" 248) == "ø" -@test (@sprintf "%c" 'ø') == "ø" - -# escape % -@test (@sprintf "%%") == "%" -@test (@sprintf "%%s") == "%s" -@test_me ArgumentError("invalid printf format string: \"%\"") @macroexpand(@sprintf "%") #" (fixes syntax highlighting) - -# argument count -@test_me ArgumentError("@sprintf: wrong number of arguments (0) should be (1)") @macroexpand(@sprintf "%s") -@test_me ArgumentError("@sprintf: wrong number of arguments (2) should be (1)") @macroexpand(@sprintf "%s" "1" "2") - -# no interpolation -@test_me ArgumentError("@sprintf: format must be a plain static string (no interpolation or prefix)") @macroexpand(@sprintf "$n") - -# type width specifier parsing (ignored) -@test (@sprintf "%llf" 1.2) == "1.200000" -@test (@sprintf "%Lf" 1.2) == "1.200000" -@test (@sprintf "%hhu" 1) == "1" -@test (@sprintf "%hu" 1) == "1" -@test (@sprintf "%lu" 1) == "1" -@test (@sprintf "%llu" 1) == "1" -@test (@sprintf "%Lu" 1) == "1" -@test (@sprintf "%zu" 1) == "1" -@test (@sprintf "%ju" 1) == "1" -@test (@sprintf "%tu" 1) == "1" - -# strings -@test (@sprintf "%s" "test") == "test" -@test (@sprintf "%s" "tést") == "tést" - -@test (@sprintf "%8s" "test") == " test" -@test (@sprintf "%-8s" "test") == "test " - -@test (@sprintf "%s" "tést") == "tést" - -@test (@sprintf "%s" :test) == "test" -@test (@sprintf "%#s" :test) == ":test" -@test (@sprintf "%#8s" :test) == " :test" -@test (@sprintf "%#-8s" :test) == ":test " - -@test (@sprintf "%8.3s" "test") == " tes" -@test (@sprintf "%#8.3s" "test") == " \"te" -@test (@sprintf "%-8.3s" "test") == "tes " -@test (@sprintf "%#-8.3s" "test") == "\"te " -@test (@sprintf "%.3s" "test") == "tes" -@test (@sprintf "%#.3s" "test") == "\"te" -@test (@sprintf "%-.3s" "test") == "tes" -@test (@sprintf "%#-.3s" "test") == "\"te" - -# reasonably complex -@test (@sprintf "Test: %s%c%C%c%#-.0f." "t" 65 66 67 -42) == "Test: tABC-42.." - -#test simple splatting -@test (@sprintf "%d%d" [1 2]...) == "12" - -# invalid format specifiers, not "diouxXDOUeEfFgGaAcCsSpn" -for c in "bBhHIjJkKlLmMNPqQrRtTvVwWyYzZ" - fmt_str = string("%", c) - @test_me ArgumentError("@sprintf: first argument must be a format string") @macroexpand(@sprintf $fmt_str 1) + +@testset "integers" begin + + @test Printf.@sprintf( "% d", 42) == " 42" + @test Printf.@sprintf( "% d", -42) == "-42" + @test Printf.@sprintf( "% 5d", 42) == " 42" + @test Printf.@sprintf( "% 5d", -42) == " -42" + @test Printf.@sprintf( "% 15d", 42) == " 42" + @test Printf.@sprintf( "% 15d", -42) == " -42" + @test Printf.@sprintf("%+d", 42) == "+42" + @test Printf.@sprintf("%+d", -42) == "-42" + @test Printf.@sprintf("%+5d", 42) == " +42" + @test Printf.@sprintf("%+5d", -42) == " -42" + @test Printf.@sprintf("%+15d", 42) == " +42" + @test Printf.@sprintf("%+15d", -42) == " -42" + @test Printf.@sprintf( "%0d", 42) == "42" + @test Printf.@sprintf( "%0d", -42) == "-42" + @test Printf.@sprintf( "%05d", 42) == "00042" + @test Printf.@sprintf( "%05d", -42) == "-0042" + @test Printf.@sprintf( "%015d", 42) == "000000000000042" + @test Printf.@sprintf( "%015d", -42) == "-00000000000042" + @test Printf.@sprintf("%-d", 42) == "42" + @test Printf.@sprintf("%-d", -42) == "-42" + @test Printf.@sprintf("%-5d", 42) == "42 " + @test Printf.@sprintf("%-5d", -42) == "-42 " + @test Printf.@sprintf("%-15d", 42) == "42 " + @test Printf.@sprintf("%-15d", -42) == "-42 " + @test Printf.@sprintf("%-0d", 42) == "42" + @test Printf.@sprintf("%-0d", -42) == "-42" + @test Printf.@sprintf("%-05d", 42) == "42 " + @test Printf.@sprintf("%-05d", -42) == "-42 " + @test Printf.@sprintf("%-015d", 42) == "42 " + @test Printf.@sprintf("%-015d", -42) == "-42 " + @test Printf.@sprintf( "%0-d", 42) == "42" + @test Printf.@sprintf( "%0-d", -42) == "-42" + @test Printf.@sprintf( "%0-5d", 42) == "42 " + @test Printf.@sprintf( "%0-5d", -42) == "-42 " + @test Printf.@sprintf( "%0-15d", 42) == "42 " + @test Printf.@sprintf( "%0-15d", -42) == "-42 " + @test_throws ArgumentError Printf.Format("%d %") + + @test Printf.@sprintf("%lld", 18446744065119617025) == "18446744065119617025" + @test Printf.@sprintf("%+8lld", 100) == " +100" + @test Printf.@sprintf("%+.8lld", 100) == "+00000100" + @test Printf.@sprintf("%+10.8lld", 100) == " +00000100" + @test_throws ArgumentError Printf.Format("%_1lld") + @test Printf.@sprintf("%-1.5lld", -100) == "-00100" + @test Printf.@sprintf("%5lld", 100) == " 100" + @test Printf.@sprintf("%5lld", -100) == " -100" + @test Printf.@sprintf("%-5lld", 100) == "100 " + @test Printf.@sprintf("%-5lld", -100) == "-100 " + @test Printf.@sprintf("%-.5lld", 100) == "00100" + @test Printf.@sprintf("%-.5lld", -100) == "-00100" + @test Printf.@sprintf("%-8.5lld", 100) == "00100 " + @test Printf.@sprintf("%-8.5lld", -100) == "-00100 " + @test Printf.@sprintf("%05lld", 100) == "00100" + @test Printf.@sprintf("%05lld", -100) == "-0100" + @test Printf.@sprintf("% lld", 100) == " 100" + @test Printf.@sprintf("% lld", -100) == "-100" + @test Printf.@sprintf("% 5lld", 100) == " 100" + @test Printf.@sprintf("% 5lld", -100) == " -100" + @test Printf.@sprintf("% .5lld", 100) == " 00100" + @test Printf.@sprintf("% .5lld", -100) == "-00100" + @test Printf.@sprintf("% 8.5lld", 100) == " 00100" + @test Printf.@sprintf("% 8.5lld", -100) == " -00100" + @test Printf.@sprintf("%.0lld", 0) == "0" + @test Printf.@sprintf("%#+21.18llx", -100) == "-0x000000000000000064" + @test Printf.@sprintf("%#.25llo", -100) == "-00000000000000000000000144" + @test Printf.@sprintf("%#+24.20llo", -100) == " -000000000000000000144" + @test Printf.@sprintf("%#+18.21llX", -100) == "-0X000000000000000000064" + @test Printf.@sprintf("%#+20.24llo", -100) == "-0000000000000000000000144" + @test Printf.@sprintf("%#+25.22llu", -1) == " -0000000000000000000001" + @test Printf.@sprintf("%#+25.22llu", -1) == " -0000000000000000000001" + @test Printf.@sprintf("%#+30.25llu", -1) == " -0000000000000000000000001" + @test Printf.@sprintf("%+#25.22lld", -1) == " -0000000000000000000001" + @test Printf.@sprintf("%#-8.5llo", 100) == "000144 " + @test Printf.@sprintf("%#-+ 08.5lld", 100) == "+00100 " + @test Printf.@sprintf("%#-+ 08.5lld", 100) == "+00100 " + @test Printf.@sprintf("%.40lld", 1) == "0000000000000000000000000000000000000001" + @test Printf.@sprintf("% .40lld", 1) == " 0000000000000000000000000000000000000001" + @test Printf.@sprintf("% .40d", 1) == " 0000000000000000000000000000000000000001" + @test Printf.@sprintf("%lld", 18446744065119617025) == "18446744065119617025" + + @test Printf.@sprintf("+%d+", 10) == "+10+" + @test Printf.@sprintf("%#012x", 1) == "0x0000000001" + @test Printf.@sprintf("%#04.8x", 1) == "0x00000001" + + @test Printf.@sprintf("%#-08.2x", 1) == "0x01 " + @test Printf.@sprintf("%#08o", 1) == "00000001" + @test Printf.@sprintf("%d", 1024) == "1024" + @test Printf.@sprintf("%d", -1024) == "-1024" + @test Printf.@sprintf("%i", 1024) == "1024" + @test Printf.@sprintf("%i", -1024) == "-1024" + @test Printf.@sprintf("%u", 1024) == "1024" + @test Printf.@sprintf("%u", UInt(4294966272)) == "4294966272" + @test Printf.@sprintf("%o", 511) == "777" + @test Printf.@sprintf("%o", UInt(4294966785)) == "37777777001" + @test Printf.@sprintf("%x", 305441741) == "1234abcd" + @test Printf.@sprintf("%x", UInt(3989525555)) == "edcb5433" + @test Printf.@sprintf("%X", 305441741) == "1234ABCD" + @test Printf.@sprintf("%X", UInt(3989525555)) == "EDCB5433" + @test Printf.@sprintf("%+d", 1024) == "+1024" + @test Printf.@sprintf("%+d", -1024) == "-1024" + @test Printf.@sprintf("%+i", 1024) == "+1024" + @test Printf.@sprintf("%+i", -1024) == "-1024" + @test Printf.@sprintf("%+u", 1024) == "+1024" + @test Printf.@sprintf("%+u", UInt(4294966272)) == "+4294966272" + @test Printf.@sprintf("%+o", 511) == "+777" + @test Printf.@sprintf("%+o", UInt(4294966785)) == "+37777777001" + @test Printf.@sprintf("%+x", 305441741) == "+1234abcd" + @test Printf.@sprintf("%+x", UInt(3989525555)) == "+edcb5433" + @test Printf.@sprintf("%+X", 305441741) == "+1234ABCD" + @test Printf.@sprintf("%+X", UInt(3989525555)) == "+EDCB5433" + @test Printf.@sprintf("% d", 1024) == " 1024" + @test Printf.@sprintf("% d", -1024) == "-1024" + @test Printf.@sprintf("% i", 1024) == " 1024" + @test Printf.@sprintf("% i", -1024) == "-1024" + @test Printf.@sprintf("% u", 1024) == " 1024" + @test Printf.@sprintf("% u", UInt(4294966272)) == " 4294966272" + @test Printf.@sprintf("% o", 511) == " 777" + @test Printf.@sprintf("% o", UInt(4294966785)) == " 37777777001" + @test Printf.@sprintf("% x", 305441741) == " 1234abcd" + @test Printf.@sprintf("% x", UInt(3989525555)) == " edcb5433" + @test Printf.@sprintf("% X", 305441741) == " 1234ABCD" + @test Printf.@sprintf("% X", UInt(3989525555)) == " EDCB5433" + @test Printf.@sprintf("%+ d", 1024) == "+1024" + @test Printf.@sprintf("%+ d", -1024) == "-1024" + @test Printf.@sprintf("%+ i", 1024) == "+1024" + @test Printf.@sprintf("%+ i", -1024) == "-1024" + @test Printf.@sprintf("%+ u", 1024) == "+1024" + @test Printf.@sprintf("%+ u", UInt(4294966272)) == "+4294966272" + @test Printf.@sprintf("%+ o", 511) == "+777" + @test Printf.@sprintf("%+ o", UInt(4294966785)) == "+37777777001" + @test Printf.@sprintf("%+ x", 305441741) == "+1234abcd" + @test Printf.@sprintf("%+ x", UInt(3989525555)) == "+edcb5433" + @test Printf.@sprintf("%+ X", 305441741) == "+1234ABCD" + @test Printf.@sprintf("%+ X", UInt(3989525555)) == "+EDCB5433" + @test Printf.@sprintf("%#o", 511) == "0777" + @test Printf.@sprintf("%#o", UInt(4294966785)) == "037777777001" + @test Printf.@sprintf("%#x", 305441741) == "0x1234abcd" + @test Printf.@sprintf("%#x", UInt(3989525555)) == "0xedcb5433" + @test Printf.@sprintf("%#X", 305441741) == "0X1234ABCD" + @test Printf.@sprintf("%#X", UInt(3989525555)) == "0XEDCB5433" + @test Printf.@sprintf("%#o", UInt(0)) == "00" + @test Printf.@sprintf("%#x", UInt(0)) == "0x0" + @test Printf.@sprintf("%#X", UInt(0)) == "0X0" + @test Printf.@sprintf("%1d", 1024) == "1024" + @test Printf.@sprintf("%1d", -1024) == "-1024" + @test Printf.@sprintf("%1i", 1024) == "1024" + @test Printf.@sprintf("%1i", -1024) == "-1024" + @test Printf.@sprintf("%1u", 1024) == "1024" + @test Printf.@sprintf("%1u", UInt(4294966272)) == "4294966272" + @test Printf.@sprintf("%1o", 511) == "777" + @test Printf.@sprintf("%1o", UInt(4294966785)) == "37777777001" + @test Printf.@sprintf("%1x", 305441741) == "1234abcd" + @test Printf.@sprintf("%1x", UInt(3989525555)) == "edcb5433" + @test Printf.@sprintf("%1X", 305441741) == "1234ABCD" + @test Printf.@sprintf("%1X", UInt(3989525555)) == "EDCB5433" + @test Printf.@sprintf("%20d", 1024) == " 1024" + @test Printf.@sprintf("%20d", -1024) == " -1024" + @test Printf.@sprintf("%20i", 1024) == " 1024" + @test Printf.@sprintf("%20i", -1024) == " -1024" + @test Printf.@sprintf("%20u", 1024) == " 1024" + @test Printf.@sprintf("%20u", UInt(4294966272)) == " 4294966272" + @test Printf.@sprintf("%20o", 511) == " 777" + @test Printf.@sprintf("%20o", UInt(4294966785)) == " 37777777001" + @test Printf.@sprintf("%20x", 305441741) == " 1234abcd" + @test Printf.@sprintf("%20x", UInt(3989525555)) == " edcb5433" + @test Printf.@sprintf("%20X", 305441741) == " 1234ABCD" + @test Printf.@sprintf("%20X", UInt(3989525555)) == " EDCB5433" + @test Printf.@sprintf("%-20d", 1024) == "1024 " + @test Printf.@sprintf("%-20d", -1024) == "-1024 " + @test Printf.@sprintf("%-20i", 1024) == "1024 " + @test Printf.@sprintf("%-20i", -1024) == "-1024 " + @test Printf.@sprintf("%-20u", 1024) == "1024 " + @test Printf.@sprintf("%-20u", UInt(4294966272)) == "4294966272 " + @test Printf.@sprintf("%-20o", 511) == "777 " + @test Printf.@sprintf("%-20o", UInt(4294966785)) == "37777777001 " + @test Printf.@sprintf("%-20x", 305441741) == "1234abcd " + @test Printf.@sprintf("%-20x", UInt(3989525555)) == "edcb5433 " + @test Printf.@sprintf("%-20X", 305441741) == "1234ABCD " + @test Printf.@sprintf("%-20X", UInt(3989525555)) == "EDCB5433 " + @test Printf.@sprintf("%020d", 1024) == "00000000000000001024" + @test Printf.@sprintf("%020d", -1024) == "-0000000000000001024" + @test Printf.@sprintf("%020i", 1024) == "00000000000000001024" + @test Printf.@sprintf("%020i", -1024) == "-0000000000000001024" + @test Printf.@sprintf("%020u", 1024) == "00000000000000001024" + @test Printf.@sprintf("%020u", UInt(4294966272)) == "00000000004294966272" + @test Printf.@sprintf("%020o", 511) == "00000000000000000777" + @test Printf.@sprintf("%020o", UInt(4294966785)) == "00000000037777777001" + @test Printf.@sprintf("%020x", 305441741) == "0000000000001234abcd" + @test Printf.@sprintf("%020x", UInt(3989525555)) == "000000000000edcb5433" + @test Printf.@sprintf("%020X", 305441741) == "0000000000001234ABCD" + @test Printf.@sprintf("%020X", UInt(3989525555)) == "000000000000EDCB5433" + @test Printf.@sprintf("%#20o", 511) == " 0777" + @test Printf.@sprintf("%#20o", UInt(4294966785)) == " 037777777001" + @test Printf.@sprintf("%#20x", 305441741) == " 0x1234abcd" + @test Printf.@sprintf("%#20x", UInt(3989525555)) == " 0xedcb5433" + @test Printf.@sprintf("%#20X", 305441741) == " 0X1234ABCD" + @test Printf.@sprintf("%#20X", UInt(3989525555)) == " 0XEDCB5433" + @test Printf.@sprintf("%#020o", 511) == "00000000000000000777" + @test Printf.@sprintf("%#020o", UInt(4294966785)) == "00000000037777777001" + @test Printf.@sprintf("%#020x", 305441741) == "0x00000000001234abcd" + @test Printf.@sprintf("%#020x", UInt(3989525555)) == "0x0000000000edcb5433" + @test Printf.@sprintf("%#020X", 305441741) == "0X00000000001234ABCD" + @test Printf.@sprintf("%#020X", UInt(3989525555)) == "0X0000000000EDCB5433" + @test Printf.@sprintf("%0-20d", 1024) == "1024 " + @test Printf.@sprintf("%0-20d", -1024) == "-1024 " + @test Printf.@sprintf("%0-20i", 1024) == "1024 " + @test Printf.@sprintf("%0-20i", -1024) == "-1024 " + @test Printf.@sprintf("%0-20u", 1024) == "1024 " + @test Printf.@sprintf("%0-20u", UInt(4294966272)) == "4294966272 " + @test Printf.@sprintf("%-020o", 511) == "777 " + @test Printf.@sprintf("%-020o", UInt(4294966785)) == "37777777001 " + @test Printf.@sprintf("%-020x", 305441741) == "1234abcd " + @test Printf.@sprintf("%-020x", UInt(3989525555)) == "edcb5433 " + @test Printf.@sprintf("%-020X", 305441741) == "1234ABCD " + @test Printf.@sprintf("%-020X", UInt(3989525555)) == "EDCB5433 " + @test Printf.@sprintf("%.20d", 1024) == "00000000000000001024" + @test Printf.@sprintf("%.20d", -1024) == "-00000000000000001024" + @test Printf.@sprintf("%.20i", 1024) == "00000000000000001024" + @test Printf.@sprintf("%.20i", -1024) == "-00000000000000001024" + @test Printf.@sprintf("%.20u", 1024) == "00000000000000001024" + @test Printf.@sprintf("%.20u", UInt(4294966272)) == "00000000004294966272" + @test Printf.@sprintf("%.20o", 511) == "00000000000000000777" + @test Printf.@sprintf("%.20o", UInt(4294966785)) == "00000000037777777001" + @test Printf.@sprintf("%.20x", 305441741) == "0000000000001234abcd" + @test Printf.@sprintf("%.20x", UInt(3989525555)) == "000000000000edcb5433" + @test Printf.@sprintf("%.20X", 305441741) == "0000000000001234ABCD" + @test Printf.@sprintf("%.20X", UInt(3989525555)) == "000000000000EDCB5433" + @test Printf.@sprintf("%20.5d", 1024) == " 01024" + @test Printf.@sprintf("%20.5d", -1024) == " -01024" + @test Printf.@sprintf("%20.5i", 1024) == " 01024" + @test Printf.@sprintf("%20.5i", -1024) == " -01024" + @test Printf.@sprintf("%20.5u", 1024) == " 01024" + @test Printf.@sprintf("%20.5u", UInt(4294966272)) == " 4294966272" + @test Printf.@sprintf("%20.5o", 511) == " 00777" + @test Printf.@sprintf("%20.5o", UInt(4294966785)) == " 37777777001" + @test Printf.@sprintf("%20.5x", 305441741) == " 1234abcd" + @test Printf.@sprintf("%20.10x", UInt(3989525555)) == " 00edcb5433" + @test Printf.@sprintf("%20.5X", 305441741) == " 1234ABCD" + @test Printf.@sprintf("%20.10X", UInt(3989525555)) == " 00EDCB5433" + @test Printf.@sprintf("%020.5d", 1024) == " 01024" + @test Printf.@sprintf("%020.5d", -1024) == " -01024" + @test Printf.@sprintf("%020.5i", 1024) == " 01024" + @test Printf.@sprintf("%020.5i", -1024) == " -01024" + @test Printf.@sprintf("%020.5u", 1024) == " 01024" + @test Printf.@sprintf("%020.5u", UInt(4294966272)) == " 4294966272" + @test Printf.@sprintf("%020.5o", 511) == " 00777" + @test Printf.@sprintf("%020.5o", UInt(4294966785)) == " 37777777001" + @test Printf.@sprintf("%020.5x", 305441741) == " 1234abcd" + @test Printf.@sprintf("%020.10x", UInt(3989525555)) == " 00edcb5433" + @test Printf.@sprintf("%020.5X", 305441741) == " 1234ABCD" + @test Printf.@sprintf("%020.10X", UInt(3989525555)) == " 00EDCB5433" + @test Printf.@sprintf("%20.0d", 1024) == " 1024" + @test Printf.@sprintf("%20.d", -1024) == " -1024" + @test Printf.@sprintf("%20.d", 0) == " 0" + @test Printf.@sprintf("%20.0i", 1024) == " 1024" + @test Printf.@sprintf("%20.i", -1024) == " -1024" + @test Printf.@sprintf("%20.i", 0) == " 0" + @test Printf.@sprintf("%20.u", 1024) == " 1024" + @test Printf.@sprintf("%20.0u", UInt(4294966272)) == " 4294966272" + @test Printf.@sprintf("%20.u", UInt(0)) == " 0" + @test Printf.@sprintf("%20.o", 511) == " 777" + @test Printf.@sprintf("%20.0o", UInt(4294966785)) == " 37777777001" + @test Printf.@sprintf("%20.o", UInt(0)) == " 0" + @test Printf.@sprintf("%20.x", 305441741) == " 1234abcd" + @test Printf.@sprintf("%20.0x", UInt(3989525555)) == " edcb5433" + @test Printf.@sprintf("%20.x", UInt(0)) == " 0" + @test Printf.@sprintf("%20.X", 305441741) == " 1234ABCD" + @test Printf.@sprintf("%20.0X", UInt(3989525555)) == " EDCB5433" + @test Printf.@sprintf("%20.X", UInt(0)) == " 0" + end -# combo -@test (@sprintf "%f %d %d %f" 1.0 [3 4]... 5) == "1.000000 3 4 5.000000" - -# multi -@test (@sprintf "%s %f %9.5f %d %d %d %d%d%d%d" [1:6;]... [7,8,9,10]...) == "1 2.000000 3.00000 4 5 6 78910" - -# comprehension -@test (@sprintf "%s %s %s %d %d %d %f %f %f" Any[10^x+y for x=1:3,y=1:3 ]...) == "11 101 1001 12 102 1002 13.000000 103.000000 1003.000000" - -# @printf -@test_me ArgumentError("@printf: called with no arguments") @macroexpand(@printf) -@test_me ArgumentError("@printf: first or second argument must be a format string") @macroexpand(@printf 1) - -# Check bug with trailing nul printing BigFloat -@test (@sprintf("%.330f", BigFloat(1)))[end] != '\0' - -# Check utf8 strings #23880 -@test (@sprintf("X%d", 2)) == "X2" -@test (@sprintf("\u00d0%d", 2)) == "\u00d02" -@test (@sprintf("\u0f00%d", 2)) == "\u0f002" -@test (@sprintf("\U0001ffff%d", 2)) == "\U0001ffff2" -@test (@sprintf("%dX%d", 1, 2)) == "1X2" -@test (@sprintf("%d\u00d0%d", 1, 2)) == "1\u00d02" -@test (@sprintf("%d\u0f00%d", 1, 2)) == "1\u0f002" -@test (@sprintf("%d\U0001ffff%d", 1, 2)) == "1\U0001ffff2" -@test (@sprintf("%d\u2203%d\u0203", 1, 2)) == "1\u22032\u0203" -@test_me ArgumentError @macroexpand(@sprintf("%y%d", 1, 2)) -@test_me ArgumentError @macroexpand(@sprintf("%\u00d0%d", 1, 2)) -@test_me ArgumentError @macroexpand(@sprintf("%\u0f00%d", 1, 2)) -@test_me ArgumentError @macroexpand(@sprintf("%\U0001ffff%d", 1, 2)) - -# test at macro execution time -@test_throws ArgumentError("@sprintf: wrong number of arguments (2) should be (3)") (@sprintf "%d%d%d" 1:2...) - -# issue #29662 -@test (@sprintf "%12.3e" pi*1e100) == " 3.142e+100" +end # @testset "Printf" diff --git a/test/worlds.jl b/test/worlds.jl index c87e0104b944c..a6aae68da2ca7 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -305,8 +305,6 @@ src4 = code_typed(applyf35855_2, (Vector{Any},))[1] @test !(wany4 == wany3) || equal(src4, src3) # code doesn't change unless you invalidate ## ambiguities do not trigger invalidation -using Printf -Printf.gen("%f") mi = instance(+, (AbstractChar, UInt8)) w = worlds(mi)