Τεχνικό Άρθρο

Bugs Delphi μόνο για Win64 από το hardening του HotPDF

Κώδικας Delphi Win64 μπορεί να αποτύχει εκεί όπου η ίδια πηγή τρέχει καθαρά σε Win32, και το HotPDF Delphi PDF component χτύπησε πέντε τέτοιες περιπτώσεις σε πρόσφατο πέρασμα hardening: το Power(10, N) που δένει στο overload Single, βρόχο while που διαβάζει βρώμικο TList.Count, όριο High(Int64) που στρογγυλοποιεί προς τα πάνω σε 2^63, κείμενο float 15 ψηφίων σε FPC, και asserts test που σταματούν τη μεταγλώττιση

Κανένα από αυτά δεν εμφανίζεται αν χτίζετε και τεστάρετε μόνο Win32, που είναι ακριβώς ο τρόπος που γλίστρησαν μέσα. Οι περιπτώσεις παρακάτω προέρχονται από τους importers SVG και XPS του HotPDF, τον renderer σελίδων του και τον reader JSON job του, και τα αριθμητικά αποτελέσματα που παραθέτονται αναπαράχθηκαν με μικρά probe προγράμματα χτισμένα για Win32 και Win64. Αν μετακινείτε codebase Delphi σε 64 bit, το καθένα αξίζει ένα grep

Γιατί το Power(10, 100) κάνει overflow μόνο σε Win64;

Σε Win64, το System.Math.Power(10, N) με ακέραια ορίσματα λύνεται στο overload Single, οπότε το αποτέλεσμα υπολογίζεται και επιστρέφεται σε single precision και οτιδήποτε πάνω από περίπου 3.4E38 κάνει overflow. Σε Win32 η ίδια κλήση δένει στο overload Extended και τρέχει στη FPU x87 με ακρίβεια 80 bit, οπότε το Power(10, 100) είναι απλώς 1E100

Το System.Math δηλώνει Power για Extended, Double και Single, συν matching οικογένεια IntPower που καλεί το Power όταν ο εκθέτης είναι ακέραιος αριθμός. Σε Win64, το Extended είναι μόνο alias για Double (SizeOf(Extended) = 8), και για δύο ακέραια ορίσματα ο μεταγλωττιστής διαλέγει την εκδοχή Single. Το αποκάλυμμα είναι η ακρίβεια, όχι μόνο το overflow: σε Win64, το Power(10, 20) επιστρέφει 1.0000000200408773E20, που είναι ακριβώς Single(1E20). Αποτέλεσμα Double θα τυπωνόταν ως 1E20. Είδαμε το ίδιο δέσιμο με κάθε μεταγλωττιστή Win64 που δοκιμάσαμε, από Delphi 10.3 έως έκδοση μεταγλωττιστή 37.0

Αυτό που συμβαίνει μετά εξαρτάται από τη μάσκα εξαιρέσεων κινητής υποδιαστολής. Το Delphi 12 και μεταγενέστερα κρύβουν όλες τις εξαιρέσεις κινητής υποστολής by default, οπότε το overflow είναι αθόρυβο: το Power(10, 100) επιστρέφει +Inf και το Power(10, -100) επιστρέφει 0. Το Delphi 11 και νωρίτερα αφήνουν το exOverflow αποκάλυπτο, και η ίδια κλήση πετά EOverflow. Εφαρμογές που θέτουν οι ίδιες τη μάσκα, και DLLs φορτωμένα σε τέτοιους hosts, παίρνουν όποια συμπεριφορά διάλεξε ο host, γι αυτό μια βιβλιοθήκη δεν μπορεί να υποθέσει ούτε το ένα ούτε το άλλο αποτέλεσμα

