Watch, Follow, &
Connect with Us
Public Report
Report From: Delphi-BCB/RTL/Delphi/Format + Float    [ Add a report in this area ]  
Report #:  67145   Status: Closed
FloatToText() malfunction in both ANSI and unicode flavors
Project:  Delphi Build #:  12.0.3170.16989
Version:    12.0 Submitted By:   Adam Wu
Report Type:  Basic functionality failure Date Reported:  9/24/2008 12:36:07 PM
Severity:    Critical / Show Stopper Last Updated: 3/20/2012 2:24:39 AM
Platform:    All versions Internal Tracking #:   265496
Resolution: Duplicate (Resolution Comments) Resolved in Build: : 12.0.3210.17555
Duplicate of:  None
Voting and Rating
Overall Rating: No Ratings Yet
0.00 out of 5
Total Votes: 4
Description
The ANSI flavored FloatToText() function contains code that treats UnicodeString as AnsiString, resulting in incorrect output.

Meanwhile, the unicode flavored FloatToText() functions delegates to the ANSI flavored functions, so its output is also incorrect.
Steps to Reproduce:
Non-ANSI:
1. Set the system's currency symbol string, thousand-separator and decimal-separator to some non-ANSI character;
2. The following line of code produces incorrect output.

Writeln(format('%m', [1234.56]));

The currency symbol string, thousand-separator and decimal-separator all appear corrupt.


ANSI:
1. Set the currency symbol string to TWO or MORE ASCII characters, such as '$$';
2. The following line of code also produces incorrect output.

Writeln(format('%m', [1234.56]));

Expected:
$$1,234.56

Output:
$ 1,234.56

(the blank is a #0)
Workarounds
No workaround.
If the system currency symbol string, thousand-separator or decimal-separator is non-ANSI character, you will need to write your own format routine.
Attachment
None
Comments

Adam Wu at 9/24/2008 6:12:44 PM -
First, I think the correct way of implementing FloatToText() is to get an unicode flavored function to work properly, and then implement the ANSI flavors by delegating to the unicode flavor and then down-convert.

Current implementation could cause problems because using the ANSI FloatToText (if working properly) first forces the unicode DecimalSeparator, ThousandSeparator and CurrencyString to down-convert, and then its AnsiString output is up-converted back to unicode.

This can cause the problem that: even with the same DecimalSeparator, ThousandSeparator and CurrencyString, merely changing the value of the RTL's DefaultSystemCodePage could make the unicode FloatToText() produce different output (but the unicode flavored functions are suppose to behave exactly the same regardless of the ANSI codepage).

Meanwhile, the current ANSI implementation of FloatToText() is also incorrect:
* It treats DecimalSeparator and ThousandSeparator as if they are AnsiChar, but they are not anymore;
* It "assumes" CurrencyString to be an AnsiString, and directly copies its content to the output, but in fact it is a UnicodeString, so it:
a. mis-reads the size and only copies half of the contents; and
b. did not down-convert, so the text copied is totally bogus.

Take the second one in the steps for example:
* The CurrencyString is unicode '$$'
* FloatToText() thinks it is AnsiString, so it looks like string: '$'#0'$'#0
* But becuase it really is a UnicodeString, so it's length field is 2
* So FloatToText() happily copies 2 bytes directly from a UnicodeString to its AnsiString output
* the output now looks like '$'#0+(digits)

Adam Wu at 9/24/2008 6:18:05 PM -
One way to fix the problem is to perform down-conversion for each of DecimalSeparator, ThousandSeparator, and CurrencyString at the beginning of the ANSI FloatToText(). But I think it will make the already complex function more chaotic.

Instead, why not just scratch off the current ANSI FloatToText(), and make a Unicode FloatToText(). In this way, there is no need to down-convert anything - no added complications. The ANSI FloatToText() can simply down convert the whole output from the Unicode flavor.

Tomohiro Takahashi at 9/25/2008 5:46:52 AM -
Could you please attach sample projects to verify your issue?

John Hansen at 9/25/2008 5:58:25 AM -
See my comment posted 5 minutes after yours.

procedure TForm1.Button2Click(Sender: TObject);
var
  x : Double;
  formatSettings : TFormatSettings;
begin
  x := -9231415123.0;
  GetLocaleFormatSettings(SysLocale.DefaultLCID, formatSettings);
  formatSettings.DecimalSeparator := '.';
  Caption := Format('Test %13.0f',[x], formatSettings);
end;

Also see this thread

https://forums.codegear.com/thread.jspa?threadID=3636&tstart=0

John Hansen at 9/25/2008 5:51:04 AM -
This problem can be triggered without using non-ANSI characters or even multiple characters in the DecimalSeparator, ThousandsSeparator, and CurrencyString variables.  All you need to do to trigger this bug is to use the thread-safe version of Format and pass in a TFormatSettings instance initialized with single character ANSI strings for all of the above.

As in this code:

procedure TForm1.Button2Click(Sender: TObject);
var
  x : Double;
  formatSettings : TFormatSettings;
begin
  x := -9231415123.0;
  GetLocaleFormatSettings(SysLocale.DefaultLCID, formatSettings);
  formatSettings.DecimalSeparator := '.';
  Caption := Format('Test %13.0f',[x], formatSettings);
end;

The caption will be set to 'Test -'

Server Response from: ETNACODE01