'Classes/ActiveBasic/Strings/SPrintF.ab

Namespace ActiveBasic
Namespace Strings

Namespace Detail

/*!
@brief	浮動小数点数を文字列化する低水準な関数。符号、指数、仮数に分けて出力。
@author	Egtra
@date	2007/09/18
@param[in]	x	文字列化する浮動小数点数
@param[out]	e	指数
@param[out]	sign	符号
@return 仮数
仮数は1の位から下へ17桁で、小数点を含まない。そのため、誤差を無視すればVal(仮数) * 10 ^ (e - 17) = Abs(x)が成り立つ。

xに無限大、無限小、非数を渡した場合の動作は未定義。
*/
Function FloatToChars(x As Double, ByRef e As Long, ByRef sign As Boolean) As String
	Imports System

	'0を弾く
	If x = 0 Then
		If GetQWord(VarPtr(x) As *QWord) And &h8000000000000000 Then
			sign = True
		Else
			sign = False
		End If

		e = 0
		FloatToChars = "00000000000000000"
		Exit Function
	End If

	'符号の判断（同時に符号を取り除く）
	If x < 0 Then
		sign = True
		x = -x
	Else

		sign = False
	End If

	'1e16 <= x < 1e17へ正規化
	'(元のx) = (正規化後のx) ^ (d - 17)である。
	Dim d = Math.Floor(Math.Log10(x)) As Long
	If d < 16 Then
		x *= ipow(10, +17 - d)
	ElseIf d > 16 Then
		x /= ipow(10, -17 + d)
	End If

	'補正
	While x < 1e16
		x *= 10
		d--
	Wend
	While x >= 1e17
		x /= 10
		d++
	Wend

	d--
	e = d

	FloatToChars = Str$(x As QWord)
End Function

/*!
@brief	書式化関数群で使用するフラグ。
@author	Egtra
@date	2007/09/18
*/
Const Enum FormatFlags
	'! 何も指定がない。
	None = &h0
	'! 符号、+。符号付変換[diAaEeFfGg]のとき、正の値でも符号を付ける。
	Sign = &h1
	/*! 空白、空白文字。
	符号付変換[diAaEeFfGg]のとき、正の値ならば符号分の空白を開ける。Signが立っているときには無視される。
	*/
	Blank = &h2
	/*! ゼロ、0。
	[diouXxAaEeFfGg]で、フィールドの空きを0で埋める。leftが立っているときには無視される。
	*/
	Zero = &h4
	'! 左揃え、-。フィールド内で左揃えにする。
	Left = &h8
	/*! 代替表記、#。
	<ul>
		<li>[OoXx]では、値が0でない場合、先頭に0、0xを付ける。</ul>
		<li>[AaEeFfGg]では、精度0でも小数点を付ける。</ul>
		<li>[Gg]では、それに加え、小数部末尾の0の省略を行わないようにする。</ul>
	</ul>
	*/
	Alt = &h10
	'! 大文字。使用するアルファベットを大文字にする。[aefgx]を[AEFGX]化する。
	Cap = &h20
End Enum

/*!
@brief	浮動小数点数をprintfの%e, %E相当の変換で文字列化する関数。
@author	Egtra
@date	2007/09/18
@param[in]	x	文字列化する浮動小数点数値。
@param[in]	d	精度。小数点以下の桁数。DWORD_MAXのとき、指定なしとして既定値6となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
@return xの文字列表現

@todo 末尾桁四捨五入
*/
Function FormatFloatE(x As Double, d As DWord, field As DWord, flags As FormatFlags) As String
	Dim sb = New System.Text.StringBuilder
	With sb
		If d = DWORD_MAX Then
			d = 6
		End If

		Dim e As Long, negative As Boolean
		Dim s = FloatToChars(x, e, negative)

		If negative Then
			.Append("-")
		Else
			If flags And Sign Then
				.Append("+")
			ElseIf flags And Blank Then
				.Append(" ")
			End If
		End If

		.Append(s[0])

		If (flags And Alt) Or d > 0 Then
			.Append(".")
			Dim outputLen = s.Length - 1
			If outputLen >= d Then
				.Append(s, 1, d)
			Else 'sで用意された桁が指定された精度より少ないとき
				.Append(s, 1, outputLen)
				.Append(&h30 As StrChar, d - outputLen) '足りない桁は0埋め
			End If
		End If

		If flags And Cap Then
			.Append("E")
		Else
			.Append("e")
		End If

		.Append(FormatIntegerD(e, 2, 0, Sign Or Zero))

		AdjustFieldWidth(sb, field, negative, flags)
	End With
	FormatFloatE = sb.ToString()