Αριθμητική παγίδα Win64 HotPDF όπου το System.Math Power με ακέραια ορίσματα δένει στο overload Single, οπότε το Power του 10 στη 20ή επιστρέφει 1.0000000200408773E20 αντί για 1E20 και το Power του 10 στη 100ή δίνει συν άπειρο όταν οι εξαιρέσεις είναι κρυμμένες ή EOverflow όταν δεν είναι
η απώλεια ακρίβειας είναι το χαρτί: αν δύναμη του δέκα επιστρέψει με θόρυβο Single κολλημένο, το λάθος overload κέρδισε — χτίστε την κλίμακα μόνος σας
uses
  System.SysUtils, System.Math;

procedure ShowPowerOverload;
var
  N: Integer;
  OldMask: TArithmeticExceptionMask;
begin
  N := 20;
  // Το Win32 τυπώνει 1E20· το Win64 τυπώνει 1.0000000200408773E20 (overload Single)
  Writeln(FloatToStrF(Power(10, N), ffGeneral, 17, 0));

  // Αναπαραγωγή ό,τι κάνει το Delphi 11, ή host με αυστηρές ρυθμίσεις FP
  OldMask := GetExceptionMask;
  SetExceptionMask(OldMask - [exOverflow, exInvalidOp]);
  try
    N := 100;
    Writeln(Power(10, N));       // Win64: EOverflow· Win32: 1E100
  finally
    SetExceptionMask(OldMask);
  end;
end;

Το αποκάλυμμα του exOverflow και του exInvalidOp για τη διάρκεια ενός test είναι ο φθηνότερος τρόπος να δείτε ό,τι βλέπει παλαιότερος μεταγλωττιστής ή αυστηρός host. Σε σύγχρονο μεταγλωττιστή με προεπιλεγμένες ρυθμίσεις το bug δεν καταρρέει, παράγει infinities και μηδενικά, και εκείνα είναι πολύ πιο δύσκολο να εντοπιστούν σε log test. Επαναφέρετε την προηγούμενη μάσκα σε finally: η μάσκα είναι κατάσταση ανά νήμα, και η υπόλοιπη εκτέλεση test κληρονομεί ό,τι αφήσετε πίσω

Πώς το overload έφτασε στην εισαγωγή SVG και XPS του HotPDF

Οι readers διαδρομών SVG και XPS του HotPDF μοιράζονται έναν σαρωτή αριθμών, και εκείνος ο σαρωτής κλιμακώσε τον mantissa με Power(10, Exponent) μόλις είχε διαβάσει εκθέτη. Οποιοδήποτε SVG περασμένο στο THotPDF.ImportSVGFormXObject (το σημείο εισόδου πίσω από την εισαγωγή SVG σε PDF ως επαναχρησιμοποιήσιμα form XObjects), και οποιαδήποτε γεωμετρία διαδρομής που χειρίζεται κατά την μετατροπή XPS και OpenXPS σε PDF, μπορούσε επομένως να ταΐσει συντεταγμένη όπως 1e100 ή 5e99 σε εκείνη την κλήση

Το v2.770.91 είχε ήδη περικόψει τον εκθέτη στα 100 και απέρριπτε τιμές που θα περνούσαν το 1E300, που φαινόταν αρκετό: το 1E100 δεν φτάνει ούτε κοντά στο όριο Double περίπου 1.8E308. Σε Win64 εξακολουθούσε να κάνει overflow, επειδή ο υπολογισμός δεν γινόταν ποτέ σε Double καθόλου. Από το v2.770.155 ο σαρωτής χτίζει ο ίδιος τη δύναμη του δέκα, και αριθμοί όπως 1e-100, ή μακρύς mantissa με μεγάλο αρνητικό εκθέτη, διαβάζονται στην πραγματική τους τιμή αντί να καταρρέουν σε 0

Μια ασφαλής δύναμη του δέκα για οριοθετημένους εκθέτες

Όταν ο εκθέτης είναι οριοθετημένος, η ασφαλέστερη δύναμη του δέκα είναι αυτή που χτίζετε μόνοι σας με πολλαπλασιασμό Double. Βρόχος το πολύ 100 πολλαπλασιασμών δεν κοστίζει τίποτα δίπλα στη σάρωση του κειμένου γύρω του, δεν παράγει ποτέ ενδιάμεσο μεγαλύτερο από την τελική κλίμακα, και συμπεριφέρεται πανομοιότυπα σε Win32, Win64 και Free Pascal

