About Excel exporting using Winnovative Excel Library for .NET

In search for a good managed Excel exporting tool I’ve come to test a Excel library from Winnovative. I’ll follow the same procedure as in my previous Excel-related post about CarlosAG component for easier comparison..

What does it do ?

Create xls/xlsx documents, including containing formulas, images, comments, charts, etc. Operate with CSVs, load data from DataTables, etc. See the feature list yourself.

What do I need ?

Support for data types, unicode, basic formatting, formulas, cell merge, column width – the same things as in my previous excel-component test.

What did I get ?

Functionally - everything I wanted. Actually for not having licence I got one extra sheet reminding me that fact, but that is not going to be the issue for production environments.

The file was decent xlsx-file, I also briefly tested getting the older xls_2003-file and it seemed to be working as well. No warning on open.. just such file:

image

How did I do it ?

using System;
using System.Globalization;
using System.Threading;
using Winnovative.ExcelLib;

public class WinnovativeExcelTest
{
private const int DummyRows = 15;

public static void Build()
{
//prepare sheet
ExcelWorkbook book = new ExcelWorkbook(ExcelWorkbookFormat.Xlsx_2007);
ExcelWorksheet sheet = book.Worksheets[0];
sheet.Name = "Sample";

//create data
int currentRow = 1;
sheet["A1:D1"].Merge(); //wow, we can use excel style range instead of coordinates.
sheet["A1"].Text = "demonstrate merge";

BuildHeaderRow(sheet, ++currentRow);
for(int i = 1; i <= DummyRows; i++)
{
BuildDataRow(sheet, i, ++currentRow);
}
BuildSummaryRow(sheet, ++currentRow);

//I don't like the default it offered so I'm setting it myself:
sheet[currentRow - DummyRows, 4, currentRow +1, 4].Style.Number.NumberFormatString
= Thread.CurrentThread.CurrentCulture.DateTimeFormat.ShortDatePattern;

//corrigate column widths:
sheet.AutofitColumns();

//output result
book.Save("Winnovative_result.xlsx");
}

private static void BuildHeaderRow(ExcelWorksheet sheet, int toRow)
{
sheet[toRow, 1].Text = "index";
sheet[toRow, 2].Text = "1/index";
sheet[toRow, 3].Text = "name";
sheet[toRow, 4].Text = "datetime";
sheet[toRow, 1, toRow, 4].Style.Font.Bold = true;
}

private static void BuildDataRow(ExcelWorksheet sheet, int dataIndex, int toRow)
{
//generate dummy data row using dataIndex for values:
sheet[toRow, 1].Value = dataIndex;
sheet[toRow, 2].Value = (1.0/dataIndex); //decimal is not recognized as number, by doc floats are.
sheet[toRow, 3].Value = "Ilus õõvaöö nr " + dataIndex.ToString(CultureInfo.InvariantCulture);
sheet[toRow, 4].Value = DateTime.Today.AddDays(dataIndex);
}

private static void BuildSummaryRow(ExcelWorksheet sheet, int toRow)
{
sheet[toRow, 1].Formula = String.Format("=AVERAGE(A{0}:A{1})", toRow-DummyRows, toRow-1);
sheet[toRow, 2].Formula = String.Format("=SUM(B{0}:B{1})", toRow-DummyRows, toRow-1);
sheet[toRow, 4].Formula = String.Format("=D{0}+1", toRow-1);
}
}


Some subjective numbers:

  • ~64 lines of code (including empty lines, usings, etc)
  • runs approx 55-60 ms on E8300 @2.83GHz, 4GB RAM.

What do I think ?

I may be a bit biased toward them because I’ve used their PDF libraries before, but it DID feel good to code this API, clean and simple.

The good

  • Fully managed code, .Net fw 2.0
  • Single dll deployment (~3.5 MB)
  • Seems to do everything I need + more
    • by documentation, did not try them all.
    • images, comments, hidden columns, DataTable to sheet, etc ..
  • Good sleek API
    • you don’t get drowned in methods but you can do a lot with the ones given.
    • very friendly and flexible cell referencing tools
      • indexed (row 1, col 1) or “A1”-style
      • the same for cell ranges
    • dynamically tracking the cells to use:  no manual creation of 100 empty lines to add a single value to 100th row.
    • applying styles on the fly to cells/ranges. no manual messing with prepared styles unless you want to.
  • Automatic cell value type detection
    • no need to format datetimes (unless you want to) or think about decimal separator.
  • Good documentation
    • sample project in package.
    • xmldoc alongside dll
    • helpful user guide (I had two questions during this testing, I found both answers with less than a minute from the manual.
  • Respectable company
    • For all the bugs I’ve seen in their products, I’ve seen also the release to fix it.
    • there is tech support.
  • Fully functional evaluation possible
    • Can test everything before buying. The only addition is one extra sheet in file reminding that you cannot go live without a licence.
    • The same dll as the licenced version. Adding a licence 2 days before release will not give dll-problems.

The bad

  • It’s not free
    • prices start from 350$. Too expensive to buy for personal use, but for enterprises this is good value for the money.
    • licence key should be stored within system.
  • It’s not open source
    • no legal way to know or modify what the component does..
  • .NEt requirements
    • it requires System.Web which means it cannot be used with .NET FW 4 client profile. This may be important in some scnearios.
    • it requires .NET 2.0, but i don’t think this is a problem. FW 1.1 applications should have died out a long time ago.
  • Noticable dll size
    • 3.5MB is not too heavy and we have tons of RAM and HDD but smaller size would be even nicer.
  • Could be slower than some alternatives
    • I didn’t do proper performance-testing but for this ultrasmall sample file it was ca 25% slower than CarlosAG.
  • Decimals are not automatically recognized as number types.
    • floats can be used instead or kept as general type (default). Not sure if this is a excel limitation or Winnovative component optimization.

About Excel exporting in CarlosAG way..

I was looking for managed code component for excel exporting and happened to test a freeware component named CarlosAG Excel XML Writer Library. The following contains my thoughts about the pros and cons..

What does it do?

“This library allows you to generate Excel Workbooks using XML, it is built 100% in C# and does not requires Excel installed at all to generate the files. It exposes a simple object model to generate the XML Workbooks.
It supports several features for generating Excel Workbooks including:

  • Formatting
  • Alignment
  • Formulas
  • Pivot Tables
  • and more... “ (From carlosag.net)

This is nice, I suppose, but these keywords are not good enough for me. I want to try it out and see what the developer sees and what exactly will end up in the file.

what do I need ?

The excel functionality I want is quite simple actually:

  • to support data types
  • to support unicode

I’ll also try out fancier things which I foresee myself using at some point:

  • basic formatting (background colors, bold, borders would be nice)
  • formulas – it can avoid tracking values across rows/cols in code
  • cell merge
  • setting column width

For testing I’ll create a excel file programmatically, trying to use these features..

What did I get ?

I could get my needs fulfilled with some minor struggling. The output file is xml, schema seems similar to the one described in:
http://en.wikipedia.org/wiki/Microsoft_Office_2003_XML_formats
.

This is the output in excel with no modifications:

image

How did I do it?

This is the code I used. The chosen methods may not be optimal but it does indicate the API you could end up using..

using System;
using System.Globalization;
using CarlosAg.ExcelXmlWriter;


public class CarlosAGTest
{
private const int DummyRows = 15;
private const string HeaderStyleName = "headerStyle";
private const string DateStyleName = "dateStyle";

public static void Build()
{
//prepare sheet
Workbook book = new Workbook();
Worksheet sheet = book.Worksheets.Add("Sample");

//delcare header style
WorksheetStyle style = book.Styles.Add(HeaderStyleName);
style.Font.Bold = true;

WorksheetStyle dateStyle = book.Styles.Add(DateStyleName);
dateStyle.NumberFormat = "Short Date";

//create data
sheet.Table.Rows.Add(BuildMergedRow());
sheet.Table.Rows.Add(BuildHeaderRow());
for(int i = 1; i <= DummyRows; i++)
{
sheet.Table.Rows.Add(BuildDataRow(i));
}
sheet.Table.Rows.Add(BuildSummaryRow());

//set column width:
sheet.Table.Columns.Add().Width = 5;
sheet.Table.Columns.Add();
sheet.Table.Columns.Add().Width = 150;

//output result
book.Save("CarlosAG_result.xls");
}

private static WorksheetRow BuildMergedRow()
{
WorksheetRow row = new WorksheetRow();
var lastCell = row.Cells.Add("demonstrate merge");
lastCell.MergeAcross = 3;
return row;
}

private static WorksheetRow BuildHeaderRow()
{
WorksheetRow row = new WorksheetRow();
//demonstrate overloads of adding cell contents.
row.Cells.Add(new WorksheetCell("index") { StyleID = HeaderStyleName });
row.Cells.Add("(1/index)", DataType.String, HeaderStyleName);
row.Cells.Add(new WorksheetCell("name", DataType.String, HeaderStyleName));
row.Cells.Add("dateTime", DataType.String, HeaderStyleName);
return row;
}

private static WorksheetRow BuildDataRow(int rowIndex)
{
//generate dummy data row using rowIndex for values:
WorksheetRow row = new WorksheetRow();
row.Cells.Add(rowIndex.ToString(CultureInfo.InvariantCulture), DataType.Number, null);
row.Cells.Add((1.0M/rowIndex).ToString(CultureInfo.InvariantCulture), DataType.Number, null);
row.Cells.Add("Ilus õõvaöö nr " + rowIndex.ToString(CultureInfo.InvariantCulture), DataType.String, null);
row.Cells.Add(DateTime.Today.AddDays(rowIndex).ToString("s"), DataType.DateTime, DateStyleName);
return row;
}

private static WorksheetRow BuildSummaryRow()
{
WorksheetRow row = new WorksheetRow();
var averageCell = row.Cells.Add();
averageCell.Formula = "=AVERAGE(R[-" + DummyRows + "]C:R[-1]C)";

var sumcell = row.Cells.Add();
sumcell.Formula = "=SUM(R[-" + DummyRows + "]C:R[-1]C)";

row.Cells.Add();

var plusOneCell = row.Cells.Add(null, DataType.DateTime, DateStyleName);
plusOneCell.Formula = "= R[-1]C + 1";
return row;
}
}


A few subjective numbers:

  • The file was generated with approx 40-50 ms on E8300 @2.83GHz, 4GB RAM.
  • 88 lines of code (including empty lines, using-statements, etc).

What do I think ?

If you are low budget then CarlosAG is certainly a good tool to use. Far from ideal, though.

The good:

  • It did everything I wanted
  • Free. no cost, use as you want.
  • Fully managed code, requires just .net FW 1.1
  • Simple deplopyment  - just a single ~100k dll.
  • Reasonably simple API.

The bad:

  • no source code
    • just the one from reflector
  • no documentation
    • no xmldoc file for dll, chm in website was broken and failed to show a single page of it.
  • no automatic value formatting
    • cell values have to be set as strings, the of API user has to manually format contents to be saved into XML. What format is expected is not obvious.For example: Datetimes, decimal separators, etc. This could lead to hard to debug problems.. and it did.
  • Probable lack of support
    • Developer seems to have made this library for fun, this could mean loss of quality and support. For example, last realease was in 2005. It is foreseeable that problems have to be solved by yourself, digging knee-deep into reflector-generated code. For code written in .net FW 1.1, this could be ugly.
  • API lacks some comfort-features

    • You have to create lots of rows and cells and columns. To add something to n-th row you have to ensure all rows up andincluding n-th row are created. same for cells, and columns.

  • No native xls/xlsx output.

    • just xml with <?mso-application progid='Excel.Sheet'?>

  • The formula input language in unknown to me
    • I'd prefer to write exactly what I would write in excel. No doubt the given langugage could be more helpful but it adds to the learning curve. For example I’m using "=SUM(R[-30] C:R[-1]C)" instead of “=SUM(A1:A30)”.

Links

Official site:
http://www.carlosag.net/Tools/ExcelXmlWriter/

Sample for exporting dataset contents:
http://www.sitepoint.com/blogs/2006/08/22/making-excel-the-carlosag-way/

Sellest kust testimiseks IE5.5-8 leida..

Veebidisaini tehes on tavaline see, et kõik ilusad plaanid ebaõnnestuvad, sest mingis dokumendis on nõue, et “peab toetama Internet Explorer 7+” vms. Ja siis sa istud oma IE8 peal ja mõtiskled, kellel/millises masinas võiks vastavat brauserit leida. Ohh, tüütu!

Pisut abi on ehk utiliidist IETester, mis võimaldab mingi täpsusega emuleerida vanemaid IE versioone:
 image
Kui olla pisut paranoiline, siis lõpptestiks ma seda brauserit ei usalda, kuid arenduse-aegseks kiirtestiks küll.

Windows 7 kasutajatel on üsna lihtne viis ka IE6 saamiseks. Nimelt on Windows XP Mode koosseisus tolle aja brauser. Kui sul on XP Mode juba olemas, on sul ka päris IE6 juba olemas.

About optional parameters in c# when targeting .net 2.0

One of the coolest thing about Visual Studio 2010 is C# 4.0 and its optional/named parameters feature. I was wondering if I can get rid of those hard to maintain overloads in my older assemblies which still need to target the CLR2.

Just to be sure I made a quick proof of concept:

Code for test program

   1: using System;
   2:  
   3: namespace OptionalParameterTest
   4: {
   5:     class Program
   6:     {
   7:         static void Main(string[] args)
   8:         {
   9:             TestMethod();
  10:             TestMethod("only string set");
  11:             TestMethod("both set", 12345);
  12:             TestMethod(someint: 12345, someString: "named parameters, both set");
  13:             TestMethod(someString: "named parameters, only string set");
  14:         }
  15:  
  16:         private static void TestMethod(String someString = "defaultStringValue", int someint = 9)
  17:         {
  18:             Console.WriteLine("Called with: " + someString + " " + someint);
  19:         }
  20:     }
  21: }

This is ofc expected to result with such output:

   1: C:\WM\Varia\OptionalParameterTest\bin\Debug>OptionalParameterTest.exe
   2: Called with: defaultStringValue 9
   3: Called with: only string set 9
   4: Called with: both set 12345
   5: Called with: named parameters, both set 12345
   6: Called with: named parameters, only string set 9

So, the target FW is 4.0

Compile, run, works as expected. Corflags agree that the executable indeed requires version 4:

   1: Version   : v4.0.30319
   2: CLR Header: 2.5
   3: PE        : PE32
   4: CorFlags  : 3
   5: ILONLY    : 1
   6: 32BIT     : 1
   7: Signed    : 0

Lets note that we have such line in app.config added by VS itself:

   1: <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup>

When target FW is 2.0

Changed target fw of the VS project, compile, run.. result was as expected and corflags agrees that we now require older CLR:

   1: Version   : v2.0.50727
   2: CLR Header: 2.5
   3: PE        : PE32
   4: CorFlags  : 3
   5: ILONLY    : 1
   6: 32BIT     : 1
   7: Signed    : 0

Btw, VS automatically adjusted my app.config to require correct version as shown below. Nice touch!

   1: <startup><supportedRuntime version="v2.0.50727"/></startup></configuration>

But lets be paranoid..

.. and really make sure that the correct runtime version corflags reports was used when running the application. For this I deployed both solutions to an box without .NET 4, just .NET 3.5.

  • Assembly targeted against CLR2.0 ran just fine. (This is what makes me happy!)
  • As expected the assembly targeted against CLR4.0 refused to run stating quite descriptevely that it needed .Net FW 4.0.

Note:

When app.config lies abiout it’s required runtime version, like stating CLR 2.0 instead of 4.0 then you get much uglier and much less descriptive “crash, debug?”-message. Be careful about that and do use the supported runtime  version setting in configuration file instead of just assuming there will be the right one present and used.

Conclusion

Optional and named parameters are C# compiler-level features. Which means the runtime does not care if you use them or not – compiler is just better at understanding which method’s call to use and inserts parameter initialization where neccessary. The generated MSIL is still CLR 2.0. The compiler just helps us to achieve more by writing less. One more reason to switch to VS2010 even when still targeting older frameworks for whatever reasons. Yei!

Sellest, et CSV jaoks on Excel saamatu..

Minu jaoks täiesti ootamatult on MS Excel 2007 andmete CSV-formaati konvertimine puudulik. Probleemideks on:

  • csv eksport ei toeta unicode’i.
    • Ma isegi ei tea, mis kodeeringus ta faili väljastab. Ühtegi seadistust ma küll ei näinud ei salvestamise ajal ega optionite all.
    • salvestamise on ka failitüübi valik “Unicode text”, mis VIST on Tab-separated CSV.. aga mida see täpselt teeb, ei tea. Ei viitsinud testida tekstisiseid reavahetusi. tabulaatorimärke ja mähkimise tähtede korral käitumist..
  • csv eraldajat ega väärtuste mähkimise tähte ei saa ise määrata.
    • Küll aga on mitu erinevad CSV analoogi save-dialoogi all failitüüpide loetelus, näiteks “CSV (Comma delimited)” jms suvalises järjestuses 28 võimaliku “failitüübi” seas.

Selgus, et OpenOffice Calc 3.2 seevastu küsib viisakalt CSV faili salvestamisel kõiki kolme asja:

  • Character set – sh unicode
  • Field delimiter
  • Text delimiter
  • .. muid asju küsib ka aga neist ma praegu ei hoolinud.

Täpselt see, mis vaja ja täpselt seal kus vaja. Hea MS, vaata ja õpi, milline peab olema CSV ekspordi dialoog!

Sellest, miks IE7 raadionupud ei ole valitavad..

Koostasin leheküljel dünaamiliselt raadionuppe. IE8, FF korral töötas raadionuppude lisamine järgmise skriptiga probleemideta:

var selectorTemplate = 'whatever content <input type="radio" class="Selector"/> whatever content';


var selector = container.append($(selectorTemplate)).find('.Selector:last');


if(someCondition) { selector.attr('checked', 'checked'); }
selector.attr("name", this.configuration.RadiobuttonName);
selector.bind("change", this.selectionChanged);


ja siis tuli info, et IE6-7 korral ei ole antud raadionupud valitavad. Klikkimise peale lihtsalt ei juhtu midagi. Lisaks sellele, et event ei käivitu, ei muutu ka raadionupu checked staatus. WTF?

Pärast vaevalist IE6-7-ga testmasina otsinguid ja pea vastu klaverit peksmist kohtasin google’s vihjet, et vana IE ei lubavat raadionuppe valida kui neil ei ole name-atribuuti. Kiire test näitas et selline totter väike muudatus nagu “name=’böö’” on see mis eraldab töötavat skripti mittetöötavast:

var selectorTemplate = 'whatever  content <input type="radio" class="Selector" name=”any”/> whatever content';

Kas JS progemine on nõme või on see hoopis väga nõme ? Ohjah.

BTW: IE6 saab kõige lihtsamalt Windows7 all XPMode vastavast virtuaalmasinast ;)

