ich möchte eine für UnicodeString optimierte Version von StrToInt mit euch teilen.
Hintergrund
Aktuell arbeite ich mit einer Bibltiothek die ausschließlich mit UTF-16 kodierten Zeichenketten arbeitet und dort viele Zahlen, Datums- und Zeitangaben als UTF-16 kodierte Zeichenketten verwendet werden.
Verwendet man die Funtion StrToInt aus der Unit SysUtils, müssen die Zeichenketten zuerst konvertiert werden. Zwar spielt die Ziel-Code-Page in der Regel keine Rolle (da die betroffenen Zeichen oft den gleichen Zahlwert haben), doch benötigt diese Konvertierung Rechenleistung.
Da alle Zeichen, die für die Darstellung von Zahlen benötigt werden, in der Basic Multilingual Plane liegen, ändert sich nichts an den Algorithmen. Es müssen nur andere
Features und Einschränkungen
- Funktioniert wie StrToInt - verwendet aber UnicodeString
- Bei Fehlern (ungültige Zahl, außerhalb des Gültigkeitsbereichs) wird EConvertError ausgelöst
- Binärzahlen
- Dezimalzahlen
- Oktalzahlen
- Hexadezimalzahlen
- Grundlegende Richtigkeit mit FPCUnit getestet (falls Fehler enthalten sind, bitte den Eingabewert mitteilen)
- Es gibt nur eine LongInt-Version; für andere muss der Ergebnis-Typ und die Eingrenzung in ParseOctal geändert werden.
- Es gibt nur die StrToInt-Version (kein TryStrToInt, StrToIntDefoder die System-Funktion val ohne Exceptions)
Laufzeitverhalten
Getestet wurde mit langen und kurzen Binär-, Oktal-, Dezimal- und Hexadezimalzahlen. Die Funktion wurde für jeden Wert sehr oft* aufgerufen und mit GetTickCount64 die Zeit über alle Iterationen gemessen. Daher gebe ich keine absoluten Zahlen an, sondern nur das Verhältnis zum Aufruf von StrToInt aus der SysUtils Unit (Benötigte Zeit Unicode-Optimiert / Benötigte Zeit SysUtils.StrToInt).
Code: Alles auswählen
Format| Zeit | Wert
------+------+----------------------------------
DEC | 45 % | 2147483647
DEC | 38 % | 1234
DEC | 33 % | 1
BIN | 39 % | %11111111111111111111111111111111
OCT | 25 % | &37777777777
HEX | 20 % | $12345678
HEX | 8 % | $FF
* = Getestet hatte ich sowohl mit $FFFF * 1000 Aufrufen als auch mit $FFFF * 5000 Aufrufen; wobei das Verhältnis zwischen den Funktionen unabhängig vom Faktor 1000 oder 5000 ist.
Lizenz
Es wird die selbe Lizenz wie bei der LCL oder RTL verwendet: die LGPL mit Linking Exception.
Quelltext
Code: Alles auswählen
{ UnicodeString (UTF-16) optimized StrToInt
Copyright (C) 2017 Simon Ameis <simon.ameis@web.de>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version with the following modification:
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent modules,and
to copy and distribute the resulting executable under terms of your choice,
provided that you also meet, for each linked independent module, the terms
and conditions of the license of that module. An independent module is a
module which is not derived from or based on this library. If you modify
this library, you may extend this exception to your version of the library,
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
for more details.
You should have received a copy of the GNU Library General Public License
along with this library; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Version:
2017-01-28 v1 Initial Publishing
2017-01-28 v2 Added missing POP compiler instruction
}
// enables inlining for embedded procedures
// this save some time as calling functions is very expensive
{$DEFINE IntToStr_SUB_INLINE}
uses SysUtils, RtlConsts;
function StrToInt(const aStr: UnicodeString): LongInt;
var
StartIdx: SizeInt;
v: LongInt;
procedure ParseBinary;{$IFDEF IntToStr_SUB_INLINE} inline; {$ENDIF}
var
i: SizeInt;
begin
if Length(aStr) > BitSizeOf(Result) + 1 then
raise EConvertError.CreateFmt('The binary string "%s" is too long for LongInt.', [aStr]);
if Length(aStr) < 2 then // must be at least % + hex char
raise EConvertError.CreateFmt(SInvalidInteger, [aStr]);
for i := StartIdx to Length(aStr) do
begin
Result := Result shl 1;
case aStr[i] of
UnicodeChar('0'): ; // 0 already shifted in; but use this for validity check
UnicodeChar('1'): Result := Result or $1;
else
raise EConvertError.CreateFmt(SInvalidInteger, [aStr]);
end;
end;
end;
procedure ParseOctal;{$IFDEF IntToStr_SUB_INLINE} inline; {$ENDIF}
var
i: SizeInt;
begin
if Length(aStr) < 2 then // must be at least & + hex char
raise EConvertError.CreateFmt(SInvalidInteger, [aStr]);
if Length(aStr) > 12 then // & char + 11 octal chars = maximum value
raise EConvertError.CreateFmt(SInvalidInteger, [aStr]);
// &37777777777 = maximum value
// &40000000000 requires 33 bits
if (Length(aStr) = 12) and (aStr[2] > UnicodeChar('3')) then
raise EConvertError.CreateFmt(SInvalidInteger, [aStr]);
for i := StartIdx to Length(aStr) do
begin
if (aStr[i] >= UnicodeChar('0')) and (aStr[i] <= UnicodeChar('7')) then
v := Word(aStr[i]) - Word(UnicodeChar('0'))
else
raise EConvertError.CreateFmt(SInvalidInteger, [aStr]);
Result := (Result shl 3) or (Byte(v) and &7);
end;
end;
// Using two different procedure for positive and negative decimal numbers
// is about 25 percent faster as there are less (jump!) instructions within
// the loop
procedure ParseDecimalPositive;{$IFDEF IntToStr_SUB_INLINE} inline; {$ENDIF}
var
i: SizeInt;
begin
try
for i := StartIdx to Length(aStr) do
begin
{$PUSH}
{$OverflowChecks ON}
Result := Result * 10;
{$POP}
if (aStr[i] >= UnicodeChar('0')) and (aStr[i] <= UnicodeChar('9')) then
begin
v := Word(aStr[i]) - $0030;
{$PUSH}
{$OverflowChecks ON}
Result := Result + v;
{$POP}
end
else
raise EConvertError.CreateFmt('Invalid number character at index %d.', [i]);
end;
except
// only EConvertError may be raised, thus catch EIntOverflow
on EIntOverflow do
raise EConvertError.CreateFmt(SInvalidInteger, [aStr]);
end;
end;
procedure ParseDecimalNegative;{$IFDEF IntToStr_SUB_INLINE} inline; {$ENDIF}
var
i: SizeInt;
begin
try
for i := StartIdx to Length(aStr) do
begin
{$PUSH}
{$OverflowChecks ON}
Result := Result * 10;
{$POP}
if (aStr[i] >= UnicodeChar('0')) and (aStr[i] <= UnicodeChar('9')) then
begin
v := Word(aStr[i]) - $0030;
{$PUSH}
{$OverflowChecks ON}
Result := Result - v;
{$POP}
end
else
raise EConvertError.CreateFmt('Invalid number character at index %d.', [i]);
end;
except
on EIntOverflow do
raise EConvertError.CreateFmt(SInvalidInteger, [aStr]);
end;
end;
procedure ParseHexadecimal; {$IFDEF IntToStr_SUB_INLINE} inline; {$ENDIF}
var
i: SizeInt;
begin
if Length(aStr) > SizeOf(Result) * 2 + 1 then
raise EConvertError.CreateFmt('The hexadecimal string "%s" is too long for LongInt.', [aStr]);
if Length(aStr) < 2 then // must be at least $ + hex char
raise EConvertError.CreateFmt(SInvalidInteger, [aStr]);
for i := StartIdx to Length(aStr) do
begin
if (aStr[i] >= UnicodeChar('0')) and (aStr[i] <= UnicodeChar('9')) then
v := Word(aStr[i]) - Word(UnicodeChar('0'))
else
if (aStr[i] >= UnicodeChar('A')) and (aStr[i] <= UnicodeChar('F')) then
v := Word(aStr[i]) - Word(UnicodeChar('A')) + 10
else
if (aStr[i] >= UnicodeChar('a')) and (aStr[i] <= UnicodeChar('f')) then
v := Word(aStr[i]) - Word(UnicodeChar('a')) + 10
else
raise EConvertError.CreateFmt(SInvalidInteger, [aStr]);
Result := (Result shl 4) or (Byte(v) and $F);
end;
end;
begin
if Length(aStr) = 0 then
raise EConvertError.CreateFmt(SInvalidInteger, ['']);
Result := 0;
StartIdx := 2;
case aStr[1] of
UnicodeChar('%'): begin ParseBinary; end;
UnicodeChar('&'): begin ParseOctal; end;
UnicodeChar('-'): begin ParseDecimalNegative end;
UnicodeChar('+'): begin ParseDecimalPositive; end;
UnicodeChar('$'): begin ParseHexadecimal; end;
else
StartIdx := 1;
ParseDecimalPositive;
end;
end;