const
  MaxDecimalExponent = 100;

function TryScaleByPowerOf10(const Value: Double; Exponent: Integer;
  out Scaled: Double): Boolean;
var
  Scale: Double;
  I: Integer;
begin
  Scaled := 0;
  Result := False;
  if (Exponent < -MaxDecimalExponent) or (Exponent > MaxDecimalExponent) then
    Exit;
  // Απορρίψτε αποτελέσματα που θα έφευγαν από το εύρος Double
  if (Exponent > 0) and (Value <> 0) and
     (Log10(Abs(Value)) + Exponent > 300) then
    Exit;
  Scale := 1.0;
  for I := 1 to Abs(Exponent) do
    Scale := Scale * 10.0;       // δεν υπερβαίνει ποτέ το 1E100
  if Exponent >= 0 then
    Scaled := Value * Scale
  else
    Scaled := Value / Scale;     // διαίρεση: το 1E-100 δεν έχει ακριβές Double
  Result := True;
end;

Τρεις λεπτομέρειες κουβαλάνε το βάρος. Ο έλεγχος εύρους χρησιμοποιεί δύο συγκρίσεις αντί για Abs(Exponent) <= 100, επειδή το Abs(Low(Integer)) εξακολουθεί να είναι αρνητικό και θα πέρνασε κατευθείαν. Οι αρνητικοί εκθέτες διαιρούν με την κλίμακα αντί να πολλαπλασιάζουν με προϋπολογισμένο 1E-100, που δεν έχει ακριβές Double και θα πρόσθετε ένα βήμα στρογγυλοποίησης ακόμα. Και ο προέλεγχος Log10 απορρίπτει αποτελέσματα έξω από το εύρος Double πριν ο πολλαπλασιασμός προλάβει να κάνει overflow

Να είστε σαφείς για το τι παρατά ο βρόχος. Δυνάμεις του δέκα έως το 1E22 είναι ακριβείς σε Double· μετά από εκεί κάθε πολλαπλασιασμός στρογγυλοποιεί, και μετά από 100 από εκείνους η κλίμακα κάθεται λίγες μονάδες στην τελευταία θέση μακριά από το σωστά στρογγυλοποιημένο 1E100. Για συντεταγμένες σχεδίασης αυτό είναι αόρατο. Για μετατροπή γενικής χρήσης text-to-double που πρέπει να αναπαράγει κάθε τιμή bit προς bit, δεν είναι αρκετό, και θέλετε αλγόριθμο σωστά στρογγυλοποιημένης μετατροπής αντί

Όταν το dcc64 διαβάζει βρώμικο TList.Count σε βρόχο while

Παρατηρήσαμε τον μεταγλωττιστή Win64 (dcc64, έκδοση μεταγλωττιστή 37.0) να παράγει κώδικα για βρόχο while List.Count > Start do που διέγραφε από το τέλος της λίστας και συνέκρινε απέναντι σε προσωρινό stack αντί να ξαναδιαβάσει το Count. Η επανεγγραφή που το διόρθωσε ήταν βρόχος for ... downto, του οποίου τα όρια αποτιμώνται ακριβώς μία φορά εξ ορισμού

Ο βρόχος έφτασε στο v2.769.3, που δίδαξε στον κώδικα ομάδων διαφάνειας του renderer να κρατά ζωντανά soft masks που δημιουργήθηκαν μέσα από render δύο περασμάτων και να τα ελευθερώνει μετά. Ο καθαρισμός καθόταν σε block finally μετά από βρόχο for ενός ή δύο περασμάτων, μέσα στον βρόχο ανά tile. Αναχθεί στο σχήμα του, το πριν και το μετά μοιάζουν έτσι:

