Last active
January 15, 2022 19:06
-
-
Save bboyle1234/df47f661b531efd7386f0dbcdfbeee6f to your computer and use it in GitHub Desktop.
Humanizes numbers. Makes them human-readable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 forX
which has more bits than the mantissa ofdouble
.