Click here to Skip to main content
Click here to Skip to main content

A fixed-point data type class in C#/Saltarelle

, 5 Dec 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
Implementing a fixed-point numeric type in Saltarelle

Introduction     

C# to JavaScript compilers like Saltarelle are becoming popular among .NET developers as they allow to write C# applications that target the web platform (HTML5/CSS3/JS).

Unfortunately JavaScript's lack of a fixed-point numeric data type makes such platform not suitable for writing client-side business applications.

This article shows how to leverage the power of the C# language and Saltarelle compiler to implement a fixed-point data type class that can be mapped transparently in JavaScript and be used in applications as a good replacement of .NET Decimal type.

The Problem 

The only numeric data type available in JavaScript is the floating point number type ("Number"). Floats are good for making scientific calculations but fail to be accurate when they are used to store money values containing fractions (cents).

Consider the following JavaScript code:

var a = 0.1; 
a = a + 0.2;
if(a==0.3) { window.alert('equal!'); }
else       { window.alert('NOT equal!'); }

you won't believe, but the result is "NOT equal" !

This because the floating point notation is unable to store the value 0.1 accurately, but can store only a very close approximation of it. This small difference may accumulate and cause unpredictable errors, manifesting as missed checks (as in the example) or incorrect values.

Of course this is not acceptable in a business application where the minimal requirement is that the computer must compute correctly!

The problem is usually solved by adopting a fixed-point notation. Established a level of precision (eg. 4 decimal digits), all numbers are scaled up to eliminate the decimal dot and are stored in memory as integers. All subsequent arithmetic operations carried on the numbers must take into account the scale factor, for example when multiplying a * b the result needs to be scaled down one time, because each of the operands add its own scale factors, resulting in numbers scaled twice if not corrected.

In this article I present a fixed-point class implementation, called Currency, that has the following features:

  • it's transparently mapped to the JavaScript Number type; this means it's not a real class and so it carry no code with it (no methods, no properties, no body class). Similarly to all other numeric types in Saltarelle, there is no way of retrieving its original C# type at runtime (calling GetType() will not return "Currency" but "Number" instead). 
  • arithmetic operations are translated into JavaScript as inline code (Saltarelle's [InlineCode] attribute decorator does all the magic).
  • when performing operations, all numbers are firstly converted to fixed-point, then the operation is calculated and the result is converted again in floating point. This allows to keep numbers stored as normal floating point numbers, without any scale involved for the final user. (The opposite would be keeping numbers stored as scaled-up integers and scale them down when they are referenced--but it's less practical). 
  • in C# the class is seen as a normal numeric type, similar to .NET's Decimal, with operators and implicit/explicit cast conversions defined. 
  • the precision (scale) of this Currency data type is fixed to 4 (scale factor 10000) which should be enough for real life applications involving money. Other scales may be obtained by simple search and replace in the source code, changing the string "10000" into another power of 10 (e.g. "100" for fewer decimal digits).

How does it work

The core of the class functionality resides in the definition of the arithmetic operators. As a rule, when a mathematical operation is required, operands are converted to fixed point by upscaling and truncating; then the operation is calculated and finally the result is converted again to floating point.

For example the addition operation is defined as:

[InlineCode("( (({d1}*10000)>>0) + (({d2}*10000)>>0) )/10000")]
public static Currency operator +(Currency d1, Currency d2) 
{ 
   return d1; 
}          

the [InlineCode] attribute tells the compiler to ignore the method body ("return d1") and output instead the inline JavaScript code contained in the string argument.

In the example, the two operands d1 and d2 are:

- scaled up by mean of "* 10000"
- truncated by mean of ">> 0"
- numbers are then added together by mean of "+"
- and scaled down again by mean of "/ 10000".

The real usefullness of the class is that it completely hides such tedious operations that otherwise had to be hardcoded manually, making the souce code less readable and difficult to maintain. Those who do not use Saltarelle and code directly in JavaScript instead of C#, have no other way; the best they can do is to have an external class library and use method calls instead of operators (e.g. d1.Add(d2) and so on).

How to Use the code 

Add Currency.cs to your Saltarelle script project and start to use the class as if it was a new data type.

Variables can be initialized with integer or floating point constants. For example:

Currency a = 10;
Currency b = (Currency) 3.14;

As initializing from a float constant (3.14) may incur in numeric rounding, the cast operator is explicitly needed. Integer numbers are encoded exactly and so they require no cast. Similarly, when converting Currency values to another numeric type, cast is always required, for example:

double c = (double) b; 

Conversion to string may be otained simply via .ToString(). For completeness, Currency implements also the following methods:

  • .ToFixed([fractionDigints]) 
  • .ToPrecision([precision])  

and the static methods: Ceiling(), Floor() and Round().

Testing the class

As a simple test we use the following C# code:

// double test
double fa = 0.1;
fa = fa + 0.2;
double fb = 0.3;
if(fa==fb) Window.Alert("equals");
else Window.Alert("NOT equals");
 
// currency test
Currency ca = (Currency) 0.1; 
ca = ca + (Currency) 0.2;
Currency cb = (Currency) 0.3;
if(ca==cb) Window.Alert("equals");
else Window.Alert("NOT equals"); 

When running it, the double section fails but the Currency one passes as expected.

Now look at the generated JavaScript code:   

// double test
var fa = 0.1;
fa = fa + 0.2;
var fb = 0.3;
if (fa === fb) {
    window.alert('equals');
}
else {
    window.alert('NOT equals');
}
   
// currency test
var ca = (0.1 * 10000 >> 0) / 10000;
ca = ((ca * 10000 >> 0) + ((0.2 * 10000 >> 0) / 10000 * 10000 >> 0)) / 10000;
var cb = (0.3 * 10000 >> 0) / 10000;
if (ca === cb) {
    window.alert('equals');
}
else {
    window.alert('NOT equals');
} 
The two sections are very similar, the only extra code added by the compiler is relative to the inline arithmetic operations (now imagine if they had to be written by hand!).

Disclaimer and conclusion  

I'm not a math expert and the code presented here is the result of my understanding of the theory, so I recommend to test it appropriately before real use. I wrote it for fun as an exercise in Saltarelle, I hope it inspires more developers to switch from plain JavaScript to full C# for their client-side web applications.

History

  • Release 1.0 - Dec, 4 2012 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Antonino Porcino
Software Developer
Italy Italy
No Biography provided

Comments and Discussions

 
GeneralMy vote of 3 Pinmemberjfriedman5-Dec-12 15:47 
GeneralRe: My vote of 3 PinmemberAntonino Porcino5-Dec-12 22:27 
GeneralRe: My vote of 3 Pinmemberjfriedman6-Dec-12 4:31 
GeneralRe: My vote of 3 PinmemberAntonino Porcino6-Dec-12 7:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 5 Dec 2012
Article Copyright 2012 by Antonino Porcino
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid