表示するSharePointリストにプッシュする10進数データがあります。特定の計算の知識に基づいて、結果データに表示される有効数字の数を制限したいのですが。 3になることもあるので、12345は12300になり、0.012345は0.0123になります。たまに4または5になります。これを処理する便利な方法はありますか?
参照: RoundToSignificantFigures by "P Daddy"。
彼の方法を、私が気に入った別の方法と組み合わせました。
有効桁数への丸めは、丸め方法が小数点以下の桁数ではなく丸め位置に基づいているTSQLではるかに簡単です。これは、.Net math.roundの場合です。 TSQLの数値を負の位置に丸めることができます。これは整数で丸めるため、スケーリングは必要ありません。
こちらもご覧ください other thread 。 Pyrolisticalの方法は良いです。
問題の末尾のゼロ部分は、文字列操作のように思えるので、必要に応じてゼロを埋めるToString()拡張メソッドを含めました。
using System;
using System.Globalization;
public static class Precision
{
// 2^-24
public const float FLOAT_EPSILON = 0.0000000596046448f;
// 2^-53
public const double DOUBLE_EPSILON = 0.00000000000000011102230246251565d;
public static bool AlmostEquals(this double a, double b, double epsilon = DOUBLE_EPSILON)
{
// ReSharper disable CompareOfFloatsByEqualityOperator
if (a == b)
{
return true;
}
// ReSharper restore CompareOfFloatsByEqualityOperator
return (System.Math.Abs(a - b) < epsilon);
}
public static bool AlmostEquals(this float a, float b, float epsilon = FLOAT_EPSILON)
{
// ReSharper disable CompareOfFloatsByEqualityOperator
if (a == b)
{
return true;
}
// ReSharper restore CompareOfFloatsByEqualityOperator
return (System.Math.Abs(a - b) < epsilon);
}
}
public static class SignificantDigits
{
public static double Round(this double value, int significantDigits)
{
int unneededRoundingPosition;
return RoundSignificantDigits(value, significantDigits, out unneededRoundingPosition);
}
public static string ToString(this double value, int significantDigits)
{
// this method will round and then append zeros if needed.
// i.e. if you round .002 to two significant figures, the resulting number should be .0020.
var currentInfo = CultureInfo.CurrentCulture.NumberFormat;
if (double.IsNaN(value))
{
return currentInfo.NaNSymbol;
}
if (double.IsPositiveInfinity(value))
{
return currentInfo.PositiveInfinitySymbol;
}
if (double.IsNegativeInfinity(value))
{
return currentInfo.NegativeInfinitySymbol;
}
int roundingPosition;
var roundedValue = RoundSignificantDigits(value, significantDigits, out roundingPosition);
// when rounding causes a cascading round affecting digits of greater significance,
// need to re-round to get a correct rounding position afterwards
// this fixes a bug where rounding 9.96 to 2 figures yeilds 10.0 instead of 10
RoundSignificantDigits(roundedValue, significantDigits, out roundingPosition);
if (Math.Abs(roundingPosition) > 9)
{
// use exponential notation format
// ReSharper disable FormatStringProblem
return string.Format(currentInfo, "{0:E" + (significantDigits - 1) + "}", roundedValue);
// ReSharper restore FormatStringProblem
}
// string.format is only needed with decimal numbers (whole numbers won't need to be padded with zeros to the right.)
// ReSharper disable FormatStringProblem
return roundingPosition > 0 ? string.Format(currentInfo, "{0:F" + roundingPosition + "}", roundedValue) : roundedValue.ToString(currentInfo);
// ReSharper restore FormatStringProblem
}
private static double RoundSignificantDigits(double value, int significantDigits, out int roundingPosition)
{
// this method will return a rounded double value at a number of signifigant figures.
// the sigFigures parameter must be between 0 and 15, exclusive.
roundingPosition = 0;
if (value.AlmostEquals(0d))
{
roundingPosition = significantDigits - 1;
return 0d;
}
if (double.IsNaN(value))
{
return double.NaN;
}
if (double.IsPositiveInfinity(value))
{
return double.PositiveInfinity;
}
if (double.IsNegativeInfinity(value))
{
return double.NegativeInfinity;
}
if (significantDigits < 1 || significantDigits > 15)
{
throw new ArgumentOutOfRangeException("significantDigits", value, "The significantDigits argument must be between 1 and 15.");
}
// The resulting rounding position will be negative for rounding at whole numbers, and positive for decimal places.
roundingPosition = significantDigits - 1 - (int)(Math.Floor(Math.Log10(Math.Abs(value))));
// try to use a rounding position directly, if no scale is needed.
// this is because the scale mutliplication after the rounding can introduce error, although
// this only happens when you're dealing with really tiny numbers, i.e 9.9e-14.
if (roundingPosition > 0 && roundingPosition < 16)
{
return Math.Round(value, roundingPosition, MidpointRounding.AwayFromZero);
}
// Shouldn't get here unless we need to scale it.
// Set the scaling value, for rounding whole numbers or decimals past 15 places
var scale = Math.Pow(10, Math.Ceiling(Math.Log10(Math.Abs(value))));
return Math.Round(value / scale, significantDigits, MidpointRounding.AwayFromZero) * scale;
}
}
これでうまくいくかもしれません:
double Input1 = 1234567;
string Result1 = Convert.ToDouble(String.Format("{0:G3}",Input1)).ToString("R0");
double Input2 = 0.012345;
string Result2 = Convert.ToDouble(String.Format("{0:G3}", Input2)).ToString("R6");
ただし、G3をG4に変更すると、奇妙な結果になります。有効数字を切り上げるように見えますか?
私は http://ostermiller.org/utils/SignificantFigures.Java.html からいくつかのコードを取得しました。それはJavaにあったので、C#ビルドを作成するために、簡単な検索/置換と再整形を行いました。それは私の重要な図のニーズにうまく機能しているようです。 FWIW、私は彼のjavadocコメントをここでより簡潔にするために削除しましたが、元のコードは非常にうまく文書化されています。
/*
* Copyright (C) 2002-2007 Stephen Ostermiller
* http://ostermiller.org/contact.pl?regarding=Java+Utilities
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* See COPYING.TXT for details.
*/
public class SignificantFigures
{
private String original;
private StringBuilder _digits;
private int mantissa = -1;
private bool sign = true;
private bool isZero = false;
private bool useScientificNotation = true;
public SignificantFigures(String number)
{
original = number;
Parse(original);
}
public SignificantFigures(double number)
{
original = Convert.ToString(number);
try
{
Parse(original);
}
catch (Exception nfe)
{
_digits = null;
}
}
public bool UseScientificNotation
{
get { return useScientificNotation; }
set { useScientificNotation = value; }
}
public int GetNumberSignificantFigures()
{
if (_digits == null) return 0;
return _digits.Length;
}
public SignificantFigures SetLSD(int place)
{
SetLMSD(place, Int32.MinValue);
return this;
}
public SignificantFigures SetLMSD(int leastPlace, int mostPlace)
{
if (_digits != null && leastPlace != Int32.MinValue)
{
int significantFigures = _digits.Length;
int current = mantissa - significantFigures + 1;
int newLength = significantFigures - leastPlace + current;
if (newLength <= 0)
{
if (mostPlace == Int32.MinValue)
{
original = "NaN";
_digits = null;
}
else
{
newLength = mostPlace - leastPlace + 1;
_digits.Length = newLength;
mantissa = leastPlace;
for (int i = 0; i < newLength; i++)
{
_digits[i] = '0';
}
isZero = true;
sign = true;
}
}
else
{
_digits.Length = newLength;
for (int i = significantFigures; i < newLength; i++)
{
_digits[i] = '0';
}
}
}
return this;
}
public int GetLSD()
{
if (_digits == null) return Int32.MinValue;
return mantissa - _digits.Length + 1;
}
public int GetMSD()
{
if (_digits == null) return Int32.MinValue;
return mantissa + 1;
}
public override String ToString()
{
if (_digits == null) return original;
StringBuilder digits = new StringBuilder(this._digits.ToString());
int length = digits.Length;
if ((mantissa <= -4 || mantissa >= 7 ||
(mantissa >= length &&
digits[digits.Length - 1] == '0') ||
(isZero && mantissa != 0)) && useScientificNotation)
{
// use scientific notation.
if (length > 1)
{
digits.Insert(1, '.');
}
if (mantissa != 0)
{
digits.Append("E" + mantissa);
}
}
else if (mantissa <= -1)
{
digits.Insert(0, "0.");
for (int i = mantissa; i < -1; i++)
{
digits.Insert(2, '0');
}
}
else if (mantissa + 1 == length)
{
if (length > 1 && digits[digits.Length - 1] == '0')
{
digits.Append('.');
}
}
else if (mantissa < length)
{
digits.Insert(mantissa + 1, '.');
}
else
{
for (int i = length; i <= mantissa; i++)
{
digits.Append('0');
}
}
if (!sign)
{
digits.Insert(0, '-');
}
return digits.ToString();
}
public String ToScientificNotation()
{
if (_digits == null) return original;
StringBuilder digits = new StringBuilder(this._digits.ToString());
int length = digits.Length;
if (length > 1)
{
digits.Insert(1, '.');
}
if (mantissa != 0)
{
digits.Append("E" + mantissa);
}
if (!sign)
{
digits.Insert(0, '-');
}
return digits.ToString();
}
private const int INITIAL = 0;
private const int LEADZEROS = 1;
private const int MIDZEROS = 2;
private const int DIGITS = 3;
private const int LEADZEROSDOT = 4;
private const int DIGITSDOT = 5;
private const int MANTISSA = 6;
private const int MANTISSADIGIT = 7;
private void Parse(String number)
{
int length = number.Length;
_digits = new StringBuilder(length);
int state = INITIAL;
int mantissaStart = -1;
bool foundMantissaDigit = false;
// sometimes we don't know if a zero will be
// significant or not when it is encountered.
// keep track of the number of them so that
// the all can be made significant if we find
// out that they are.
int zeroCount = 0;
int leadZeroCount = 0;
for (int i = 0; i < length; i++)
{
char c = number[i];
switch (c)
{
case '.':
{
switch (state)
{
case INITIAL:
case LEADZEROS:
{
state = LEADZEROSDOT;
}
break;
case MIDZEROS:
{
// we now know that these zeros
// are more than just trailing place holders.
for (int j = 0; j < zeroCount; j++)
{
_digits.Append('0');
}
zeroCount = 0;
state = DIGITSDOT;
}
break;
case DIGITS:
{
state = DIGITSDOT;
}
break;
default:
{
throw new Exception(
"Unexpected character '" + c + "' at position " + i
);
}
}
}
break;
case '+':
{
switch (state)
{
case INITIAL:
{
sign = true;
state = LEADZEROS;
}
break;
case MANTISSA:
{
state = MANTISSADIGIT;
}
break;
default:
{
throw new Exception(
"Unexpected character '" + c + "' at position " + i
);
}
}
}
break;
case '-':
{
switch (state)
{
case INITIAL:
{
sign = false;
state = LEADZEROS;
}
break;
case MANTISSA:
{
state = MANTISSADIGIT;
}
break;
default:
{
throw new Exception(
"Unexpected character '" + c + "' at position " + i
);
}
}
}
break;
case '0':
{
switch (state)
{
case INITIAL:
case LEADZEROS:
{
// only significant if number
// is all zeros.
zeroCount++;
leadZeroCount++;
state = LEADZEROS;
}
break;
case MIDZEROS:
case DIGITS:
{
// only significant if followed
// by a decimal point or nonzero digit.
mantissa++;
zeroCount++;
state = MIDZEROS;
}
break;
case LEADZEROSDOT:
{
// only significant if number
// is all zeros.
mantissa--;
zeroCount++;
state = LEADZEROSDOT;
}
break;
case DIGITSDOT:
{
// non-leading zeros after
// a decimal point are always
// significant.
_digits.Append(c);
}
break;
case MANTISSA:
case MANTISSADIGIT:
{
foundMantissaDigit = true;
state = MANTISSADIGIT;
}
break;
default:
{
throw new Exception(
"Unexpected character '" + c + "' at position " + i
);
}
}
}
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{
switch (state)
{
case INITIAL:
case LEADZEROS:
case DIGITS:
{
zeroCount = 0;
_digits.Append(c);
mantissa++;
state = DIGITS;
}
break;
case MIDZEROS:
{
// we now know that these zeros
// are more than just trailing place holders.
for (int j = 0; j < zeroCount; j++)
{
_digits.Append('0');
}
zeroCount = 0;
_digits.Append(c);
mantissa++;
state = DIGITS;
}
break;
case LEADZEROSDOT:
case DIGITSDOT:
{
zeroCount = 0;
_digits.Append(c);
state = DIGITSDOT;
}
break;
case MANTISSA:
case MANTISSADIGIT:
{
state = MANTISSADIGIT;
foundMantissaDigit = true;
}
break;
default:
{
throw new Exception(
"Unexpected character '" + c + "' at position " + i
);
}
}
}
break;
case 'E':
case 'e':
{
switch (state)
{
case INITIAL:
case LEADZEROS:
case DIGITS:
case LEADZEROSDOT:
case DIGITSDOT:
{
// record the starting point of the mantissa
// so we can do a substring to get it back later
mantissaStart = i + 1;
state = MANTISSA;
}
break;
default:
{
throw new Exception(
"Unexpected character '" + c + "' at position " + i
);
}
}
}
break;
default:
{
throw new Exception(
"Unexpected character '" + c + "' at position " + i
);
}
}
}
if (mantissaStart != -1)
{
// if we had found an 'E'
if (!foundMantissaDigit)
{
// we didn't actually find a mantissa to go with.
throw new Exception(
"No digits in mantissa."
);
}
// parse the mantissa.
mantissa += Convert.ToInt32(number.Substring(mantissaStart));
}
if (_digits.Length == 0)
{
if (zeroCount > 0)
{
// if nothing but zeros all zeros are significant.
for (int j = 0; j < zeroCount; j++)
{
_digits.Append('0');
}
mantissa += leadZeroCount;
isZero = true;
sign = true;
}
else
{
// a hack to catch some cases that we could catch
// by adding a ton of extra states. Things like:
// "e2" "+e2" "+." "." "+" etc.
throw new Exception(
"No digits in number."
);
}
}
}
public SignificantFigures SetNumberSignificantFigures(int significantFigures)
{
if (significantFigures <= 0)
throw new ArgumentException("Desired number of significant figures must be positive.");
if (_digits != null)
{
int length = _digits.Length;
if (length < significantFigures)
{
// number is not long enough, pad it with zeros.
for (int i = length; i < significantFigures; i++)
{
_digits.Append('0');
}
}
else if (length > significantFigures)
{
// number is too long chop some of it off with rounding.
bool addOne; // we need to round up if true.
char firstInSig = _digits[significantFigures];
if (firstInSig < '5')
{
// first non-significant digit less than five, round down.
addOne = false;
}
else if (firstInSig == '5')
{
// first non-significant digit equal to five
addOne = false;
for (int i = significantFigures + 1; !addOne && i < length; i++)
{
// if its followed by any non-zero digits, round up.
if (_digits[i] != '0')
{
addOne = true;
}
}
if (!addOne)
{
// if it was not followed by non-zero digits
// if the last significant digit is odd round up
// if the last significant digit is even round down
addOne = (_digits[significantFigures - 1] & 1) == 1;
}
}
else
{
// first non-significant digit greater than five, round up.
addOne = true;
}
// loop to add one (and carry a one if added to a nine)
// to the last significant digit
for (int i = significantFigures - 1; addOne && i >= 0; i--)
{
char digit = _digits[i];
if (digit < '9')
{
_digits[i] = (char) (digit + 1);
addOne = false;
}
else
{
_digits[i] = '0';
}
}
if (addOne)
{
// if the number was all nines
_digits.Insert(0, '1');
mantissa++;
}
// chop it to the correct number of figures.
_digits.Length = significantFigures;
}
}
return this;
}
public double ToDouble()
{
return Convert.ToDouble(original);
}
public static String Format(double number, int significantFigures)
{
SignificantFigures sf = new SignificantFigures(number);
sf.SetNumberSignificantFigures(significantFigures);
return sf.ToString();
}
}
DecimalでGetBitsメソッドを使用し、BigIntegerを利用してマスキングを実行することにより、エレガントなビット完璧な丸めを取得できます。
いくつかのユーティリティ
public static int CountDigits
(BigInteger number) => ((int)BigInteger.Log10(number))+1;
private static readonly BigInteger[] BigPowers10
= Enumerable.Range(0, 100)
.Select(v => BigInteger.Pow(10, v))
.ToArray();
主な機能
public static decimal RoundToSignificantDigits
(this decimal num,
short n)
{
var bits = decimal.GetBits(num);
var u0 = unchecked((uint)bits[0]);
var u1 = unchecked((uint)bits[1]);
var u2 = unchecked((uint)bits[2]);
var i = new BigInteger(u0)
+ (new BigInteger(u1) << 32)
+ (new BigInteger(u2) << 64);
var d = CountDigits(i);
var delta = d - n;
if (delta < 0)
return num;
var scale = BigPowers10[delta];
var div = i/scale;
var rem = i%scale;
var up = rem > scale/2;
if (up)
div += 1;
var shifted = div*scale;
bits[0] =unchecked((int)(uint) (shifted & BigUnitMask));
bits[1] =unchecked((int)(uint) (shifted>>32 & BigUnitMask));
bits[2] =unchecked((int)(uint) (shifted>>64 & BigUnitMask));
return new decimal(bits);
}
テストケース0
public void RoundToSignificantDigits()
{
WMath.RoundToSignificantDigits(0.0012345m, 2).Should().Be(0.0012m);
WMath.RoundToSignificantDigits(0.0012645m, 2).Should().Be(0.0013m);
WMath.RoundToSignificantDigits(0.040000000000000008, 6).Should().Be(0.04);
WMath.RoundToSignificantDigits(0.040000010000000008, 6).Should().Be(0.04);
WMath.RoundToSignificantDigits(0.040000100000000008, 6).Should().Be(0.0400001);
WMath.RoundToSignificantDigits(0.040000110000000008, 6).Should().Be(0.0400001);
WMath.RoundToSignificantDigits(0.20000000000000004, 6).Should().Be(0.2);
WMath.RoundToSignificantDigits(0.10000000000000002, 6).Should().Be(0.1);
WMath.RoundToSignificantDigits(0.0, 6).Should().Be(0.0);
}
テストケース1
public void RoundToSigFigShouldWork()
{
1.2m.RoundToSignificantDigits(1).Should().Be(1m);
0.01235668m.RoundToSignificantDigits(3).Should().Be(0.0124m);
0.01m.RoundToSignificantDigits(3).Should().Be(0.01m);
1.23456789123456789123456789m.RoundToSignificantDigits(4)
.Should().Be(1.235m);
1.23456789123456789123456789m.RoundToSignificantDigits(16)
.Should().Be(1.234567891234568m);
1.23456789123456789123456789m.RoundToSignificantDigits(24)
.Should().Be(1.23456789123456789123457m);
1.23456789123456789123456789m.RoundToSignificantDigits(27)
.Should().Be(1.23456789123456789123456789m);
}
数値の 有効数字 を計算する際の答えは簡単です。これがコードとテスト結果です...
using System;
using System.Collections.Generic;
namespace ConsoleApplicationRound
{
class Program
{
static void Main(string[] args)
{
//char cDecimal = '.'; // for English cultures
char cDecimal = ','; // for German cultures
List<double> l_dValue = new List<double>();
ushort usSignificants = 5;
l_dValue.Add(0);
l_dValue.Add(0.000640589);
l_dValue.Add(-0.000640589);
l_dValue.Add(-123.405009);
l_dValue.Add(123.405009);
l_dValue.Add(-540);
l_dValue.Add(540);
l_dValue.Add(-540911);
l_dValue.Add(540911);
l_dValue.Add(-118.2);
l_dValue.Add(118.2);
l_dValue.Add(-118.18);
l_dValue.Add(118.18);
l_dValue.Add(-118.188);
l_dValue.Add(118.188);
foreach (double d in l_dValue)
{
Console.WriteLine("d = Maths.Round('" +
cDecimal + "', " + d + ", " + usSignificants +
") = " + Maths.Round(
cDecimal, d, usSignificants));
}
Console.Read();
}
}
}
使用される数学クラスは次のとおりです。
using System;
using System.Text;
namespace ConsoleApplicationRound
{
class Maths
{
/// <summary>
/// The Word "Window"
/// </summary>
private static String m_strZeros = "000000000000000000000000000000000";
/// <summary>
/// The minus sign
/// </summary>
public const char m_cDASH = '-';
/// <summary>
/// Determines the number of digits before the decimal point
/// </summary>
/// <param name="cDecimal">
/// Language-specific decimal separator
/// </param>
/// <param name="strValue">
/// Value to be scrutinised
/// </param>
/// <returns>
/// Nr. of digits before the decimal point
/// </returns>
private static ushort NrOfDigitsBeforeDecimal(char cDecimal, String strValue)
{
short sDecimalPosition = (short)strValue.IndexOf(cDecimal);
ushort usSignificantDigits = 0;
if (sDecimalPosition >= 0)
{
strValue = strValue.Substring(0, sDecimalPosition + 1);
}
for (ushort us = 0; us < strValue.Length; us++)
{
if (strValue[us] != m_cDASH) usSignificantDigits++;
if (strValue[us] == cDecimal)
{
usSignificantDigits--;
break;
}
}
return usSignificantDigits;
}
/// <summary>
/// Rounds to a fixed number of significant digits
/// </summary>
/// <param name="d">
/// Number to be rounded
/// </param>
/// <param name="usSignificants">
/// Requested significant digits
/// </param>
/// <returns>
/// The rounded number
/// </returns>
public static String Round(char cDecimal,
double d,
ushort usSignificants)
{
StringBuilder value = new StringBuilder(Convert.ToString(d));
short sDecimalPosition = (short)value.ToString().IndexOf(cDecimal);
ushort usAfterDecimal = 0;
ushort usDigitsBeforeDecimalPoint =
NrOfDigitsBeforeDecimal(cDecimal, value.ToString());
if (usDigitsBeforeDecimalPoint == 1)
{
usAfterDecimal = (d == 0)
? usSignificants
: (ushort)(value.Length - sDecimalPosition - 2);
}
else
{
if (usSignificants >= usDigitsBeforeDecimalPoint)
{
usAfterDecimal =
(ushort)(usSignificants - usDigitsBeforeDecimalPoint);
}
else
{
double dPower = Math.Pow(10,
usDigitsBeforeDecimalPoint - usSignificants);
d = dPower*(long)(d/dPower);
}
}
double dRounded = Math.Round(d, usAfterDecimal);
StringBuilder result = new StringBuilder();
result.Append(dRounded);
ushort usDigits = (ushort)result.ToString().Replace(
Convert.ToString(cDecimal), "").Replace(
Convert.ToString(m_cDASH), "").Length;
// Add lagging zeros, if necessary:
if (usDigits < usSignificants)
{
if (usAfterDecimal != 0)
{
if (result.ToString().IndexOf(cDecimal) == -1)
{
result.Append(cDecimal);
}
int i = (d == 0) ? 0 : Math.Min(0, usDigits - usSignificants);
result.Append(m_strZeros.Substring(0, usAfterDecimal + i));
}
}
return result.ToString();
}
}
}
短いコードで答えはありますか?
次のコードは、小数点の左側で何も丸めようとしないため、仕様を完全に満たしていません。しかし、ここで紹介する他の何よりも簡単です(これまでのところ)。 C#にはこれを処理するための組み込みメソッドがないことにかなり驚きました。
static public string SignificantDigits(double d, int digits=10)
{
int magnitude = (d == 0.0) ? 0 : (int)Math.Floor(Math.Log10(Math.Abs(d))) + 1;
digits -= magnitude;
if (digits < 0)
digits = 0;
string fmt = "f" + digits.ToString();
return d.ToString(fmt);
}
私は この記事 でそれを簡単に検索しているのを見つけました。基本的にこれは文字列に変換され、最大に達するまで、その配列の文字を1つずつ処理します。意義。これは機能しますか?