Sellest, et WCF proxy saab using-blokiga halvasti läbi..

On saanud harjumuseks IDisposable tüüpe kasutades kohmaka try-finally bloki asemel ilusat using-süntaksit kasutada. Paraku selgus, et Windows Communication Foundation ei ole selle suhtes väga sõbralikult meelestatud juhul kui teenuse tarbimise käigus tekib mõni viga WCF kanaliga. Näiteks:

using (var usingClient = new FtpProxyServiceClient())
{
    usingClient.RecieveFile(target); //this call throws an exception
}

tagastab võrdlemisi kasutu veateate:

System.ServiceModel.CommunicationObjectFaultedException: The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.

Server stack trace:
   at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)

Exception rethrown at [0]:
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at System.ServiceModel.ICommunicationObject.Close(TimeSpan timeout)
   at System.ServiceModel.ClientBase`1.System.ServiceModel.ICommunicationObject.Close(TimeSpan timeout)
   at System.ServiceModel.ClientBase`1.Close()
   at System.ServiceModel.ClientBase`1.System.IDisposable.Dispose()
   at SomeNameSpace.UIConsole.Program.Test() in W:\SomePath\UIConsole\Program.cs:line 32

Pöörakem tähelepanu, et vea põhjuse või tekkekoha kohta ei ole jälgegi, vaid viga näib tulevat hoopis Dispose() väljakutsumisel. Selgub, et WCF proxy Close() ebaõnnestub kui eelneva vea tõttu on ühendus juba maha võetud. Tulemuseks on UUS viga ja vana unustatakse hoopis.

Lahenduseks on tagasipöördumine try-finally lahenduse poole ja Abort() kasutamine:

var tryClient = new FtpProxyServiceClient();
Boolean succeeded = false;
try
{
    tryClient.RecieveFile(target); //this call throws an exception
    tryClient.Close();
    succeeded = true;
}
finally
{
    if (!succeeded) { tryClient.Abort(); }
}

Ehk vea järel kutsume Close() asemel välja Abort() meetodi. Selle peale saame lõpuks ometi teada ka algse vea põhjuse, asjaosalise stackframe'i jne:

System.ServiceModel.CommunicationException: The maximum message size quota for incoming messages (20) has been exceeded. To increase the quota, use the MaxReceivedMessageSize property on the appropriate binding element. ---> System.ServiceModel.QuotaExceededException: The maximum message size quota for incoming messages (20) has been exceeded. To increase the quota, use the MaxReceivedMessageSize property on the appropriate binding element.
   --- End of inner exception stack trace ---

[ - skipped some nonrelevant stacktrace entries - ]

   at SomeNameSpace.Ftp.IFtpProxyService.RecieveFile(Uri targetFile)
   at SomeNameSpace.Ftp.FtpProxyServiceClient.RecieveFile(Uri targetFile) in w:\SomePath\ServiceClients.Ftp\Reference.cs:line 102
   at SomeNameSpace.UIConsole.Program.Test()
                 

Kui jõudlus ei ole probleemiks siis funktsionaalselt enam-vähem samaväärne on õnnestumise jälgimine ära jätta ja Abort() alati välja kutsuda. Viga ta ei viska, aga lisandub väike performance overhead ja see oleks ka lihtsalt vale ;)

Abiks oli Damian McGivern'i postitus, kus muuhulgas vihjatakse, kuidas seda try-finally-succeeded blokki mugavamaks teha saaks.

Huvitav, kas c# 4.0 jaoks on WCF proxy Close() pisut koostööaltimaks tehtud ..

Sellest kuidas Skype kaaperdab HTTPS porti..

Tekkis vajadus vahetada IIS6 all aktiivset Website'i. Selline lihtne tegevus, millest ei oskaks probleemi oodata:

a) site A -> Stop
b) site B -> Start

Site A peatati kenasti, IIS aga keeldub Web Site'i B avamast ja kangekaelselt jutustab:

"This process cannot access the file because it is being used by another process"

Informatiivne on veateates kasutada väljendit "the file", kui ÜHEST konkreetsest failist pole juttugi. Õnneks Eventlog on praktilisem ja source HTTP teatab, et :

Unable to bind to the underlying transport for 0.0.0.0:443. The IP Listen-Only list may contain a reference to an interface which may not exist on this machine.  The data field contains the error number.

Selgus, et probleemiks oli Skype, mis leidis omavoliliselt ja alatult, et 443 kuulub talle. Tegelegu oma asjadega (ja võimaldagu admin-inimestel mugavamalt skype pordid kinni keerata kui vaja).

Pärast Skype ajutist mahatapmist sai IIS (ja mina) oma tööd teha.

Sellest, mida System.Decimal kõhus peidab..

Ilmselt iga asjalikum koodikirjutaja teab, mille poolest erinevad System.Single (või float, kui keegi seda rohkem eelistab) ja System.Decimal andmetüübid. Yada-yada, kahendsüsteem vs kümnendsüsteem. Sellegipoolest jäin mõneks hetkeks mõttesse kui avastasin, et ka ToString() käitub erinevalt:

Console.WriteLine((1.0F).ToString());   // returns '1'
Console.WriteLine((1.000F).ToString()); // returns '1'

Console.WriteLine((1M).ToString());     // returns '1'
Console.WriteLine((1.000M).ToString()); // returns '1,000'
Console.WriteLine(1M == 1.000M);        // returns true

Järeldus tuleviku tarbeks on see, et Decimal korral ei tohi eeldada, et ToString() sama väärtusega sisendi korral alati sama vastuse annaks. Kui formaat on oluline, siis tuleb alati see ka täpsustada ning mitte lootma jääda parameetriteta meetodile.

Console.WriteLine((1.000M).ToString("0"));   
// returns '1'

Loomulikult, kui formaat on märgi täpsusega oluline, siis tuleks ka kultuur täpsustada. Antud juhul ei ole see oluline.

 

Miks Decimal nii käitub ?

Teatavasti on  Decimal 128-bitine struktuur. Sisemiselt:

  • 3 * 32bit "täpsusosa" - low, med and  hi-bits
  • märgibitt
  • scale-väärtusele, mis määrab kui suur osa täpsusosa kümnendkohtadest on murdosa.

Siit ka tuleneb lubatud väärtuste hulk:

{ s * c * 10^(-e)|
     s kuulub hulka {-1,1}, 
     0 <= c <= 2^96 ,
     ja 0 <= e <= 28
}

Muuhulgas nähtub, et seetõttu leiduvad erinevad (s,c,e)-komplektid (ehk decimal sisemised väärtustused), mis omavad muutuja kasutaja poolt vaadatuna sama väärtust (näiteks arvu 1). Eelpool kirjeldatud näide demonstreeris just kahte erinevat sisemist väärtustust.

Illustreerimiseks võib selle näite veel ilmekamalt lahti kirjutada, kasutades konstruktorit, mis võimaldab kõiki eeltoodud komponente ise sisestada:

Decimal d10 = new decimal(10, 0, 0, false, 1);
Console.WriteLine(d10.ToString());   // returns '1,0'
Decimal d1000 = new decimal(1000, 0, 0, false, 3);
Console.WriteLine(d1000.ToString()); // returns '1,000'
Console.WriteLine(d10 == d1000);     //returns True;


Aga mille jaoks on ülejäänud bitid ?

Komponendid e ja märgibitt saavad kasutada 128 - 96 = 32 biti jagu ruumi. Kui märgibitt võtab neist tubli 1, siis miks e lubatud väärtused on piiratud 29 erineva väärtusega, kui kasutada on 2^31 ? Vägisi jääb mulje, et 31 - up(log_2 29) = 26 bitti iga Decimali kohta on kasutu ballast.. Raske uskuda, aga mõistlikku selgitust ei näe.


Aga miks System.Single ToString() ikkagi teisiti käitub ?

Teoreetiliselt on ka nende jaoks võimalik konstrueerida samasugune juhtum (1 * 2^10 vs 2 * 2^9) . Pead või sõrmi panti ei paneks, aga oletan, et levinumate floating-point tüüpide korral lahendatakse "normaliseerimine" riistvaras. Kahendsüsteemis nihutamine on ka oluliselt  odavam ettevõtmine.

Sellest, kuidas SQLis kuupäevad keele-sõltumatud hoida..

Seoses avastusega, et ma olen kunagi lohakat koodi kirjutanud ja lubamatult eeldanud, et MSSQL sööb DateTime InvariantCulture väljundit. Sööbki, kui keeled, baasis määratud formaat jms sobivad. Lollikindel on aga kasutada sõltumatut formaati, näiteks ISO8601 (kuju
1999-02-23T22:33:44), mida väljastab näiteks DateTime.ToString("s").

Hea ülevaade teistest võimalustest on toodud siin:
The ultimate guide to the datetime datatypes