// Το σχήμα που είδαμε να μεταγλωττίζεται λάθος από το dcc64 (έκδοση μεταγλωττιστή 37.0)
procedure DropMasksWhile(Masks: TList; Start: Integer);
begin
  while Masks.Count > Start do
  begin
    TObject(Masks[Masks.Count - 1]).Free;
    Masks.Delete(Masks.Count - 1);
  end;
end;

// Αντικατάσταση: τα όρια αποτιμώνται μία φορά, κανένα προσωρινό να γίνει βρώμικο
procedure DropMasksFrom(Masks: TList; Start: Integer);
var
  Idx: NativeInt;               // το TList.Count είναι NativeInt από το Delphi 12
begin
  for Idx := Masks.Count - 1 downto Start do
  begin
    TObject(Masks[Idx]).Free;
    Masks.Delete(Idx);
  end;
end;

Στον παραγόμενο κώδικα Win64, το Count στη συνθήκη του βρόχου και το Count που διαβαζόταν μέσα στο σώμα μοιράζονταν μία θέση stack. Η συνθήκη συνέκρινε απέναντι σε εκείνη τη θέση στην είσοδο, πριν οτιδήποτε την είχε γράψει, και τίποτα δεν την ανανέωνε μετά το Delete. Όταν μια ομάδα δεν είχε δημιουργήσει δικά της soft masks, το σώμα έτρεχε ούτως ή άλλως και ζητούσε από κενή λίστα το item -1, οπότε σε builds 64 bit κάθε σελίδα που περιείχε τέτοια ομάδα διαφάνειας απέτυχε με EListError. Ο κώδικας Win32 για την ίδια πηγή ήταν σωστός, και το v2.770.1 αντικατέστησε τον βρόχο

Παγίδα codegen Win64 HotPDF στον καθαρισμό renderer: βρόχος while που ξαναδιάβαζε TList.Count μοιραζόταν μία θέση stack ανάμεσα στη συνθήκη και το σώμα, το dcc64 δεν την ανανέωνε ποτέ μετά το Delete, κενές ομάδες διαφάνειας ελευθέρωναν το item -1 και πετούσαν EListError, και η διόρθωση είναι βρόχος for downto του οποίου τα όρια αποτιμώνται μία φορά
το πρακτικό μάθημα κοστίζει λιγότερο από τη ρίζα: βρόχοι downto σταθερών ορίων δεν μπορούν να γίνουν βρώμικοι, και η δουλειά renderer δεν έχει τελειώσει μέχρι το dcc64 να τρέξει τη σουίτα

Δεν το έχουμε ανάγει σε ελάχιστη αναπαραγωγή, και μικρός αυτόνομος βρόχος όπως ο DropMasksWhile μπορεί κάλλιστα να μεταγλωττίζεται σωστά· τα περιβάλλοντα try/finally και οι εμφωλευμένοι βρόχοι φαίνεται να μετράνε. Μεταχειριστείτε το ως παραγωγή κώδικα που παρατηρήσαμε σε μία έκδοση μεταγλωττιστή, όχι ως γνωστό ελάττωμα κάθε μεταγλωττιστή Win64. Το πρακτικό μάθημα είναι φθηνότερο από τη ρίζα: βρόχος του οποίου η συνθήκη ξαναδιαβάζει το πλήθος μιας συλλογής ενώ το σώμα μικραίνει εκείνη τη συλλογή αξίζει επανεγγραφή ως σταθερών ορίων for ... downto, και αλλαγές renderer θέλουν πλήρη εκτέλεση test Win64, όχι μόνο Win32

Εντοπισμός crash που δείχνει μόνο βελτιστοποιημένο build Win64

