/*
* Copyright (C) 2007-2012 Xagasoft, All rights reserved.
*
* This file is part of the libgats library and is released under the
* terms of the license contained in the file LICENSE.
*/
using System.IO;
using System;
namespace Com.Xagasoft.Gats
{
///
/// Encapsulates a single floating point value.
///
///
/// The GATS floating point encoding is bit-exact with any standard
/// floating point encoding similar to the IEEE encoding. This means that
/// unlike textual encoding you will always get the exact same floating
/// point value back that you encoded. This format is arbitrary precision
/// and not dependant on platform or hardware.
///
/// Although the format is arbitrary precision, the backend in C# uses a
/// double for maximum precision without resorting to a software arbitrary
/// precision library.
///
/// Interestingly, unlike many other languages, C# provides a decimal type
/// that is also floating point, however the decimal type is encoded using
/// a bcd-like scheme. This makes it an excellent choice for many human
/// activities, but it cannot be encoded precisely as a GatsFloat. If you
/// would like to preserve the decimal nature of those types it may be
/// better to encode them as strings.
///
/// In encoding, the GATS float uses two different type specifiers, an 'f'
/// indicates a normal floating point value. An 'F' represents a special
/// value: positive or negative NaN, infinity, or zero.
///
public class GatsFloat : GatsObject
{
private static readonly double Log256 = Math.Log( 256.0 );
public double Value { get; set; }
public GatsFloat()
{
Value = 0.0;
}
public GatsFloat( double val )
{
Value = val;
}
public override string ToString()
{
return Value.ToString();
}
public override void Read( Stream s, char type )
{
if( type == 'F' )
{
int subType = s.ReadByte();
if( subType == -1 )
throw new GatsException( GatsException.Type.PrematureEnd );
switch( (char)subType )
{
case 'N': Value = -Double.NaN; break;
case 'n': Value = Double.NaN; break;
case 'I': Value = Double.NegativeInfinity; break;
case 'i': Value = Double.PositiveInfinity; break;
case 'Z': Value = -0.0; break;
case 'z': Value = 0.0; break;
}
}
else if( type == 'f' )
{
int len = (int)GatsInteger.ReadPackedInt( s );
bool neg = false;
if( len < 0 )
{
neg = true;
len = -len;
}
int[] buf = new int[len];
for( int j = 0; j < len; j++ )
{
buf[j] = s.ReadByte();
}
Value = 0.0;
for( int j = len-1; j > 0; j-- )
{
Value = (Value+(double)buf[j]) * (1.0/256.0);
}
Value += buf[0];
long scale = GatsInteger.ReadPackedInt( s );
Value *= Math.Pow( 256.0, scale );
if( neg )
Value = -Value;
}
}
public override void Write( Stream s )
{
if( Value == 0.0 )
{
s.WriteByte( (int)'F' );
s.WriteByte( (int)'z' );
}
else if( Double.IsInfinity( Value ) )
{
s.WriteByte( (int)'F' );
if( Double.IsNegativeInfinity( Value ) )
s.WriteByte( (int)'I' );
else
s.WriteByte( (int)'i' );
}
else if( Double.IsNaN( Value ) )
{
s.WriteByte( (int)'F' );
s.WriteByte( (int)'n' );
}
else
{
s.WriteByte( (int)'f' );
double d = Value;
bool neg = false;
if( d < 0.0 )
{
neg = true;
d = -d;
}
MemoryStream ms = new MemoryStream();
long scale = (long)(Math.Log( d ) / Log256);
if( scale < 0 ) scale--;
d /= Math.Pow( 256.0, scale );
ms.WriteByte( (byte)d );
d -= (int)d;
for( int j = 0; j < 150 && d != 0.0; j++ )
{
d = d*256.0;
ms.WriteByte( (byte)d );
d -= (int)d;
}
byte[] msbuf = ms.ToArray();
if( neg )
GatsInteger.WritePackedInt( s, -msbuf.Length );
else
GatsInteger.WritePackedInt( s, msbuf.Length );
s.Write( msbuf, 0, msbuf.Length );
GatsInteger.WritePackedInt( s, scale );
}
}
}
}