End Function

'! DWordの最大値4294967295の文字数 - 1。FormatIntegerU内で使用。
Const MaxSizeU = 9

/*!
@brief	符号無し整数をprintfの%u相当の変換で文字列化する関数。
@author	Egtra
@date	2007/09/18
@param[in]	x	文字列化する整数値。
@param[in]	d	精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
@return xの文字列表現
*/
Function FormatIntegerU(x As DWord, d As DWord, field As DWord, flags As FormatFlags) As String
	FormatIntegerU = FormatInteger(x, d, field, flags And (Not (Sign Or Blank)), 0)
End Function

/*!
DWordの最大値4294967295やLongの最大値2147483647、最小値-2147483648
（全て十進法）の符号部を除いた文字数 - 1。FormatIntegerU内で使用。
*/
Const MaxSizeD = 9

/*!
@brief	符号有り整数をprintfの%u相当の変換で文字列化する関数。
@author	Egtra
@date	2007/10/13
@param[in]	x	文字列化する整数値。
@param[in]	d	精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
@return xの文字列表現
*/
Function FormatIntegerD(x As Long, d As DWord, field As DWord, flags As FormatFlags) As String
	Dim dwX As DWord

	Dim signChar As StrChar
	If x < 0 Then
		dwX = (-x) As DWord
		signChar = Asc("-")
	Else
		dwX = x As DWord
		If flags And Sign Then
			signChar = Asc("+")
		ElseIf flags And Blank Then
			signChar = Asc(" ")
		End If
	End If

	FormatIntegerD = FormatInteger(dwX, d, field, flags, signChar)
End Function

/*!
@brief	整数をprintfの%d, %u相当の変換で文字列化する関数。
@author	Egtra
@date	2007/09/18
@param[in]	x	文字列化する整数値。
@param[in]	d	精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
@param[in]	signChar	符号部分の文字。\0なら存在しないとして扱われる。
@return xの文字列表現
*/
Function FormatInteger(x As DWord, d As DWord, field As DWord, flags As FormatFlags, signChar As StrChar) As String
	PreProcessFormatInteger(d, flags)

	Dim sb = New System.Text.StringBuilder
	With sb
		If signChar <> 0 Then
			.Append(signChar)
		End If

		Dim buf[MaxSizeU] As StrChar
		Dim i = MaxSizeU
		While x <> 0
			buf[i] = (x As Int64 Mod 10 + &h30) As StrChar 'Int64への型変換は#117対策
			x \= 10
			i--
		Wend
		
		Dim len = (MaxSizeU - i) As Long
		If len < d Then
			.Append(&h30 As StrChar, d - len)
		End If

		.Append(buf, i + 1, len)

		AdjustFieldWidth(sb, field, signChar <> 0, flags)
	End With
	FormatInteger = sb.ToString()
End Function


/*!
DWordの最大値の八進法表現37777777777の文字数 - 1。FormatIntegerO内で使用。
*/
Const MaxSizeO = 10

/*!
@brief	整数をprintfの%o相当の変換で文字列化する関数。
@author	Egtra
@date	2007/10/19
@param[in]	x	文字列化する整数値。
@param[in]	d	精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
@return xの文字列表現
*/
Function FormatIntegerO(x As DWord, d As DWord, field As DWord, flags As FormatFlags) As String
	PreProcessFormatInteger(d, flags)

	Dim sb = New System.Text.StringBuilder
	With sb
		Dim buf[MaxSizeO] As StrChar
		Dim i = MaxSizeO
		While x <> 0
			buf[i] = ((x And &o7) + &h30) As StrChar
			x >>= 3
			i--
		Wend
		
		Dim len = (MaxSizeO - i) As Long

		If flags And Alt Then
			.Append(&h30 As StrChar)
			len++
		End If

		If len < d Then
			.Append(&h30 As StrChar, d - len)
		End If

		.Append(buf, i + 1, len)

		AdjustFieldWidth(sb, field, False, flags And (Not (Sign Or Blank)))
	End With
	FormatIntegerO = sb.ToString()