Η αποτυχία αναπαράγονταν μόνο στο βελτιστοποιημένο build Win64, οπότε η τοποθεσία ήρθε από εργαλεία έξω από το IDE. Ένα μικρό probe πρόγραμμα κατέγραψε vectored exception handler με AddVectoredExceptionHandler, συνέλαβε τη στοίβα στην πρώτη εξαίρεση με RtlCaptureStackBackTrace, και μετέφρασε τις διευθύνσεις επιστροφής σε ονόματα συναρτήσεων χρησιμοποιώντας το αναλυτικό map αρχείο που γράφει ο linker με -GD. Η αποσυναρμολόγηση εκείνης της συνάρτησης έδειξε μετά τη σύγκριση να διαβάζει θέση stack, [rbp+0x298], που γράφονταν μόνο μέσα στο σώμα του βρόχου. Εκείνο είναι το επίπεδο αποδείξεων που θέλετε πριν κατηγορήσετε έναν μεταγλωττιστή, και πήρε λιγότερο χρόνο από το stepping μέσα από release build

Γιατί το High(Int64) δεν είναι ασφαλές άνω όριο για Double;

Ένα Double δεν μπορεί να αναπαραστήσει το High(Int64): η μετατροπή του 9223372036854775807 σε Double στρογγυλοποιεί προς τα πάνω σε ακριβώς 2^63, ένα πάνω από τον μεγαλύτερο Int64. Σε Win64 εκείνη η μετατροπή συμβαίνει μέσα στην ίδια τη σύγκριση, οπότε το D <= High(Int64) είναι True για D = 2^63, και το Round ή Trunc που ακολουθεί κάνει overflow

Το Win32 κρύβει αυτό για τον ίδιο λόγο που έκρυβε το πρόβλημα Power. Η σύγκριση τρέχει σε ακρίβεια Extended 80 bit με mantissa 64 bit, όπου το High(Int64) είναι ακριβές και το 2^63 συγκρίνεται σωστά ως μεγαλύτερο. Το Win64 δεν έχει ευρύτερο τύπο να πέσει πίσω. Η εκτός εύρους μετατροπή δεν είναι όμορφη ούτε αυτή: στα Win64 test μας το Round(2^63) επέστρεφε Low(Int64), αθόρυβη αντιστροφή πρόσημου, είτε το exInvalidOp ήταν κρυμμένο είτε όχι. Το Win32 επιστρέφει την ίδια τιμή όταν είναι κρυμμένο και πετά EInvalidOp όταν είναι αποκαλυμμένο

Παγίδα ορίου Int64 HotPDF: ένα Double δεν μπορεί να αναπαραστήσει το High(Int64), οπότε σύγκριση Win64 μετατρέπει το όριο πάνω σε 2^63, D ίσο με 2^63 περνά τον έλεγχο και το Round επιστρέφει αθόρυβα Low(Int64), ενώ το Win32 συγκρίνει σε Extended 80 bit όπου το όριο είναι ακριβές και η ίδια σύγκριση είναι False
μία μετατροπή είναι όλο το bug: το όριο στρογγυλοποιεί πάνω στην ίδια την τιμή που αποκλείετε, οπότε γράψτε το ταβάνι ως literal με αυστηρό less-than
ΈκφρασηWin32Win64
Power(10, N), N = 201E201.0000000200408773E20
Power(10, 100), εξαιρέσεις κρυμμένες (προεπιλογή Delphi 12+)1E100+Inf
Power(10, 100), exOverflow αποκαλυμμένο1E100EOverflow
D <= High(Int64), D = 2^63FalseTrue
Round(2^63), exInvalidOp αποκαλυμμένοEInvalidOpLow(Int64)

Το HotPDF συνάντησε αυτό στον reader JSON πίσω από τις τιμές document job του. Το JSON δεν θέτει όριο εύρους σε αριθμούς, και ο παλιός serializer γύρνε κάθε τιμή με Frac(Value) = 0 σε ακέραιο με Round, οπότε ένα απολύτως νόμιμο 1e19 γινόταν είτε λάθος ακέραιος είτε exception, ανάλογα με τη μάσκα. Από το v2.770.169 ακέραιος αριθμός γράφεται ως ακέραιος μόνο όταν χωράει σε Int64, όλα τα άλλα κρατούν το κινητού κειμένου τους, και οι getters ακεραίων επιστρέφουν την προεπιλογή του καλούντος για εκτός εύρους τιμές αντί για τυλιγμένη

