diff --git a/base/Base.jl b/base/Base.jl index f64c061f9e9fb..f5feb4ce1d5c9 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -269,7 +269,6 @@ include("filesystem.jl") using .Filesystem include("cmd.jl") include("process.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/base/grisu/grisu.jl b/base/grisu/grisu.jl deleted file mode 100644 index 5140a84703071..0000000000000 --- a/base/grisu/grisu.jl +++ /dev/null @@ -1,241 +0,0 @@ -# This file is a part of Julia. License is MIT: https://julialang.org/license - -module Grisu - -export print_shortest -export DIGITS, DIGITSs, grisu - -const SHORTEST = 1 -const FIXED = 2 -const PRECISION = 3 - -include("grisu/float.jl") -include("grisu/fastshortest.jl") -include("grisu/fastprecision.jl") -include("grisu/fastfixed.jl") -include("grisu/bignums.jl") -include("grisu/bignum.jl") - -const DIGITS = Vector{UInt8}(undef, 309+17) -const BIGNUMS = [Bignums.Bignum(),Bignums.Bignum(),Bignums.Bignum(),Bignums.Bignum()] - -# NOTE: DIGITS[s] is deprecated; you should use getbuf() instead. -const DIGITSs = [DIGITS] -const BIGNUMSs = [BIGNUMS] -function __init__() - Threads.resize_nthreads!(DIGITSs) - Threads.resize_nthreads!(BIGNUMSs) -end - -function getbuf() - tls = task_local_storage() - d = get(tls, :DIGITS, nothing) - if d === nothing - d = Vector{UInt8}(undef, 309+17) - tls[:DIGITS] = d - end - return d::Vector{UInt8} -end - -""" - (len, point, neg) = Grisu.grisu(v::AbstractFloat, mode, requested_digits, [buffer], [bignums]) - -Convert the number `v` to decimal using the Grisu algorithm. - -`mode` can be one of: - - `Grisu.SHORTEST`: convert to the shortest decimal representation which can be "round-tripped" back to `v`. - - `Grisu.FIXED`: round to `requested_digits` digits. - - `Grisu.PRECISION`: round to `requested_digits` significant digits. - -The characters are written as bytes to `buffer`, with a terminating NUL byte, and `bignums` are used internally as part of the correction step. You can call `Grisu.getbuf()` to obtain a suitable task-local buffer. - -The returned tuple contains: - - - `len`: the number of digits written to `buffer` (excluding NUL) - - `point`: the location of the radix point relative to the start of the array (e.g. if - `point == 3`, then the radix point should be inserted between the 3rd and 4th - digit). Note that this can be negative (for very small values), or greater than `len` - (for very large values). - - `neg`: the signbit of `v` (see [`signbit`](@ref)). -""" -function grisu(v::AbstractFloat,mode,requested_digits,buffer=DIGITSs[Threads.threadid()],bignums=BIGNUMSs[Threads.threadid()]) - if signbit(v) - neg = true - v = -v - else - neg = false - end - if mode == PRECISION && requested_digits == 0 - buffer[1] = 0x00 - len = 0 - return 0, 0, neg - end - if v == 0.0 - buffer[1] = 0x30 - buffer[2] = 0x00 - len = point = 1 - return len, point, neg - end - if mode == SHORTEST - status,len,point = fastshortest(v,buffer) - elseif mode == FIXED - status,len,point = fastfixedtoa(v,0,requested_digits,buffer) - elseif mode == PRECISION - status,len,point = fastprecision(v,requested_digits,buffer) - end - status && return len-1, point, neg - status, len, point = bignumdtoa(v,mode,requested_digits,buffer,bignums) - return len-1, point, neg -end - -nanstr(x::AbstractFloat) = "NaN" -nanstr(x::Float32) = "NaN32" -nanstr(x::Float16) = "NaN16" -infstr(x::AbstractFloat) = "Inf" -infstr(x::Float32) = "Inf32" -infstr(x::Float16) = "Inf16" - -function _show(io::IO, x::AbstractFloat, mode, n::Int, typed, compact) - isnan(x) && return print(io, typed ? nanstr(x) : "NaN") - if isinf(x) - signbit(x) && print(io,'-') - print(io, typed ? infstr(x) : "Inf") - return - end - typed && isa(x,Float16) && print(io, "Float16(") - buffer = getbuf() - len, pt, neg = grisu(x,mode,n,buffer) - pdigits = pointer(buffer) - if mode == PRECISION - while len > 1 && buffer[len] == 0x30 - len -= 1 - end - end - neg && print(io,'-') - exp_form = pt <= -4 || pt > 6 - exp_form = exp_form || (pt >= len && abs(mod(x + 0.05, 10^(pt - len)) - 0.05) > 0.05) # see issue #6608 - if exp_form # .00001 to 100000. - # => #.#######e### - # assumes ASCII/UTF8 encoding of digits is okay for out: - unsafe_write(io, pdigits, 1) - print(io, '.') - if len > 1 - unsafe_write(io, pdigits+1, len-1) - else - print(io, '0') - end - print(io, (typed && isa(x,Float32)) ? 'f' : 'e') - print(io, string(pt - 1)) - typed && isa(x,Float16) && print(io, ")") - return - elseif pt <= 0 - # => 0.00######## - print(io, "0.") - while pt < 0 - print(io, '0') - pt += 1 - end - unsafe_write(io, pdigits, len) - elseif pt >= len - # => ########00.0 - unsafe_write(io, pdigits, len) - while pt > len - print(io, '0') - len += 1 - end - print(io, ".0") - else # => ####.#### - unsafe_write(io, pdigits, pt) - print(io, '.') - unsafe_write(io, pdigits+pt, len-pt) - end - typed && !compact && isa(x,Float32) && print(io, "f0") - typed && isa(x,Float16) && print(io, ")") - nothing -end - -# function Base.show(io::IO, x::Union{Float64,Float32}) -# if get(io, :compact, false) -# _show(io, x, PRECISION, 6, x isa Float64, true) -# else -# _show(io, x, SHORTEST, 0, get(io, :typeinfo, Any) !== typeof(x), false) -# end -# end - -# function Base.show(io::IO, x::Float16) -# hastypeinfo = Float16 === get(io, :typeinfo, Any) -# # if hastypeinfo, the printing would be more compact using `SHORTEST` -# # while still retaining all the information -# # BUT: we want to print all digits in `show`, not in display, so we rely -# # on the :compact property to make the decision -# # (cf. https://github.com/JuliaLang/julia/pull/24651#issuecomment-345535687) -# if get(io, :compact, false) && !hastypeinfo -# _show(io, x, PRECISION, 5, false, true) -# else -# _show(io, x, SHORTEST, 0, !hastypeinfo, false) -# end -# end - -# Base.print(io::IO, x::Float32) = _show(io, x, SHORTEST, 0, false, false) -# Base.print(io::IO, x::Float16) = _show(io, x, SHORTEST, 0, false, false) - -# normal: -# 0 < pt < len ####.#### len+1 -# pt <= 0 0.000######## len-pt+1 -# len <= pt (dot) ########000. pt+1 -# len <= pt (no dot) ########000 pt -# exponential: -# pt <= 0 ########e-### len+k+2 -# 0 < pt ########e### len+k+1 - -function _print_shortest(io::IO, x::AbstractFloat, dot::Bool, mode, n::Int) - isnan(x) && return print(io, "NaN") - x < 0 && print(io,'-') - isinf(x) && return print(io, "Inf") - buffer = getbuf() - len, pt, neg = grisu(x,mode,n,buffer) - pdigits = pointer(buffer) - e = pt-len - k = -9<=e<=9 ? 1 : 2 - if -pt > k+1 || e+dot > k+1 - # => ########e### - unsafe_write(io, pdigits+0, len) - print(io, 'e') - print(io, string(e)) - return - elseif pt <= 0 - # => 0.000######## - print(io, "0.") - while pt < 0 - print(io, '0') - pt += 1 - end - unsafe_write(io, pdigits+0, len) - elseif e >= dot - # => ########000. - unsafe_write(io, pdigits+0, len) - while e > 0 - print(io, '0') - e -= 1 - end - if dot - print(io, '.') - end - else # => ####.#### - unsafe_write(io, pdigits+0, pt) - print(io, '.') - unsafe_write(io, pdigits+pt, len-pt) - end - nothing -end - -""" - print_shortest(io::IO, x) - -Print the shortest possible representation, with the minimum number of consecutive non-zero -digits, of number `x`, ensuring that it would parse to the exact same number. -""" -print_shortest(io::IO, x::AbstractFloat, dot::Bool) = _print_shortest(io, x, dot, SHORTEST, 0) -print_shortest(io::IO, x::Union{AbstractFloat,Integer}) = print_shortest(io, float(x), false) - -end # module diff --git a/base/printf.jl b/base/printf.jl index d85732fe88e38..7966f716900b9 100644 --- a/base/printf.jl +++ b/base/printf.jl @@ -1,1265 +1,444 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license module Printf -using .Base.Grisu -using .Base.Ryu -using .Base.GMP - -### printf formatter generation ### -const SmallFloatingPoint = Union{Float64,Float32,Float16} -const SmallNumber = Union{SmallFloatingPoint,Base.BitInteger} -function getbuf() - tls = task_local_storage() - d = get(tls, :DIGITS, nothing) - if d === nothing - d = Vector{UInt8}(undef, 309+17) - tls[:DIGITS] = d - end - return d::Vector{UInt8} -end +using .Base.Ryu -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 = $(getbuf()))) - gotbuf = true +export @printf, @sprintf + +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'}} + +struct Spec{T} # T => %type => Val{'type'} + leftalign::Bool + plus::Bool + space::Bool + zero::Bool + hash::Bool + width::Int + precision::Int +end + +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) + +struct Format{S, T} + str::S + substrings::Vector{UnitRange{Int}} + formats::T # Tuple of Specs +end + +base(::Type{T}) where {T <: HexBases} = 16 +base(::Type{Val{'o'}}) = 8 +base(x) = 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 - arg, ex = f(x...) - push!(args, arg) - push!(blk.args, ex) - end - end - push!(blk.args, :nothing) - return args, blk -end - -### printf format string parsing ### - -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 : Base.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 + 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 = 10width + (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 - list[i] *= list[j] end - deleteat!(list,i+1:j) end - i += 1 - end - return list -end - -## parse a single printf specifier ## - -# 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 - -pop_or_die!(s, a) = !isempty(a) ? popfirst!(a) : - throw(ArgumentError("invalid printf format string: $(repr(s))")) - -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) + # 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 - 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) - 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))")) - end - # TODO: warn about silly flag/conversion combinations - flags, width, precision, c -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 -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 + elseif b in b"Ljqtz" + b = bytes[pos] + pos += 1 + end + # parse type + !(b in b"diouxXDOUeEfFgGaAcCsSpn") && throw(ArgumentError("invalid format string: '$f'")) + type = Val{Char(b)} + if type <: Ints && precision > 0 + zero = false + elseif (type <: Strings || type <: Chars) && !parsedprecdigits + precision = -1 + elseif type <: Floats && !parsedprecdigits + precision = 6 + end + push!(fmts, Spec{type}(leftalign, plus, space, zero, hash, width, precision)) + start = pos + 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 + push!(strs, start:pos - 1 - (b == UInt8('%'))) end + return Format(bytes, strs, Tuple(fmts)) end -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 - end - end +macro format_str(str) + Format(str) 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 - end - count += ndigits - precision -= ndigits - elseif ndigits <= pt - # dddd000.000000 - count += ndigits - if ndigits < pt - count += pt - ndigits - end - count += trailingzeros - else # 0 < pt < ndigits - # dd.dd0000 - ndigits -= pt - count += pt + 1 + ndigits - precision -= ndigits - end - if trailingzeros && precision > 0 - count += precision - end - return count -end +const hex = b"0123456789abcdef" +const HEX = b"0123456789ABCDEF" -# note: if print_fixed is changed, print_fixed_width should be changed accordingly -function print_fixed(out, precision, pt, ndigits, trailingzeros=true, buf = getbuf()) - pdigits = pointer(buf) - if pt <= 0 - # 0.0dddd0 - print(out, '0') - print(out, '.') - precision += pt - while pt < 0 - print(out, '0') - pt += 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 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 - 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 -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 +@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 - print(out, Char('0'+d)) - end - exp = rem(exp,100) - print(out, Char('0'+div(exp,10))) - print(out, Char('0'+rem(exp,10))) -end - -function print_exp_a(out, exp::Integer) - print(out, exp < 0 ? '-' : '+') - exp = abs(exp) - print(out, exp) -end - - -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 + 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 - # 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 + return pos end -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)) +# strings +@inline function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Strings} + leftalign, width, prec = spec.leftalign, spec.width, spec.precision + str = string(arg) + p = prec == -1 ? length(str) : prec + if !leftalign && width > p + for _ = 1:(width - p) + buf[pos] = UInt8(' ') + pos += 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, '.'))) + for (i, c) in enumerate(str) + i > p && break + pos = writechar(buf, pos, c) end - # print space padding - if padding !== nothing && '-' in flags - push!(blk.args, pad(width-1, padding, ' ')) - end - # return arg, expr - :(($x)::Real), ex -end - -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(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 - 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 - end - padn - 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')) - end + if leftalign && width > p + for _ = 1:(width - p) + buf[pos] = UInt8(' ') + pos += 1 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 + return pos 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(getbuf())-1) - push!(blk.args, :((do_out, args) = $fn(out,$x,$ndigits, $flags, $width, $precision, $c, buf))) +# 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) + n = i = ndigits(arg, base=bs, pad=1) + x, neg = arg < 0 ? (-arg, true) : (arg, false) + arglen = n + (neg || (plus | space)) + + (T == Val{'o'} && hash ? 2 : 0) + + (T == Val{'x'} && hash ? 2 : 0) + (T == Val{'X'} && hash ? 2 : 0) + arglen2 = arglen < width && prec > 0 ? arglen + min(max(0, prec - n), width - arglen) : arglen + if !leftalign && !zero && arglen2 < width + # pad left w/ spaces + for _ = 1:(width - arglen2) + buf[pos] = UInt8(' ') + pos += 1 + end end - 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) + 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 - # 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 + if T == Val{'o'} && hash + buf[pos] = UInt8('0') + buf[pos + 1] = UInt8('o') + pos += 2 + 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 '+' 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)) + if zero && arglen2 < width + for _ = 1:(width - arglen2) + buf[pos] = UInt8('0') + pos += 1 + end + elseif n < prec + for _ = 1:(prec - n) + buf[pos] = UInt8('0') + pos += 1 + end + elseif arglen < arglen2 + for _ = 1:(arglen2 - arglen) + buf[pos] = UInt8('0') + pos += 1 end end - if precision < 0 && width > 0 - if '#' in flags - padding = :($padding - (len-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 - padding = :($padding - (len>1 ? len : 0)) + x = oftype(x, div(x, 10)) end + i -= 1 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')) - end - elseif precision < 0 - ifvpblk = Expr(:if, :(len > 1), Expr(:block)) - vpblk = ifvpblk.args[2] - if '#' in flags - push!(blk.args, :(print(out, '.'))) + pos += n + if leftalign && arglen2 < width + # pad right + for _ = 1:(width - arglen2) + buf[pos] = UInt8(' ') + pos += 1 + end + end + return pos +end + +# floats +@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 = float(arg) + if T <: Union{Val{'e'}, Val{'E'}} + newpos = Ryu.writeexp(buf, pos, x, plus, space, hash, prec, char(T), UInt8('.')) + elseif T <: Union{Val{'f'}, Val{'F'}} + newpos = Ryu.writefixed(buf, pos, x, plus, space, hash, prec, UInt8('.')) + elseif T <: Union{Val{'g'}, Val{'G'}} + exp = exponent(arg) + prec = prec == 0 ? 1 : prec + x = round(x, sigdigits=prec) + if exp < -4 || exp >= prec + newpos = Ryu.writeexp(buf, pos, x, plus, space, hash, prec, T == Val('g') ? UInt8('e') : UInt8('E'), UInt8('.')) else - push!(vpblk.args, :(print(out, '.'))) + newpos = Ryu.writefixed(buf, pos, x, plus, space, hash, prec, UInt8('.'), true) 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)), ' ')) - end - push!(blk.args, :(print(out, $x))) - if '-' in flags - push!(blk.args, pad(width, :($width-textwidth($x)), ' ')) - 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))) + if newpos - pos < width + # need to pad + if leftalign + # easy case, just pad spaces after number + for _ = 1:(width - (newpos - pos)) + buf[newpos] = UInt8(' ') + newpos += 1 + end else - push!(blk.args, :(show(io, $x))) - end - if precision!=-1 - push!(blk.args, :(print(out, _limit(String(take!(io)), $precision)))) + # right aligned + n = width - (newpos - pos) + if zero + ex = (arg < 0 || (plus | space)) + so = pos + ex + len = (newpos - pos) - ex + unsafe_copyto!(buf, so + n, buf, so, len) + for i = so:(so + n - 1) + buf[i] = UInt8('0') + end + newpos += n + else + unsafe_copyto!(buf, pos + n, buf, pos, newpos - pos) + for i = pos:(pos + n - 1) + buf[i] = UInt8(' ') + end + newpos += n + end 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 + return newpos 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(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] +# pointers +fmt(buf, pos, arg, spec::Spec{Pointer}) = fmt(buf, pos, Int(arg), ptrfmt(spec)) - ### %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)) +@inline function format(buf, pos, f::Format, args...) + # write out first substring + for i in f.substrings[1] + @inbounds buf[pos] = f.str[i] + pos += 1 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)) - end - end -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 - end - pt = Base.ndigits0z(x, 8) + 1 - length(digits) < pt+1 && resize!(digits, pt+1) - neg && (x.size = -x.size) - p = convert(Ptr{UInt8}, digits) + 1 - GMP.MPZ.get_str!(p, 8, x) - neg && (x.size = -x.size) - return neg, Int32(pt), Int32(pt) -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) - 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' + # for each format, write out arg and next substring + # unroll up to 8 formats + N = length(f.formats) + Base.@nexprs 8 i -> begin + if N >= i + pos = fmt(buf, pos, args[i], f.formats[i]) + for j in f.substrings[i + 1] + buf[pos] = f.str[j] + pos += 1 + end end end - return Int32(len), Int32(pt), neg -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) - -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 -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 + if N > 8 + for i = 9:length(f.formats) + pos = fmt(buf, pos, args[i], f.formats[i]) + for j in f.substrings[i + 1] + @inbounds buf[pos] = f.str[j] + pos += 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 -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 -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 - info = decode_dec(x) - d == n && return info - p = convert(Ptr{Cvoid}, digits) + info[2] - ccall(:memset, Ptr{Cvoid}, (Ptr{Cvoid}, Cint, Csize_t), p, '0', n - info[2]) - return info - end - return (n, d, decode_dec(round(BigInt,x/big(10)^(d-n)))[3]) + return pos end +plength(f::Spec{T}, x::Real) where {T} = max(f.width, f.precision, plength(x)) + plength(T) +plength(f, x::AbstractString) = max(f.width, min(f.precision == -1 ? sizeof(x) : f.precision, sizeof(x))) +plength(f, x) = max(f.width, plength(x)) -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) +plength(::Type{T}) where {T <: Union{Val{'o'}, HexBases}} = 2 +plength(::Type{T}) where {T} = 0 -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) +plength(x::Float16) = 9 + 5 +plength(x::Float32) = 39 + 9 +plength(x::Float64) = 309 + 17 +plength(x::Real) = plength(float(x)) +plength(x::Integer) = ndigits(x, base=10) +plength(c::Char) = ncodeunits(c) +plength(s::AbstractString) = sizeof(s) +plength(p::Ptr) = 2 * sizeof(p) + 2 +plength(x) = 10 -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) - end - i = (sizeof(u)<<1)-(leading_zeros(u)>>2) - while i > 0 - digits[i] = symbols[(u&0xf)+1] - u >>= 4 - i -= 1 +@inline function preallocate(f, args...) + len = sum(sizeof, f.substrings) + N = length(f.formats) + # unroll up to 8 formats + Base.@nexprs 8 i -> begin + if N >= i + len += plength(f.formats[i], args[i]) end - # pt is the binary exponent - return Int32(n), Int32(p-1), x < 0.0 end -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 + if N > 8 + for i = 9:length(f.formats) + len += plength(f.formats[i], args[i]) end - # pt is the binary exponent - return Int32(n), Int32(p-1), x < 0.0 end + return len end -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 - -# not implemented -ini_hex(x::Integer,ndigits::Int,digits) = throw(MethodError(ini_hex,(x,ndigits,digits))) +@noinline argmismatch(a, b) = + throw(ArgumentError("mismatch between # of format specifiers and provided args: $a != $b")) -#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(args) == length(f.formats) || argmismatch(length(args), length(f.formats)) + buf = Vector{UInt8}(undef, preallocate(f, args...)) + pos = format(buf, 1, f, args...) + GC.@preserve buf unsafe_write(io, pointer(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(args) == length(f.formats) || argmismatch(length(args), length(f.formats)) + buf = Vector{UInt8}(undef, preallocate(f, args...)) + pos = format(buf, 1, f, args...) + return unsafe_string(pointer(buf), pos-1) end -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, fmt_or_first_arg, args...) + if io_or_fmt isa String + io = stdout + fmt = Format(io_or_fmt) + return esc(:(Printf.format($io, $fmt, $fmt_or_first_arg, $(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 + fmt = Format(fmt_or_first_arg) + return esc(:(Printf.format($io, $fmt, $(args...)))) end end -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 # module diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index aa00de46981d8..abc25b2598001 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -137,7 +137,7 @@ function generate_precompile_statements() # Extract the precompile statements from stderr statements = Set{String}() for statement in eachline(precompile_file_h) - occursin("Main.", statement) && continue + (occursin("Main.", statement) || occursin("Printf.", statement)) && continue # check for `#x##s66` style variable names not in quotes occursin(r"#\w", statement) && count(r"(?:#+\w+)+", statement) != diff --git a/stdlib/Printf/src/Printf.jl b/stdlib/Printf/src/Printf.jl index 8828d61b6aa91..6319b796bb003 100644 --- a/stdlib/Printf/src/Printf.jl +++ b/stdlib/Printf/src/Printf.jl @@ -1,69 +1,9 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license module Printf -# the macro implementations here exactly mirrors the -# macros left in base/printf.jl, and uses the utility there export @printf, @sprintf -using Base.Printf: _printf, is_str_expr, fix_dec, DIGITS, DIGITSs, print_fixed, print_fixed_width, decode_dec, decode_hex, - ini_hex, ini_HEX, print_exp_a, decode_0ct, decode_HEX, ini_dec, print_exp_e, - decode_oct, _limit, SmallNumber -using Unicode: textwidth -""" - @printf([io::IOStream], "%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) -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 -``` -""" -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]) - 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]) - end -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 -``` -""" -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 -end +using Base.Printf end # module diff --git a/stdlib/Printf/test/runtests.jl b/stdlib/Printf/test/runtests.jl index 5f8935955156b..3f4835e06e0da 100644 --- a/stdlib/Printf/test/runtests.jl +++ b/stdlib/Printf/test/runtests.jl @@ -2,297 +2,600 @@ 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 - end +# TODO + # %a + # tests %p + # tests %g + # different types for existing tests + # BigFloat + # %s "alternate" forms for String/Symbol + +@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 -# printf -# int -@test (@sprintf "%d" typemax(Int64)) == "9223372036854775807" -@test (@sprintf "%a" typemax(Int64)) == "0x7.fffffffffffffffp+60" -@test (@sprintf "%A" typemax(Int64)) == "0X7.FFFFFFFFFFFFFFFP+60" - -#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")), - num in (UInt16(42), UInt32(42), UInt64(42), UInt128(42), - Int16(42), Int32(42), Int64(42), Int128(42), big"42") - #big"42" causes stack overflow on %a ; gh #14409 - num isa BigInt && fmt in ["%a", "%#o", "%g"] && continue - @test @eval(@sprintf($fmt, $num) == $val) +@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 -# pointers -if Sys.WORD_SIZE == 64 - @test (@sprintf "%20p" 0) == " 0x0000000000000000" - @test (@sprintf "%-20p" 0) == "0x0000000000000000 " -elseif Sys.WORD_SIZE == 32 - @test (@sprintf "%20p" 0) == " 0x00000000" - @test (@sprintf "%-20p" 0) == "0x00000000 " -else - @test false -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) -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" -# 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) - fsign = string((val < 0 ? "-" : "+"), prefix) - nsign = string((val < 0 ? "-" : " "), prefix) - osign = val < 0 ? string("-", prefix) : string(prefix, "0") - esign = string(val < 0 ? "-" : "", prefix) - esignend = val < 0 ? "" : " " - - for (flag::AbstractString, ans::AbstractString) in ( - ("", string(" ", nsign, res)), - ("+", string(" ", fsign, res)), - (" ", string(" ", nsign, res)), - ("0", string(osign, "00", res)), - ("-", string(esign, res, " ", esignend)), - ("0+", string(fsign, "00", res)), - ("0 ", string(nsign, "00", res)), - ("-+", string(fsign, res, " ")), - ("- ", string(nsign, res, " ")), - ) - fmt_string = string(vflag, flag, space_fmt) - @test @eval(@sprintf($fmt_string, $val) == $ans) - end end -for i in ( - (42, "", "i", "42", ""), - (42, "", "d", "42", ""), - - (42, "", "u", "42", ""), - (42, "", "x", "2a", ""), - (42, "", "X", "2A", ""), - (42, "", "o", "52", ""), - - (42, "#", "x", "2a", "0x"), - (42, "#", "X", "2A", "0X"), - (42, "#", "o", "052", ""), +@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", '𐀀') == "𐀀" - (1.2345, "", ".2f", "1.23", ""), - (1.2345, "", ".2e", "1.23e+00", ""), - (1.2345, "", ".2E", "1.23E+00", ""), +end - (1.2345, "#", ".0f", "1.", ""), - (1.2345, "#", ".0e", "1.e+00", ""), - (1.2345, "#", ".0E", "1.E+00", ""), +@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" - (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 -# 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) -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) == "-0o0000000000000000000000144" + @test Printf.@sprintf("%#+24.20llo", -100) == " -0o00000000000000000144" + @test Printf.@sprintf("%#+18.21llX", -100) == "-0X000000000000000000064" + @test Printf.@sprintf("%#+20.24llo", -100) == "-0o000000000000000000000144" + @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) == "0o00144 " + @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) == "0o000001" + @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) == "0o777" + @test Printf.@sprintf("%#o", UInt(4294966785)) == "0o37777777001" + @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)) == "0o0" + @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) == " 0o777" + @test Printf.@sprintf("%#20o", UInt(4294966785)) == " 0o37777777001" + @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) == "0o000000000000000777" + @test Printf.@sprintf("%#020o", UInt(4294966785)) == "0o000000037777777001" + @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: 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" +# # printf +# # int +# @test (@sprintf "%d" typemax(Int64)) == "9223372036854775807" +# @test (@sprintf "%a" typemax(Int64)) == "0x7.fffffffffffffffp+60" +# @test (@sprintf "%A" typemax(Int64)) == "0X7.FFFFFFFFFFFFFFFP+60" + +# # pointers +# if Sys.WORD_SIZE == 64 +# @test (@sprintf "%20p" 0) == " 0x0000000000000000" +# @test (@sprintf "%-20p" 0) == "0x0000000000000000 " +# elseif Sys.WORD_SIZE == 32 +# @test (@sprintf "%20p" 0) == " 0x00000000" +# @test (@sprintf "%-20p" 0) == "0x00000000 " +# 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) +# end + +# # %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) +# 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" + +# # 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") + +# # reasonably complex +# @test (@sprintf "Test: %s%c%C%c%#-.0f." "t" 65 66 67 -42) == "Test: tABC-42.." + +# # 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) +# 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: 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' + +# # 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"