Skip to content

Instantly share code, notes, and snippets.

@zsoi
Created January 6, 2017 17:14
Show Gist options
  • Save zsoi/a6c0429dd4247ec58fc3375c510a3dbd to your computer and use it in GitHub Desktop.
Save zsoi/a6c0429dd4247ec58fc3375c510a3dbd to your computer and use it in GitHub Desktop.
Utility classes to store floats or doubles in human-readable, yet exact representations. Inspired by http://the-witness.net/news/2011/12/engine-tech-concurrent-world-editing/
namespace ZS.Tools
{
using System;
using System.Globalization;
using System.Text;
/// <summary>
/// The safe double class is mainly a interface between doubles and
/// their string representation. Traditional string representations of
/// doubles suffer from lossy precision. i.e. converting doubles to strings
/// to doubles to strings, ... over and over again changes the original value
/// of the double. The safe double class introduces some conversion methods which
/// will create/parse a special string format which contains a 1:1 representation
/// of the memory representation of the double and can thus be restored without the
/// fear of any data loss.
/// The "safe" format contains of two parts: A traditional human readable fixed double
/// notation and a machine-readable hex-encoded binary representation. These two are
/// separated by a colon. The machine-readable part is optional. When removing the colon and
/// the binary part, the humand readable notation will be parsed. If a binary part is
/// existing, the readable notation will be ignored (even if their values differ in a big dimension)
/// and the binary part will be restored.
/// </summary>
public static class SafeDouble
{
/// <summary>
/// Converts a double to a safe representation that can be
/// restored without loss (i.e. bit precision)
/// </summary>
/// <returns>The double to convert</returns>
/// <param name="f">The string representation.</param>
public static string ToString(double f)
{
byte[] bytes = BitConverter.GetBytes(f);
StringBuilder sb = new StringBuilder();
sb.AppendFormat(CultureInfo.InvariantCulture, "{0:R} : ", f);
foreach (byte b in bytes)
{
sb.AppendFormat("{0:x2}", b);
}
return sb.ToString();
}
/// <summary>
/// Parses a string which should be in either fixed-point double notation "#.###""
/// or in the combined safe double format "#.### : xxxxxxxx"
/// </summary>
/// <returns>The converted double</returns>
/// <param name="s">String in safe-double notation or in fixed double notation</param>
public static double ToDouble(string s)
{
string[] parts = s.Replace(" ", "").Split(':');
if (parts.Length == 2)
{
byte[] bytes = new byte[8];
for (int n = 0; n < bytes.Length; n++)
{
bytes[n] = Convert.ToByte(parts[1].Substring(n * 2, 2), 16);
}
return BitConverter.ToDouble(bytes, 0);
}
else if (parts.Length == 1)
{
return double.Parse(parts[0], CultureInfo.InvariantCulture);
}
throw new Exception("Safe double string has wrong format");
}
}
}
namespace ZS.Tools
{
using System;
using System.Globalization;
using System.Text;
/// <summary>
/// The safe float class is mainly a interface between floats and
/// their string representation. Traditional string representations of
/// floats suffer from lossy precision. i.e. converting floats to strings
/// to floats to strings, ... over and over again changes the original value
/// of the float. The safe float class introduces some conversion methods which
/// will create/parse a special string format which contains a 1:1 representation
/// of the memory representation of the float and can thus be restored without the
/// fear of any data loss.
/// The "safe" format contains of two parts: A traditional human readable fixed float
/// notation and a machine-readable hex-encoded binary representation. These two are
/// separated by a colon. The machine-readable part is optional. When removing the colon and
/// the binary part, the humand readable notation will be parsed. If a binary part is
/// existing, the readable notation will be ignored (even if their values differ in a big dimension)
/// and the binary part will be restored.
///
/// Examples:
/// 1500 : 0080bb44
/// 400 : 0000c843
/// 20550 : 008ca046
/// -0.2418448 : 27a677be
/// 0.664463 : 401a2a3f
/// </summary>
public static class SafeFloat
{
/// <summary>
/// Converts a float to a safe representation that can be
/// restored without loss (i.e. bit precision)
/// </summary>
/// <returns>The float to convert</returns>
/// <param name="f">The string representation.</param>
public static string ToString(float f)
{
byte[] bytes = BitConverter.GetBytes(f);
StringBuilder sb = new StringBuilder();
sb.AppendFormat(CultureInfo.InvariantCulture, "{0:R} : ", f);
foreach (byte b in bytes)
{
sb.AppendFormat("{0:x2}", b);
}
return sb.ToString();
}
/// <summary>
/// Parses a string which should be in either fixed-point float notation "#.###""
/// or in the combined safe float format "#.### : xxxxxxxx"
/// </summary>
/// <returns>The converted float</returns>
/// <param name="s">String in safe-float notation or in fixed float notation</param>
public static float ToFloat(string s)
{
string[] parts = s.Replace(" ", "").Split(':');
if (parts.Length == 2)
{
byte[] bytes = new byte[4];
for (int n = 0; n < bytes.Length; n++)
{
bytes[n] = Convert.ToByte(parts[1].Substring(n * 2, 2), 16);
}
return BitConverter.ToSingle(bytes, 0);
}
else if (parts.Length == 1)
{
return float.Parse(parts[0], CultureInfo.InvariantCulture);
}
throw new Exception("Safe float string has wrong format");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment