-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathEraUtils.pas
197 lines (154 loc) · 6.11 KB
/
EraUtils.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
unit EraUtils;
(*
Miscellaneous useful functions, used by other modules.
*)
(***) interface (***)
uses
SysUtils, Math,
Utils, Alg, StrLib,
Trans;
(* Converts integer to string, separating each three digit group by ThousandSeparator characer.
Specify IgnoreSmallNumbers to leave values <= 9999 as is. Uses game locale settings.
Example: 2138945 => "2 138 945" *)
function DecorateInt (Value: integer; IgnoreSmallNumbers: boolean = false): string;
(* Formats given positive or negative quantity to human-readable string with desired constraints on length
and maximal number of digits. Uses game locale settings and metric suffixes like "K", "M" and "G".
Examples:
FormatQuantity(1234567890, 10, 10) = '1234567890'
FormatQuantity(-1234567890, 6, 4) = '-1.23G'
FormatQuantity(123, 2, 4) = '0K'
FormatQuantity(1234567890, 6, 2) = '1G'
FormatQuantity(1234567890, 1, 2) = '9'
FormatQuantity(1234567890, 1, 0) = ''
*)
function FormatQuantity (Value, MaxLen, MaxDigits: integer): string;
(***) implementation (***)
function DecorateInt (Value: integer; IgnoreSmallNumbers: boolean = false): string;
const
GROUP_LEN = 3;
var
StrLen: integer;
IsNegative: boolean;
NumDelims: integer;
FinalStrLen: integer;
i, j: integer;
begin
result := SysUtils.IntToStr(Value);
if (Value >= 1000) or (Value < -1000) then begin
IsNegative := Value < 0;
StrLen := Length(result);
NumDelims := (StrLen - 1 - ord(IsNegative)) div GROUP_LEN;
FinalStrLen := StrLen + NumDelims;
SetLength(result, FinalStrLen);
j := FinalStrLen;
for i := 0 to StrLen - 1 - ord(IsNegative) do begin
if (i > 0) and (i mod 3 = 0) then begin
result[j] := Trans.LocalThousandSeparator;
Dec(j);
end;
result[j] := result[StrLen - i];
Dec(j);
end;
end; // .if
end; // .function DecorateInt
function FormatQuantity (Value, MaxLen, MaxDigits: integer): string;
const
METRIC_STEP_MULTIPLIER = 1000;
METRIC_STEP_DIGITS = 3;
MIN_FRACTIONAL_PART_LEN = 2;
METRIC_SUFFIX_LEN = 1;
DECIMAL_SEPARATOR_LEN = 1;
var
Str: string;
IsNegative: boolean;
SignLen: integer;
MetricSuffixInd: integer;
MetricSuffixMultiplier: integer;
IntPart: integer;
FractPart: integer;
NumIntPartDigits: integer;
NumFractPartDigits: integer;
MaxNumFractPartDigits: integer;
begin
result := '';
IsNegative := Value < 0;
if IsNegative then begin
result := '-';
end;
// Exit if no space for any sensibile result, '-' for single character negative number
if (MaxLen <= 0) or (MaxDigits <= 0) or ((MaxLen <= 1) and IsNegative) then begin
exit;
end;
SignLen := ord(IsNegative);
MaxDigits := Min(MaxLen - SignLen, MaxDigits);
Str := SysUtils.IntToStr(Value);
// Exit if simple int => str conversion fits all requirements
if (Length(result) <= MaxLen) and (Length(Str) - SignLen <= MaxDigits) then begin
result := Str;
exit;
end;
// No space for metric suffix like "K", just bound value to -9..+9 range.
if MaxDigits <= 1 then begin
result := SysUtils.IntToStr(Alg.ToRange(Value, -9, 9));
exit;
end;
(* From now there is a space for at least one digit and a metric suffix
Metric suffix will always be used from this point, with or without decimal point *)
// Convert number to positive
if Value < 0 then begin
Value := -Value;
if Value < 0 then begin
Value := High(integer);
end;
end;
// Exclude sign and metric suffix from constraints
Dec(MaxLen, METRIC_SUFFIX_LEN + SignLen);
MaxDigits := Min(MaxLen, MaxDigits);
// Determine order of magnitude
MetricSuffixMultiplier := METRIC_STEP_MULTIPLIER;
MetricSuffixInd := Low(Trans.MetricSuffixes);
while (MetricSuffixInd < High(Trans.MetricSuffixes)) and (Value >= (MetricSuffixMultiplier * METRIC_STEP_MULTIPLIER)) do begin
MetricSuffixMultiplier := MetricSuffixMultiplier * METRIC_STEP_MULTIPLIER;
Inc(MetricSuffixInd);
end;
// Calculate integer part
IntPart := Value div MetricSuffixMultiplier;
// If there is no space even for full integer part digits, produce value with zero int part and one step higher
// order of magnitude like "0K" for 700 or "0M" for 19 350
NumIntPartDigits := Alg.CountDigits(IntPart);
if NumIntPartDigits > MaxLen then begin
result := result + '0' + Trans.MetricSuffixes[MetricSuffixInd + 1];
exit;
end;
// Handle case, where is no space for fractional part like "0K", "36K" or "5M".
if MaxLen < (NumIntPartDigits + MIN_FRACTIONAL_PART_LEN) then begin
result := result + SysUtils.IntToStr(IntPart) + Trans.MetricSuffixes[MetricSuffixInd];
exit;
end;
// Allocate space for decimal separator
Dec(MaxLen, DECIMAL_SEPARATOR_LEN);
MaxDigits := Min(MaxLen, MaxDigits);
// Calculate fractional part
FractPart := Value mod MetricSuffixMultiplier;
(* Here we know, that there is definitely space for integer part and probably not full fractional part *)
NumFractPartDigits := METRIC_STEP_DIGITS * (MetricSuffixInd + 1);
MaxNumFractPartDigits := MaxDigits - NumIntPartDigits;
// Truncate fractional part
if NumFractPartDigits > MaxNumFractPartDigits then begin
FractPart := FractPart div Alg.IntPow10(NumFractPartDigits - MaxNumFractPartDigits);
end;
if FractPart <> 0 then begin
// Make fractional part leading zeroes prefix
NumFractPartDigits := Min(MaxNumFractPartDigits, NumFractPartDigits);
SetLength(Str, NumFractPartDigits - Alg.CountDigits(FractPart));
if Str <> '' then begin
System.FillChar(Str[1], Length(Str), '0');
end;
// Produce final result with integer part, fractional part and metric suffix
result := StrLib.Concat([result, SysUtils.IntToStr(IntPart), Trans.LocalDecimalSeparator, Str, SysUtils.IntToStr(FractPart), Trans.MetricSuffixes[MetricSuffixInd]]);
end else begin
// Got zero length fractional part, skip it
result := StrLib.Concat([result, SysUtils.IntToStr(IntPart), Trans.MetricSuffixes[MetricSuffixInd]]);
end;
end; // .function FormatQuantity
end.