/* * 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 ); } } } }