const
  TwoPow63 = 9223372036854775808.0;   // 2^63, ακριβές σε Double και Extended

function TryDoubleToInt64(const Value: Double; out R: Int64): Boolean;
begin
  R := 0;
  Result := not IsNan(Value) and not IsInfinite(Value) and
    (Frac(Value) = 0) and (Value >= -TwoPow63) and (Value < TwoPow63);
  if Result then
    R := Trunc(Value);
end;

function JsonNumberText(const Value: Double): string;
var
  R: Int64;
begin
  // Οι καλούντες απορρίπτουν πρώτα NaN και infinities: το JSON δεν έχει ορθογραφία για εκείνα
  if TryDoubleToInt64(Value, R) then
    Result := IntToStr(R)
  else
  begin
{$IFDEF FPC}
    Str(Value:24, Result);       // το ffGeneral FPC Win64 σταματά στα 15 ψηφία
    Result := Trim(Result);
{$ELSE}
    Result := FloatToStrF(Value, ffGeneral, 17, 0, TFormatSettings.Invariant);
{$ENDIF}
  end;
end;

Το άνω όριο είναι το literal 9223372036854775808.0 με αυστηρό <. Εκείνη η σταθερά είναι το 2^63, ακριβές σε Double και Extended, οπότε η σύγκριση σημαίνει το ίδιο πράγμα σε κάθε πλατφόρμα. Το κάτω όριο μπορεί να χρησιμοποιήσει >= επειδή το -2^63 είναι ακριβώς Low(Int64). Το test IsNan και IsInfinite πρώτα, με αποτίμηση short-circuit, κρατά NaN και infinities μακριά από Frac και τις συγκρίσεις, που μπορούν να πετάξουν EInvalidOp όταν ο host το έχει αποκαλύψει

Πόσα ψηφία δίνει πραγματικά η μετατροπή float-σε-κείμενο σε Win64;

Λιγότερα από όσα ζητάτε, σε δύο μεταγλωττιστές στους τρεις. Το FloatToStrF(Value, ffGeneral, 17, 0) του Free Pascal 3.3.1 σε Win64 σταματά στα 15 σημαντικά ψηφία, οπότε το 1/3 επιστρέφει ως 0.333333333333333 και δύο διαφορετικές τιμές Double μπορούν να σειριοποιηθούν σε πανομοιότυπο κείμενο. Το Str(Value:24, Text) ακολουθούμενο από Trim παράγει 17 σημαντικά ψηφία σε επιστημονική σημείωση, 3.3333333333333331E-001 για την ίδια τιμή, και γράφει πάντα τελεία ως δεκαδικό διαχωριστικό ανεξαρτήτως locale. Αν το HotPDF σε FPC είναι μέρος του build matrix σας, οι σημειώσεις υποστήριξης HotPDF Free Pascal και Lazarus Win64 καλύπτουν τις υπόλοιπες διαφορές πλατφόρμας

Το Delphi δέχεται το αίτημα 17 ψηφίων, αλλά οι δύο στόχοι Delphi εξακολουθούν να διαφωνούν στην έξοδο: το FloatToStrF(0.1, ffGeneral, 17, 0) δίνει 0.10000000000000001 σε Win32 και 0.1 σε Win64. Το RTL Win64 μπορεί επίσης να εισάγει σφάλμα στρογγυλοποίησης τελευταίου ψηφίου τόσο κατά τη μορφοποίηση όσο και κατά την ανάλυση, οπότε περισσότερα ψηφία στενεύουν το κενό χωρίς να εγγυώνται ότι κάθε bit μοτίβο Double επιβιώνει από text round trip. Η τεκμηρίωση του HotPDF δεν κάνει τέτοια υπόσχεση, και η δική σας δεν πρέπει ούτε αυτή εκτός αν παραδίδετε δικό σας σωστά στρογγυλοποιημένο formatter και parser. Περάστε TFormatSettings.Invariant, ή αντικαταστήστε τον διαχωριστικό μόνοι σας σε παλαιότερες εκδόσεις Delphi, ώστε γερμανικό ή γαλλικό locale να μην γράψει κόμμα μέσα σε JSON

