-
-
Save bboyle1234/df47f661b531efd7386f0dbcdfbeee6f to your computer and use it in GitHub Desktop.
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using static System.Math; | |
namespace Foo { | |
public static class DoubleExtensions { | |
public static double RoundToSignificantFigures(this double value, int numSignificantFigures) { | |
if (value == 0) return 0.0; | |
var scale = Pow(10, Floor(Log10(Abs(value))) + 1); | |
// Perform the last step using decimals to prevent double-arithmetic re-introducing tiny errors (and more figures to the result) | |
return (double)((decimal)scale * (decimal)Math.Round(value / scale, numSignificantFigures)); | |
} | |
} | |
} |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using static System.Math; | |
namespace Foo { | |
public static class HumanReadableDoubles { | |
// Created with thanks to http://stackoverflow.com/questions/16083666/make-big-and-small-numbers-human-readable/16091580#16091580 | |
static readonly string[] humanReadableSuffixes = { "f", "a", "p", "n", "μ", "m", "", "k", "M", "G", "T", "P", "E" }; | |
public static string ToHumanReadable(this double value, int numSignificantDigits) { | |
// Deal with special values | |
if (double.IsInfinity(value) || double.IsNaN(value) || value == 0 || numSignificantDigits <= 0) | |
return value.ToString(); | |
// We deal only with positive values in the code below | |
var isNegative = Sign(value) < 0; | |
value = Abs(value); | |
// Calculate the exponent as a multiple of 3, ie -6, -3, 0, 3, 6, etc | |
var exponent = (int)Floor(Log10(value) / 3) * 3; | |
// Find the correct suffix for the exponent, or fall back to scientific notation | |
var indexOfSuffix = exponent / 3 + 6; | |
var suffix = indexOfSuffix >= 0 && indexOfSuffix < humanReadableSuffixes.Length | |
? humanReadableSuffixes[indexOfSuffix] | |
: "·10^" + exponent; | |
// Scale the value to the exponent, then format it to the correct number of significant digits and add the suffix | |
value = value * Pow(10, -exponent); | |
var numIntegerDigits = (int)Floor(Log(value, 10)) + 1; | |
var numFractionalDigits = Min(numSignificantDigits - numIntegerDigits, 15); | |
var format = $"{new string('0', numIntegerDigits)}.{new string('0', numFractionalDigits)}"; | |
var result = value.ToString(format) + suffix; | |
// Handle negatives | |
if (isNegative) | |
result = "-" + result; | |
return result; | |
} | |
public static double ParseHumanReadableDouble(this string expression) { | |
var multiplier = 1.0; | |
if (expression.Contains("·10^")) { | |
var indexOfCaret = expression.LastIndexOf('^'); | |
multiplier = Pow(10, int.Parse(expression.Substring(indexOfCaret + 1))); | |
expression = expression.Substring(0, indexOfCaret - 3); | |
} else { | |
var suffix = humanReadableSuffixes.SingleOrDefault(s => s.Length > 0 && expression.EndsWith(s, StringComparison.InvariantCulture)) ?? ""; | |
var suffixIndex = humanReadableSuffixes.IndexOf(suffix); | |
multiplier = Pow(10, 3 * (suffixIndex - 6)); | |
expression = expression.Replace(suffix, string.Empty); | |
} | |
return double.Parse(expression) * multiplier; | |
} | |
} | |
} |
I want to contribute one more function that rounds floating point numbers based on the number of significant digits.
public static double RoundSig(this double d, int significant_figures)
{
Contract.Requires(significant_figures>=0&&significant_figures<=MaxSignificantFigures);
// http://stackoverflow.com/a/374470
if(d==0)
return 0;
double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d)))+1);
return scale*Math.Round(d/scale, significant_figures);
}
ja72, I've updated the gist. Thank you. I wasn't sure of whether the decimal thing had to be added to the rounding method. Experience made me drop it in, but I didn't spend time testing to determine whether it's truly necessary.
I just saw it. It is an interesting approach. Do you have any references that describe in more detail this trick? I can see if the number is decomposed into this form X 10^n
that we can use decimal for X
which has more bits than the mantissa of double
.
Sorry to dump more code at you, but how about these numeric manipulation functions:
/// <summary>
/// Return 1 only when values in between min and max, 0 otherwise.
/// </summary>
/// <param name="value">The value to evaluate</param>
/// <param name="min_value">The min value</param>
/// <param name="max_value">The max value</param>
/// <returns></returns>
[Pure]
public static double Chi(this double value, double min_value, double max_value)
{
double dx = Math.Abs(max_value-min_value);
min_value=Math.Min(min_value, max_value);
max_value=min_value+dx;
return value<min_value ? 0 : (value>max_value ? 0 : 1);
}
/// <summary>
/// Return a saw-tooth value between a minimum and a maxmimum value.
/// <remarks>Sorts the min/max values from lowest to highest</remarks>
/// </summary>
/// <param name="value">The value to wrap</param>
/// <param name="min_value">The lower limit</param>
/// <param name="max_value">The upper limit</param>
/// <returns>A scalar value</returns>
[Pure]
public static double WrapAround(this double value, double min_value, double max_value)
{
double dx = Math.Abs(max_value-min_value);
min_value=Math.Min(min_value, max_value);
return value-dx*Math.Floor((value-min_value)/dx);
}
/// <summary>
/// Return a saw-tooth value between zero an a maximum value.
/// </summary>
/// <param name="x">The value to use</param>
/// <param name="x_high">The maximum value allowed</param>
/// <returns>A scalar value</returns>
[Pure]
public static double WrapAround(this double value, double max_value)
{
return WrapAround(value, 0, max_value);
}
/// <summary>
/// Return x when between min and max value, otherwise clamp at limits
/// </summary>
/// <example>
/// ClapMinMax(-0.33, 0.0, 1.0) = 0.00
/// ClapMinMax( 0.33, 0.0, 1.0) = 0.33
/// ClapMinMax( 1.33, 0.0, 1.0) = 1.00
/// </example>
/// <param name="x">The value to clamp</param>
/// <param name="min_value">The minimum value to use</param>
/// <param name="max_value">The maximum value to use</param>
/// <returns>A scalar value</returns>
[Pure]
public static double ClampMinMax(this double value, double min_value, double max_value)
{
return value>max_value ? max_value : value<min_value ? min_value : value;
}
/// <summary>
/// Return x when more than min, otherwise return min
/// </summary>
/// <param name="x">The value to clamp</param>
/// <param name="min_value">The minimum value to use</param>
/// <returns>A scalar value</returns>
[Pure]
public static double ClampMin(this double value, double min_value)
{
return value<min_value ? min_value : value;
}
/// <summary>
/// Return x when less than max, otherwise return max
/// </summary>
/// <param name="x">The value to clamp</param>
/// <param name="max_value">The maximum value to use</param>
/// <returns>A scalar value</returns>
[Pure]
public static double ClampMax(this double value, double max_value)
{
return value>max_value ? max_value : value;
}
Fore me WrapAround
and ClampMinMax
are used all the time. Chi
not so often. Examples would be getting angle results between -180 and +180. or 0 to 360 for usage in Sin()
or Cos()
in order to maintain precision. Try to see that Sin(1) != Sin(1+2*Math.PI)
. They differ by some 10^-16
. But as the angles go up that error accumulates rapidly.
Hi. Author of the SO answer here. How about adding Parsing of the human readable value.