.NET Float Types
First lets examine the types of floats that .NET supports.
|Single||4||7||-3.4×1038 to +3.4×1038|
|Double||8||15-16||+-5.0×10324 to +-1.7×10308|
|Decimal||16||28-29||(-7.9×1028 to 7.9×1028) / (100 to 1028)|
Delphi Float Types
|Approximate Positive Range|
|Single||4||7-8||1.5×10-45 to 3.4×1038|
|Double||8||15-16||5.0×10-324 to 1.7×10308|
|Extended (x86)||10||10-20||3.4×10-4932 to 1.1×104932|
|Extended (x64)||8||15-16||5.0×10-324 to 1.7×10308|
Delphi also has a Currency type but it is a fixed decimal type, not a floating one. Since it has no matching equivalent in .NET, it is not used by CrossTalk.
The type used in Delphi depends on the type in .NET. When CrossTalk generates the .pas wrapper, the following mappings are used.
Single and double appear equivalent in Delphi and .NET, however because Delphi extends all float literals and most math operations to extended, in code they do not act exactly the same. Delphi’s most precise Extended is much less capable than .NET’s Decimal, so in such cases some precision may be lost.
Because internal representations may vary, and also because of varying precisions (such as Decimal and Extended), CrossTalk passes float values as strings. This ensures that the proper data is passed at all times and that precision loss is handled by the respective languages directly. Since Delphi treats all float literals as Extended and extends Single and Double to Extended for string operations, this can cause some unintended consequences.
xMyClass.SingleField := 1.22;
xSingle := xMyClass.SingleField;
Assert(xSingle = 1.22);
xMyClass is a .NET class, and SingleField is a .NET Single. One might expect that this code should execute without error, however the assertion will fail. The assertion fails because Single cannot store 1.22 accurately and stores it as 1.22000002861023. This is floating point error and well known by developers. However Delphi has an issue that extends it for comparison in the Assert, and thus tries to compare 1.22 to 1.22000002861023 which causes the Assert to fail. Float literals in Delphi cannot be type casted. The only solution is to implicitly cast by assigning to temporary variables.
s := 1.22;
// s becomes 1.22000002861023
Assert(s = single(1.22));
The above code is purely Delphi and does not involve CrossTalk, however you can see that the behavior is the same. Thus, the issue is a Delphi one not a CrossTalk or .NET issue.
.NET does not have this issue because float literals and string operations are typed. CrossTalk could have mapped all .NET float types to Extended instead, but this would cause other issues and cause CrossTalk code to act differently than Delphi code.
When dealing with floating points in Delphi, especially Single, the developer must be careful with float literals.