'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 = FormatIntegerLU((x As Int64) As QWord, 17, 0, None)
End Function
/*!
@brief 書式化関数群で使用するフラグ。
@author Egtra
@date 2007/09/18
*/
Const Enum FormatFlags
'! 何も指定がない。
None = &h0
/*!
符号、+。符号付変換[diAaEeFfGg]のとき、正の値でも符号を付ける。
AdjustFieldWidthの仕様から、Format関数郡内からAdjustFieldWidthにかけて、
単に数値が符号付である(負の値である)ことを示す意味でも用いられる。
*/
Sign = &h1
/*! 空白、空白文字。
符号付変換[diAaEeFfGg]のとき、正の値ならば符号分の空白を開ける。Signが立っているときには無視される。
*/
Blank = &h2
/*! ゼロ、0。
[diouXxAaEeFfGg]で、フィールドの空きを0で埋める。leftが立っているときには無視される。
*/
Zero = &h4
'! 左揃え、-。フィールド内で左揃えにする。
Left = &h8
/*! 代替表記、#。
- [OoXx]では、値が0でない場合、先頭に0、0xを付ける。
[AaEeFfGg]では、精度0でも小数点を付ける。
[Gg]では、それに加え、小数部末尾の0の省略を行わないようにする。
*/
Alt = &h10
'! 大文字。使用するアルファベットを大文字にする。[aefgx]を[AEFGX]化する。
Cap = &h20
/*!
内部処理用に予約。
@note Minusとして使用されている。
*/
Reserved = &h80000000
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
If d = DWORD_MAX Then
d = 6
End If
Dim e As Long, negative As Boolean
Dim s = FloatToChars(x, e, negative)
Dim sb = New System.Text.StringBuilder
With sb
AppendSign(sb, negative, flags)
.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, flags)
End With
FormatFloatE = sb.ToString()
End Function
/*!
@brief 浮動小数点数をprintfの%f(小数形式、十進法)相当の変換で文字列化する関数。
@author Egtra
@date 2007/10/23
@param[in] x 文字列化する浮動小数点数値。
@param[in] precision 精度。小数点以下の桁数。DWORD_MAXのとき、指定なしとして既定値6となる。
@param[in] field フィールド幅。
@param[in] flags 書式フラグ。
@return xの文字列表現
*/
Function FormatFloatF(x As Double, precision As DWord, field As DWord, flags As FormatFlags) As String
If precision = DWORD_MAX Then
precision = 6
End If
Dim e As Long, negative As Boolean
Dim s = FloatToChars(x, e, negative)
Dim sb = New System.Text.StringBuilder
With sb
AppendSign(sb, negative, flags)
Dim intPartLen = e + 1
Dim outputDigit = 0 As DWord
If intPartLen >= 17 Then
'有効桁が全て整数部に収まる場合
.Append(s)
.Append(&h30 As StrChar, intPartLen - 17)
outputDigit = 17
ElseIf intPartLen > 0 Then
'有効桁の一部が整数部にかかる場合
.Append(s, 0, intPartLen)
outputDigit = intPartLen
Else
'有効桁が全く整数部にかからない場合
.Append(&h30 As StrChar)
End If
If precision > 0 Or (flags And Alt) Then
.Append(".")
Dim lastDigit = s.Length - outputDigit
If lastDigit >= precision Then '変換して得られた文字列の桁数が精度以上ある場合
Dim zeroDigit = 0
If intPartLen < 0 Then
'1.23e-4 = 0.000123のように指数が負のため小数点以下に0が続く場合
zeroDigit = System.Math.Min(-intPartLen As DWord, precision)
.Append(&h30 As StrChar, zeroDigit As Long)
End If
.Append(s, outputDigit, (precision - zeroDigit) As Long)
Else
.Append(s, outputDigit, lastDigit)
.Append(&h30 As StrChar, (precision - lastDigit) As Long) '残りの桁は0埋め
End If
End If
AdjustFieldWidth(sb, field, flags)
End With
FormatFloatF = sb.ToString()
End Function
/*!
@brief 先頭に符号もしくはその分の空白を出力する。FormatFloat用。
@author Egtra
@date 2007/10/23
@param[in, out] sb 出力先
@param[in] negative 符号
@param[in, out] flags フラグ。negative = Trueなら、Signを立てて返す。
*/
Sub AppendSign(sb As System.Text.StringBuilder, negative As Boolean, ByRef flags As FormatFlags)
With sb
If negative Then
.Append("-")
flags Or= Sign
Else
If flags And Sign Then
.Append("+")
ElseIf flags And Blank Then
.Append(" ")
End If
End If
End With
End Sub
/*!
@brief DWordの最大値4294967295の文字数 - 1。FormatIntegerU内で使用。
@author Egtra
@date 2007/09/18
*/
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
Return FormatIntegerEx(TraitsIntegerDU, x, d, field, flags And (Not (Sign Or Blank)))
End Function
/*!
@brief FormatIntegerUのQWord版
@author Egtra
@date 2007/10/26
*/
Function FormatIntegerLU(x As QWord, d As DWord, field As DWord, flags As FormatFlags) As String
Return FormatIntegerEx(TraitsIntegerLDU, x, d, field, flags And (Not (Sign Or Blank)))
End Function
/*!
@brief 符号有り整数をprintfの%d(十進法表現)相当の変換で文字列化する関数。
@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 unsignedX As DWord
If x < 0 Then
unsignedX = (-x) As DWord
flags Or= Minus
Else
unsignedX = x As DWord
End If
Return FormatIntegerEx(TraitsIntegerDU, unsignedX, d, field, flags)
End Function
/*!
@brief FormatIntegerDのInt64版
@author Egtra
@date 2007/10/26
*/
Function FormatIntegerLD(x As Int64, d As DWord, field As DWord, flags As FormatFlags) As String
Dim unsignedX As QWord
If x < 0 Then
unsignedX = (-x) As QWord
flags Or= Minus
Else
unsignedX = x As QWord
End If
Return FormatIntegerEx(TraitsIntegerLDU, unsignedX, d, field, flags)
End Function
/*!
@author Egtra
@date 2007/10/26
*/
Dim TraitsIntegerDU As IntegerConvertTraits
With TraitsIntegerDU
.Convert = AddressOf(IntegerDU_Convert)
.Prefix = AddressOf(IntegerDU_Prefix)
.MaxSize = MaxSizeU
End With
/*!
@author Egtra
@date 2007/10/26
*/
Dim TraitsIntegerLDU As IntegerConvertTraits
With TraitsIntegerLDU
.Convert = AddressOf(IntegerLDU_Convert)
.Prefix = AddressOf(IntegerDU_Prefix)
.MaxSize = MaxSizeLU
End With
/*!
@brief 負数を表すフラグ。FormatIntegerD, LDからIntegerDU_Prefixまでの内部処理用。
@author Egtra
@date 2007/10/26
*/
Const Minus = Reserved
/*!
@author Egtra
@date 2007/10/26
*/
Function IntegerDU_Convert(buf As *StrChar, xq As QWord, flags As FormatFlags) As DWord
Dim x = xq As DWord
Dim i = MaxSizeU
While x <> 0
buf[i] = (x As Int64 Mod 10 + &h30) As StrChar 'Int64への型変換は#117対策
x \= 10
i--
Wend
Return i
End Function
/*!
@brief IntegerDU_ConvertのQWord版
@author Egtra
@date 2007/10/26
@bug #117のため、現在Int64の最大値を超える値を正しく処理できない。
*/
Function IntegerLDU_Convert(buf As *StrChar, x As QWord, flags As FormatFlags) As DWord
Dim i = MaxSizeLU
While x <> 0
buf[i] = (x As Int64 Mod 10 + &h30) As StrChar 'Int64への型変換は#117対策
x \= 10
i--
Wend
Return i
End Function
/*!
@author Egtra
@date 2007/10/26
*/
Function IntegerDU_Prefix(x As QWord, flags As FormatFlags) As String
If flags And Minus Then
IntegerDU_Prefix = "-"
ElseIf flags And Sign Then
IntegerDU_Prefix = "+"
ElseIf flags And Blank Then
IntegerDU_Prefix = " "
End If
End Function
/*!
@brief DWordの最大値の八進法表現37777777777の文字数 - 1 + 1。IntegerO_Convert内で使用。
@author Egtra
@date 2007/10/19
上の式で1を加えているのは、八進接頭辞の分。
*/
Const MaxSizeO = 11
/*!
@brief QWordの最大値の八進法表現1777777777777777777777の文字数 - 1 + 1。IntegerO_Convert内で使用。
@author Egtra
@date 2007/10/26
上の式で1を加えているのは、八進接頭辞の分。
*/
Const MaxSizeLO = 22
/*!
@author Egtra
@date 2007/10/22
*/
Dim TraitsIntegerO As IntegerConvertTraits
With TraitsIntegerO
.Convert = AddressOf(IntegerO_Convert)
.Prefix = AddressOf(IntegerO_Prefix)
.MaxSize = MaxSizeO
End With
/*!
@author Egtra
@date 2007/10/26
*/
Dim TraitsIntegerLO As IntegerConvertTraits
With TraitsIntegerLO
.Convert = AddressOf(IntegerLO_Convert)
.Prefix = AddressOf(IntegerO_Prefix)
.MaxSize = MaxSizeLO
End With
/*!
@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
Return FormatIntegerEx(TraitsIntegerO, x, d, field, flags)
End Function
/*!
@brief FormatIntegerOのQWord版。
@author Egtra
@date 2007/10/26
*/
Function FormatIntegerLO(x As QWord, d As DWord, field As DWord, flags As FormatFlags) As String
Return FormatIntegerEx(TraitsIntegerLO, x, d, field, flags)
End Function
/*!
@author Egtra
@date 2007/10/22
*/
Function IntegerO_Convert(buf As *StrChar, xq As QWord, flags As FormatFlags) As DWord
Dim x = xq As DWord
Dim i = MaxSizeO
While x <> 0
buf[i] = ((x And &o7) + &h30) As StrChar
x >>= 3
i--
Wend
If flags And Alt Then
buf[i] = &h30
i--
End If
Return i
End Function
/*!
@brief IntegerO_ConvertのQWord版。
@author Egtra
@date 2007/10/26
*/
Function IntegerLO_Convert(buf As *StrChar, x As QWord, flags As FormatFlags) As DWord
Dim i = MaxSizeLO
While x <> 0
buf[i] = ((x And &o7) + &h30) As StrChar
x >>= 3
i--
Wend
If flags And Alt Then
buf[i] = &h30
i--
End If
Return i
End Function
/*!
@author Egtra
@date 2007/10/22
@note #フラグ (Alt)の処理は、IntegerO/LO_Convert内で行うので、ここで処理することはない。
*/
Function IntegerO_Prefix(x As QWord, flags As FormatFlags) As String
End Function
/*!
@brief DWordの最大値の十六進法表現ffffffffの文字数 - 1。FormatIntegerO内で使用。
@author Egtra
@date 2007/10/24
*/
Const MaxSizeX = 7
/*!
@brief QWordの最大値の十六進法表現ffffffffffffffffの文字数 - 1。FormatIntegerO内で使用。
@author Egtra
@date 2007/10/26
*/
Const MaxSizeLX = 15
/*!
@author Egtra
@date 2007/10/24
*/
Dim TraitsIntegerX As IntegerConvertTraits
With TraitsIntegerX
.Convert = AddressOf(IntegerX_Convert)
.Prefix = AddressOf(IntegerX_Prefix)
.MaxSize = MaxSizeX
End With
/*!
@author Egtra
@date 2007/10/26
*/
Dim TraitsIntegerLX As IntegerConvertTraits
With TraitsIntegerLX
.Convert = AddressOf(IntegerLX_Convert)
.Prefix = AddressOf(IntegerX_Prefix)
.MaxSize = MaxSizeLX
End With
/*!
@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
Return FormatIntegerEx(TraitsIntegerX, x, d, field, flags)
End Function
/*!
@brief FormatIntegerXのQWord版。
@author Egtra
@date 2007/10/22
*/
Function FormatIntegerLX(x As QWord, d As DWord, field As DWord, flags As FormatFlags) As String
Return FormatIntegerEx(TraitsIntegerLX, x, d, field, flags)
End Function
/*!
@author Egtra
@date 2007/10/22
*/
Function IntegerX_Convert(buf As *StrChar, xq As QWord, flags As FormatFlags) As DWord
Dim i = MaxSizeX
Dim x = xq As DWord
While x <> 0
buf[i] = _System_HexadecimalTable[x And &h0f]
x >>= 4
i--
Wend
Return i
End Function
/*!
@brief IntegerX_ConvertのQWord版。
@author Egtra
@date 2007/10/22
*/
Function IntegerLX_Convert(buf As *StrChar, x As QWord, flags As FormatFlags) As DWord
Dim i = MaxSizeLX
While x <> 0
buf[i] = _System_HexadecimalTable[x And &h0f]
x >>= 4
i--
Wend
Return i
End Function
/*!
@author Egtra
@date 2007/10/24
*/
Function IntegerX_Prefix(x As QWord, flags As FormatFlags) As String
If flags And Alt Then
If x <> 0 Then
IntegerX_Prefix = "0X"
End If
End If
End Function
/*!
@brief FormatIntegerExへ渡す変換特性を表す構造体型。
@author Egtra
@date 2007/10/22
*/
Type IntegerConvertTraits
'!変換を行う関数へのポインタ。
Convert As *Function(buf As *StrChar, x As QWord, flags As FormatFlags) As DWord
'!接頭辞を取得する関数へのポインタ。
Prefix As *Function(x As QWord, flags As FormatFlags) As String
'!必要なバッファの大きさ。
MaxSize As DWord
End Type
/*!
@brief 整数変換全てを行う関数。これを雛形とし、形式毎の差異はIntegerConvertTraitsで表現する。
@author Egtra
@date 2007/10/22
@param[in] tr 特性情報。
@param[in] x 変換元の数値。
@param[in] d 精度。ここでは最低限出力する桁数。
@param[in] field フィールド幅。
@param[in] flags フラグ。
*/
Function FormatIntegerEx(ByRef tr As IntegerConvertTraits, x As QWord, d As DWord, field As DWord, flags As FormatFlags) As String
If d = DWORD_MAX Then
d = 1
Else
'精度が指定されているとき、ゼロフラグは無視される。
'仕様上、左揃えのときも無視されるが、それはAdjustFieldWidthが行ってくれる。
flags And= Not Zero
End If
Dim sb = New System.Text.StringBuilder
With sb
Dim prefixFunc = tr.Prefix
Dim prefix = prefixFunc(x, flags)
sb.Append(prefix)
Dim prefixLen = 0 As DWord
If String.IsNullOrEmpty(prefix) = False Then
prefixLen = prefix.Length As DWord
End If
Dim buf = GC_malloc_atomic((tr.MaxSize + 1) * SizeOf (StrChar)) As *StrChar
Dim convertFunc = tr.Convert
Dim bufStartPos = convertFunc(buf, x, flags)
Dim len = (tr.MaxSize - bufStartPos) As Long
If len < 0 Then
Debug
End If
If len < d Then
.Append(&h30 As StrChar, d - len)
End If
.Append(buf, bufStartPos + 1, len)
AdjustFieldWidth(sb, field, flags And (Not (Sign Or Blank)), prefixLen)
End With
FormatIntegerEx = sb.ToString()
If (flags And Cap) = 0 Then
FormatIntegerEx = FormatIntegerEx.ToLower()
End If
End Function
/*!
@brief QWordの最大値18446744073709551615の文字数 - 1。FormatIntegerLU内で使用。
@author Egtra
@date 2007/09/18
*/
Const MaxSizeLU = 19
/*!
@brief 文字列をフィールド幅まで満たされるように空白などを挿入する。
@author Egtra
@date 2007/10/13
@param[in,out] sb 対象文字列
@param[in] field フィールド幅
@param[in] hasSign 符号を持っている(負の値か)か否か
@param[in] flags フラグ
@param[in] prefixLen (あれば)接頭辞の文字数。ゼロ埋めする際、この数だけ挿入位置を後ろにする。
sbが"-1"のように負符号を持っている場合は、呼出元でSignフラグ(またはBlank)を立てること。
*/
Sub AdjustFieldWidth(sb As System.Text.StringBuilder, field As DWord, 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) 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