Γιατί το Assert.AreEqual σταματά να μεταγλωττίζεται σε Win64;

Το Assert.AreEqual(3, Length(Arr)) πάνω σε δυναμικό πίνακα μεταγλωττίζεται για Win32 και αποτυγχάνει για Win64 με E2532, "Couldn't infer generic type argument from different argument types", επειδή το Length δυναμικού πίνακα επιστρέφει NativeInt σε Win64. Με literal Integer στη μία πλευρά και 64-bit NativeInt στην άλλη, το γενικό Assert.AreEqual<T> του DUnitX δεν μπορεί να κατασταλάξει σε μοναδικό T, και το build σταματά

Το TList.Count πυροδοτεί το ίδιο σφάλμα από το Delphi 12, όπου η ιδιότητα έγινε NativeInt· το Delphi 11 εξακολουθεί να το δηλώνει ως Integer. Το Length string επιστρέφει Integer και στις δύο πλατφόρμες και δεν επηρεάζεται, που είναι ο λόγος το σφάλμα εμφανίζεται σε μερικά test units και όχι σε άλλα. Γράψτε το όρισμα τύπου ρητά, Assert.AreEqual<NativeInt>(3, Length(Arr)), και μεταγλωττίστε το test project με dcc64 πριν το commit. Σουίτα που χτίζει μόνο για Win32 δεν θα σας πει ότι το build Win64 της είναι σπασμένο μέχρι κάποιος άλλος να το δοκιμάσει

Λίστα ελέγχου μεταφοράς Win64 για αριθμητικό κώδικα Delphi

  • Ψάξτε για κλήσεις Power( και IntPower( με ακέραια ορίσματα· περάστε τιμές τύπου Double ή χτίστε οριοθετημένες δυνάμεις του δέκα μόνοι σας
  • Τρέξτε αριθμητικά test τουλάχιστον μία φορά με exOverflow και exInvalidOp αφαιρεμένα μέσω SetExceptionMask, και σε Win32 και σε Win64
  • Γράψτε το άνω όριο Int64 ως < 9223372036854775808.0, ποτέ <= High(Int64), και απορρίψτε NaN και infinities πριν από οποιαδήποτε σύγκριση
  • Μην μετατρέψετε parsed αριθμό σε Int64 μόνο επειδή το Frac είναι 0· αριθμοί JSON μπορούν να είναι πολύ μεγαλύτεροι
  • Ξαναγράψτε βρόχους while που ξαναδιαβάζουν Count ενώ διαγράφουν items ως βρόχους σταθερών ορίων for ... downto
  • Σε FPC Win64, χρησιμοποιήστε Str(Value:24, Text) όταν θέλετε πάνω από 15 σημαντικά ψηφία
  • Χρησιμοποιήστε Assert.AreEqual<NativeInt> για asserts Length και Count, και μεταγλωττίστε τα tests με dcc64 πριν το commit
  • Μετά από κάθε αλλαγή σε parser ή renderer, τρέξτε την πλήρη regression σουίτα σε Win32 και Win64, όχι μόνο σε έναν από τους δύο

Οι διορθώσεις πλευράς βιβλιοθήκης που περιγράφησαν εδώ είναι όλες στο HotPDF από το v2.770.169, οπότε η εισαγωγή SVG, η μετατροπή XPS, το rendering διαφάνειας και ο χειρισμός JSON job συμπεριφέρονται πλέον το ίδιο σε Win64 όπως σε Win32. Αν παράγετε ή επεξεργάζεστε PDF αρχεία από Delphi ή C++Builder και για τις δύο πλατφόρμες, η σελίδα HotPDF Delphi PDF component έχει τις λήψεις και την πλήρη λίστα χαρακτηριστικών