End Function

/*!
DWordの最大値の十六進法表現ffffffffの文字数 - 1。FormatIntegerO内で使用。
*/
Const MaxSizeX = 7

/*!
@brief	整数をprintfの%x, %X相当の変換で文字列化する関数。
@author	Egtra
@date	2007/10/19
@param[in]	x	文字列化する整数値。
@param[in]	d	精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。
@param[in]	field	フィールド幅。
@param[in]	flags	書式フラグ。
@return xの文字列表現
*/
Function FormatIntegerX(x As DWord, d As DWord, field As DWord, flags As FormatFlags) As String
	PreProcessFormatInteger(d, flags)

	Dim sb = New System.Text.StringBuilder
	With sb
		Dim prefixLen = 0 As DWord
		If (flags And Alt) Then
			If x <> 0 Then
				.Append("0X")
				prefixLen = 2
			End If
		End If

		Dim buf[MaxSizeX] As StrChar
		Dim i = MaxSizeX
		While x <> 0
			buf[i] = _System_HexadecimalTable[x And &h0f]
			x >>= 4
			i--
		Wend
		
		Dim len = (MaxSizeX - i) As Long

		If len < d Then
			.Append(&h30 As StrChar, d - len)
		End If

		.Append(buf, i + 1, len)

		AdjustFieldWidth(sb, field, False, flags And (Not (Sign Or Blank)), prefixLen)
	End With
	FormatIntegerX = sb.ToString()
	
	If (flags And Cap) = 0 Then
		FormatIntegerX = FormatIntegerX.ToLower()
	End If
End Function

'! QWordの最大値18446744073709551615の文字数 - 1。FormatIntegerLU内で使用。
Const MaxSizeLU = 19

/*!
@brief 整数変換共通の前処理
@author	Egtra
@date	2007/10/19
@param[in, out] d	精度。DWORD_MAXで省略とみなされ、精度1になる。
@param[in, out] flags	フラグ。精度が指定されたとき、Zeroを消す。
*/
Sub PreProcessFormatInteger(ByRef d As DWord, ByRef flags As FormatFlags)
	If d = DWORD_MAX Then
		d = 1
	Else
		'精度が指定されているとき、ゼロフラグは無視される。
		'左揃えのときも無視されるが、それはAdjustFieldWidthが行ってくれる。
		flags And= Not Zero
	End If
End Sub

/*!
@brief 文字列をフィールド幅まで満たされるように空白などを挿入する。
@param [in,out]	sb	対象文字列
@param [in]	field	フィールド幅
@param [in] hasSign	符号を持っている（負の値か）か否か
@param [in]	flags	フラグ
@param [in]	prefixLen	（あれば）接頭辞の文字数。ゼロ埋めする際、この数だけ挿入位置を後ろにする。
*/
Sub AdjustFieldWidth(sb As System.Text.StringBuilder, field As DWord, hasSign As Boolean, flags As FormatFlags, prefixLen = 0 As DWord)
	With sb
		If .Length < field Then
			Dim embeddedSize = field - .Length
			If flags And Left Then
				.Append(&h20, embeddedSize)
			Else
				Dim insPos As Long
				If (flags And Zero) <> 0 Then
					If ((flags And Blank) Or (flags And Sign) Or hasSign) Then
						insPos++
					End If
					insPos += prefixLen
					.Insert(insPos, String$(embeddedSize, "0"))
				Else
					.Insert(insPos, String$(embeddedSize, " "))
				End If
			End If
		End If
	End With
End Sub

End Namespace 'Detail

End Namespace 'Strings
End Namespace 'ActiveBasic
