Show formatted decimal rounding to specific amount of digits [duplicate] - c#
If I have a double (234.004223), etc., I would like to round this to x significant digits in C#.
So far I can only find ways to round to x decimal places, but this simply removes the precision if there are any 0s in the number.
For example, 0.086 to one decimal place becomes 0.1, but I would like it to stay at 0.08.
The framework doesn't have a built-in function to round (or truncate, as in your example) to a number of significant digits. One way you can do this, though, is to scale your number so that your first significant digit is right after the decimal point, round (or truncate), then scale back. The following code should do the trick:
static double RoundToSignificantDigits(this double d, int digits){
if(d == 0)
return 0;
double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1);
return scale * Math.Round(d / scale, digits);
}
If, as in your example, you really want to truncate, then you want:
static double TruncateToSignificantDigits(this double d, int digits){
if(d == 0)
return 0;
double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1 - digits);
return scale * Math.Truncate(d / scale);
}
I've been using pDaddy's sigfig function for a few months and found a bug in it. You cannot take the Log of a negative number, so if d is negative the results is NaN.
The following corrects the bug:
public static double SetSigFigs(double d, int digits)
{
if(d == 0)
return 0;
decimal scale = (decimal)Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1);
return (double) (scale * Math.Round((decimal)d / scale, digits));
}
It sounds to me like you don't want to round to x decimal places at all - you want to round to x significant digits. So in your example, you want to round 0.086 to one significant digit, not one decimal place.
Now, using a double and rounding to a number of significant digits is problematic to start with, due to the way doubles are stored. For instance, you could round 0.12 to something close to 0.1, but 0.1 isn't exactly representable as a double. Are you sure you shouldn't actually be using a decimal? Alternatively, is this actually for display purposes? If it's for display purposes, I suspect you should actually convert the double directly to a string with the relevant number of significant digits.
If you can answer those points, I can try to come up with some appropriate code. Awful as it sounds, converting to a number of significant digits as a string by converting the number to a "full" string and then finding the first significant digit (and then taking appropriate rounding action after that) may well be the best way to go.
If it is for display purposes (as you state in the comment to Jon Skeet's answer), you should use Gn format specifier. Where n is the number of significant digits - exactly what you are after.
Here is the the example of usage if you want 3 significant digits (printed output is in the comment of each line):
Console.WriteLine(1.2345e-10.ToString("G3"));//1.23E-10
Console.WriteLine(1.2345e-5.ToString("G3")); //1.23E-05
Console.WriteLine(1.2345e-4.ToString("G3")); //0.000123
Console.WriteLine(1.2345e-3.ToString("G3")); //0.00123
Console.WriteLine(1.2345e-2.ToString("G3")); //0.0123
Console.WriteLine(1.2345e-1.ToString("G3")); //0.123
Console.WriteLine(1.2345e2.ToString("G3")); //123
Console.WriteLine(1.2345e3.ToString("G3")); //1.23E+03
Console.WriteLine(1.2345e4.ToString("G3")); //1.23E+04
Console.WriteLine(1.2345e5.ToString("G3")); //1.23E+05
Console.WriteLine(1.2345e10.ToString("G3")); //1.23E+10
I found two bugs in the methods of P Daddy and Eric. This solves for example the precision error that was presented by Andrew Hancox in this Q&A. There was also a problem with round directions. 1050 with two significant figures isn't 1000.0, it's 1100.0. The rounding was fixed with MidpointRounding.AwayFromZero.
static void Main(string[] args) {
double x = RoundToSignificantDigits(1050, 2); // Old = 1000.0, New = 1100.0
double y = RoundToSignificantDigits(5084611353.0, 4); // Old = 5084999999.999999, New = 5085000000.0
double z = RoundToSignificantDigits(50.846, 4); // Old = 50.849999999999994, New = 50.85
}
static double RoundToSignificantDigits(double d, int digits) {
if (d == 0.0) {
return 0.0;
}
else {
double leftSideNumbers = Math.Floor(Math.Log10(Math.Abs(d))) + 1;
double scale = Math.Pow(10, leftSideNumbers);
double result = scale * Math.Round(d / scale, digits, MidpointRounding.AwayFromZero);
// Clean possible precision error.
if ((int)leftSideNumbers >= digits) {
return Math.Round(result, 0, MidpointRounding.AwayFromZero);
}
else {
return Math.Round(result, digits - (int)leftSideNumbers, MidpointRounding.AwayFromZero);
}
}
}
As Jon Skeet mentions: better handle this in the textual domain. As a rule: for display purposes, don't try to round / change your floating point values, it never quite works 100%. Display is a secondary concern and you should handle any special formatting requirements like these working with strings.
My solution below I implemented several years ago and has proven very reliable. It has been thoroughly tested and it performs quite well also. About 5 times longer in execution time than P Daddy / Eric's solution.
Examples of input + output given below in code.
using System;
using System.Text;
namespace KZ.SigDig
{
public static class SignificantDigits
{
public static string DecimalSeparator;
static SignificantDigits()
{
System.Globalization.CultureInfo ci = System.Threading.Thread.CurrentThread.CurrentCulture;
DecimalSeparator = ci.NumberFormat.NumberDecimalSeparator;
}
/// <summary>
/// Format a double to a given number of significant digits.
/// </summary>
/// <example>
/// 0.086 -> "0.09" (digits = 1)
/// 0.00030908 -> "0.00031" (digits = 2)
/// 1239451.0 -> "1240000" (digits = 3)
/// 5084611353.0 -> "5085000000" (digits = 4)
/// 0.00000000000000000846113537656557 -> "0.00000000000000000846114" (digits = 6)
/// 50.8437 -> "50.84" (digits = 4)
/// 50.846 -> "50.85" (digits = 4)
/// 990.0 -> "1000" (digits = 1)
/// -5488.0 -> "-5000" (digits = 1)
/// -990.0 -> "-1000" (digits = 1)
/// 0.0000789 -> "0.000079" (digits = 2)
/// </example>
public static string Format(double number, int digits, bool showTrailingZeros = true, bool alwaysShowDecimalSeparator = false)
{
if (Double.IsNaN(number) ||
Double.IsInfinity(number))
{
return number.ToString();
}
string sSign = "";
string sBefore = "0"; // Before the decimal separator
string sAfter = ""; // After the decimal separator
if (number != 0d)
{
if (digits < 1)
{
throw new ArgumentException("The digits parameter must be greater than zero.");
}
if (number < 0d)
{
sSign = "-";
number = Math.Abs(number);
}
// Use scientific formatting as an intermediate step
string sFormatString = "{0:" + new String('#', digits) + "E0}";
string sScientific = String.Format(sFormatString, number);
string sSignificand = sScientific.Substring(0, digits);
int exponent = Int32.Parse(sScientific.Substring(digits + 1));
// (the significand now already contains the requested number of digits with no decimal separator in it)
StringBuilder sFractionalBreakup = new StringBuilder(sSignificand);
if (!showTrailingZeros)
{
while (sFractionalBreakup[sFractionalBreakup.Length - 1] == '0')
{
sFractionalBreakup.Length--;
exponent++;
}
}
// Place decimal separator (insert zeros if necessary)
int separatorPosition = 0;
if ((sFractionalBreakup.Length + exponent) < 1)
{
sFractionalBreakup.Insert(0, "0", 1 - sFractionalBreakup.Length - exponent);
separatorPosition = 1;
}
else if (exponent > 0)
{
sFractionalBreakup.Append('0', exponent);
separatorPosition = sFractionalBreakup.Length;
}
else
{
separatorPosition = sFractionalBreakup.Length + exponent;
}
sBefore = sFractionalBreakup.ToString();
if (separatorPosition < sBefore.Length)
{
sAfter = sBefore.Substring(separatorPosition);
sBefore = sBefore.Remove(separatorPosition);
}
}
string sReturnValue = sSign + sBefore;
if (sAfter == "")
{
if (alwaysShowDecimalSeparator)
{
sReturnValue += DecimalSeparator + "0";
}
}
else
{
sReturnValue += DecimalSeparator + sAfter;
}
return sReturnValue;
}
}
}
Math.Round() on doubles is flawed (see Notes to Callers in its documentation). The later step of multiplying the rounded number back up by its decimal exponent will introduce further floating point errors in the trailing digits. Using another Round() as #Rowanto does won't reliably help and suffers from other problems. However if you're willing to go via decimal then Math.Round() is reliable, as is multiplying and dividing by powers of 10:
static ClassName()
{
powersOf10 = new decimal[28 + 1 + 28];
powersOf10[28] = 1;
decimal pup = 1, pdown = 1;
for (int i = 1; i < 29; i++) {
pup *= 10;
powersOf10[i + 28] = pup;
pdown /= 10;
powersOf10[28 - i] = pdown;
}
}
/// <summary>Powers of 10 indexed by power+28. These are all the powers
/// of 10 that can be represented using decimal.</summary>
static decimal[] powersOf10;
static double RoundToSignificantDigits(double v, int digits)
{
if (v == 0.0 || Double.IsNaN(v) || Double.IsInfinity(v)) {
return v;
} else {
int decimal_exponent = (int)Math.Floor(Math.Log10(Math.Abs(v))) + 1;
if (decimal_exponent < -28 + digits || decimal_exponent > 28 - digits) {
// Decimals won't help outside their range of representation.
// Insert flawed Double solutions here if you like.
return v;
} else {
decimal d = (decimal)v;
decimal scale = powersOf10[decimal_exponent + 28];
return (double)(scale * Math.Round(d / scale, digits, MidpointRounding.AwayFromZero));
}
}
}
I agree with the spirit of Jon's assessment:
Awful as it sounds, converting to a number of significant digits as a string by converting the number to a "full" string and then finding the first significant digit (and then taking appropriate rounding action after that) may well be the best way to go.
I needed significant-digit rounding for approximate and non-performance-critical computational purposes, and the format-parse round-trip through "G" format is good enough:
public static double RoundToSignificantDigits(this double value, int numberOfSignificantDigits)
{
return double.Parse(value.ToString("G" + numberOfSignificantDigits));
}
This question is similiar to the one you're asking:
Formatting numbers with significant figures in C#
Thus you could do the following:
double Input2 = 234.004223;
string Result2 = Math.Floor(Input2) + Convert.ToDouble(String.Format("{0:G1}", Input2 - Math.Floor(Input2))).ToString("R6");
Rounded to 1 significant digit.
Let inputNumber be input that needs to be converted with significantDigitsRequired after decimal point, then significantDigitsResult is the answer to the following pseudo code.
integerPortion = Math.truncate(**inputNumber**)
decimalPortion = myNumber-IntegerPortion
if( decimalPortion <> 0 )
{
significantDigitsStartFrom = Math.Ceil(-log10(decimalPortion))
scaleRequiredForTruncation= Math.Pow(10,significantDigitsStartFrom-1+**significantDigitsRequired**)
**siginficantDigitsResult** = integerPortion + ( Math.Truncate (decimalPortion*scaleRequiredForTruncation))/scaleRequiredForTruncation
}
else
{
**siginficantDigitsResult** = integerPortion
}
Tested on .NET 6.0
In my opinion, the rounded results are inconsistent due to the defects of the framework and the error of the floating point. Therefore, be careful about use.
decimal.Parse(doubleValue.ToString("E"), NumberStyles.Float);
example:
using System.Diagnostics;
using System.Globalization;
List<double> doubleList = new();
doubleList.Add( 0.012345);
doubleList.Add( 0.12345 );
doubleList.Add( 1.2345 );
doubleList.Add( 12.345 );
doubleList.Add( 123.45 );
doubleList.Add( 1234.5 );
doubleList.Add(12345 );
doubleList.Add(10 );
doubleList.Add( 0 );
doubleList.Add( 1 );
doubleList.Add(-1 );
doubleList.Add( 0.1);
Debug.WriteLine("");
foreach (var item in doubleList)
{
Debug.WriteLine(decimal.Parse(item.ToString("E2"), NumberStyles.Float));
// 0.0123
// 0.123
// 1.23
// 12.3
// 123
// 1230
// 12300
// 10.0
// 0.00
// 1.00
// -1.00
// 0.100
}
Debug.WriteLine("");
foreach (var item in doubleList)
{
Debug.WriteLine(decimal.Parse(item.ToString("E3"), NumberStyles.Float));
// 0.01235
// 0.1235
// 1.234
// 12.35
// 123.5
// 1234
// 12340
// 10.00
// 0.000
// 1.000
// -1.000
// 0.1000
}
As pointed out by #Oliver Bock is that Math.Round() on doubles is flawed (see Notes to Callers in its documentation). The later step of multiplying the rounded number back up by its decimal exponent will introduce further floating point errors in the trailing digits. Generally, any multiplication by or division by a power of ten gives a non-exact result, since floating-point is typically represented in binary, not in decimal.
Using the following function will avoid floating point errors in the trailing digits:
static double RoundToSignificantDigits(double d, int digits)
{
if (d == 0.0 || Double.IsNaN(d) || Double.IsInfinity(d))
{
return d;
}
// Compute shift of the decimal point.
int shift = digits - 1 - (int)Math.Floor(Math.Log10(Math.Abs(d)));
// Return if rounding to the same or higher precision.
int decimalPlaces = 0;
for (long pow = 1; Math.Floor(d * pow) != (d * pow); pow *= 10) decimalPlaces++;
if (shift >= decimalPlaces)
return d;
// Round to sf-1 fractional digits of normalized mantissa x.dddd
double scale = Math.Pow(10, Math.Abs(shift));
return shift > 0 ?
Math.Round(d * scale, MidpointRounding.AwayFromZero) / scale :
Math.Round(d / scale, MidpointRounding.AwayFromZero) * scale;
}
However if you're willing to go via decimal then Math.Round() is reliable, as is multiplying and dividing by powers of 10:
static double RoundToSignificantDigits(double d, int digits)
{
if (d == 0.0 || Double.IsNaN(d) || Double.IsInfinity(d))
{
return d;
}
decimal scale = (decimal)Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1);
return (double)(scale * Math.Round((decimal)d / scale, digits, MidpointRounding.AwayFromZero));
}
Console.WriteLine("{0:G17}", RoundToSignificantDigits(5.015 * 100, 15)); // 501.5
for me, this one works pretty fine and is also valid for negative numbers:
public static double RoundToSignificantDigits(double number, int digits)
{
int sign = Math.Sign(number);
if (sign < 0)
number *= -1;
if (number == 0)
return 0;
double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(number))) + 1);
return sign * scale * Math.Round(number / scale, digits);
}
My solution may be helpful in some cases, I use it to display crypto prices which vary greatly in magnitude - it always gives me a specified number of significant figures but unlike ToString("G[number of digits]") it doesn't show small values in scientific notation (don't know a way to avoid this with ToString(), if there is then please let me know!)
const int MIN_SIG_FIGS = 6; //will be one more for < 0
int numZeros = (int)Math.Floor(Math.Log10(Math.Abs(price))); //get number of zeros before first digit, will be negative for price > 0
int decPlaces = numZeros < MIN_SIG_FIGS
? MIN_SIG_FIGS - numZeros < 0
? 0
: MIN_SIG_FIGS - numZeros
: 0; //dec. places: set to MIN_SIG_FIGS + number of zeros, unless numZeros greater than sig figs then no decimal places
return price.ToString($"F{decPlaces}");
Here's a version inspired by Peter Mortensen that adds a couple of safeguards for edge cases such as value being NaN, Inf or very small:
public static double RoundToSignificantDigits(this double value, int digits)
{
if (double.IsNaN(value) || double.IsInfinity(value))
return value;
if (value == 0.0)
return 0.0;
double leftSideNumbers = Math.Floor(Math.Log10(Math.Abs(value))) + 1;
int places = digits - (int)leftSideNumbers;
if (places > 15)
return 0.0;
double scale = Math.Pow(10, leftSideNumbers);
double result = scale * Math.Round(value / scale, digits, MidpointRounding.AwayFromZero);
if (places < 0)
places = 0;
return Math.Round(result, places, MidpointRounding.AwayFromZero);
}
I just did:
int integer1 = Math.Round(double you want to round,
significant figures you want to round to)
Here is something I did in C++
/*
I had this same problem I was writing a design sheet and
the standard values were rounded. So not to give my
values an advantage in a later comparison I need the
number rounded, so I wrote this bit of code.
It will round any double to a given number of significant
figures. But I have a limited range written into the
subroutine. This is to save time as my numbers were not
very large or very small. But you can easily change that
to the full double range, but it will take more time.
Ross Mckinstray
rmckinstray01#gmail.com
*/
#include <iostream>
#include <fstream>
#include <string>
#include <math.h>
#include <cmath>
#include <iomanip>
#using namespace std;
double round_off(double input, int places) {
double roundA;
double range = pow(10, 10); // This limits the range of the rounder to 10/10^10 - 10*10^10 if you want more change range;
for (double j = 10/range; j< 10*range;) {
if (input >= j && input < j*10){
double figures = pow(10, places)/10;
roundA = roundf(input/(j/figures))*(j/figures);
}
j = j*10;
}
cout << "\n in sub after loop";
if (input <= 10/(10*10) && input >= 10*10) {
roundA = input;
cout << "\nDID NOT ROUND change range";
}
return roundA;
}
int main() {
double number, sig_fig;
do {
cout << "\nEnter number ";
cin >> number;
cout << "\nEnter sig_fig ";
cin >> sig_fig;
double output = round_off(number, sig_fig);
cout << setprecision(10);
cout << "\n I= " << number;
cout << "\n r= " <<output;
cout << "\nEnter 0 as number to exit loop";
}
while (number != 0);
return 0;
}
Hopefully I did not change anything formatting it.
Related
Specify a starting index for continuation of calculating Pi
This C# code will calculate Pi to whatever length I specify. I want to be able to start at a given index without recalculating to that point. Precision is not a great concern as this is a puzzle project but I do need this code to reproduce the same results over and over. It works fine as is but I haven't been able to figure out how to modify for a starting point. //Looking to pass BigInteger to specify a starting index for continuation of calculating Pi public static BigInteger GetPi(int digits, int iterations) { return 16 * ArcTan1OverX(5, digits).ElementAt(iterations) - 4 * ArcTan1OverX(239, digits).ElementAt(iterations); } public static IEnumerable<BigInteger> ArcTan1OverX(int x, int digits) { var mag = BigInteger.Pow(10, digits); var sum = BigInteger.Zero; bool sign = true; for (int i = 1; true; i += 2) { var cur = mag / (BigInteger.Pow(x, i) * i); if (sign) { sum += cur; } else { sum -= cur; } yield return sum; sign = !sign; } }
You are using the Machin formula with the Taylor serie expansion for Arctan. It should give you about 1.4 digits of precision for each "cycle" (see here). You can't "shortcut" the calculation of the Taylor serie. You can speed-up a little the program removing the IEnumerable<BigInteger> part and simply returning the nth iteration (the yield instruction has a cost) and by changing the BigInteger.Pow with a fixed multiplication. But the calculation will still be made iteratively. There is no known way for calculating PI with a precision of n digits in O(1) time. Note that there are algorithms (see the wiki) that converge in a smaller number of cycles, but I'm not sure if they converge in a smaller number of operations (their cycles are much more complex). An optimized version of the code: public static BigInteger GetPi2(int digits, int iterations) { return 16 * ArcTan1OverX2(5, digits, iterations) - 4 * ArcTan1OverX2(239, digits, iterations); } public static BigInteger ArcTan1OverX2(int x, int digits, int iterations) { var mag = BigInteger.Pow(10, digits); var sum = BigInteger.Zero; bool sign = true; int imax = 1 + (2 * iterations); int xsquared = x * x; BigInteger pow = x; for (int i = 1; i <= imax; i += 2) { if (i != 1) { pow *= xsquared; } var cur = mag / (pow * i); if (sign) { sum += cur; } else { sum -= cur; } sign = !sign; } return sum; }
Unit test function that enforces specific decimal precision level
I'm writing software for counting preferential multi-seat elections. One common requirement is fixed precision. This means that all math operations must be done on values with a fixed specified precision and the result must have the same precision. Fixed precision means a set number of digits after the decimal. Any digits after that are discarded. So if we assume 5 digits of precision: 42/139 becomes: 42.00000/139.00000 = 0.30215 I'm having problems writing unit tests for this. So far I've written these two tests for big and small numbers. public void TestPrecisionBig() { PRECISION = 5; decimal d = Precision(1987.7845263487169386183643876m); Assert.That(d == 1987.78452m); } public void TestPrecisionSmall() { PRECISION = 5; decimal d = Precision(42); Assert.That(d == 42.00000m); } But it evaluates to 42 == 42.00000m Not what I want. How do I test this? I guess I could do a d.ToString, but would that be a good "proper" test? Edit: I was asked to show my implementation of the Precision method. It's not very elegant, but it works. public static decimal Precision(decimal d) { if (d == 0) return 0.00000m; decimal output = Math.Round(d, 6); string s = output.ToString(CurrentCulture); char c = char.Parse(CurrentCulture.NumberFormat.NumberDecimalSeparator); if (s.Contains(c)) { output = decimal.Parse(s.Substring(0, s.Length - 1)); return output; } s += c; for (int i = 0; i <= Constants.PRECISION; i++) s += '0'; output = decimal.Parse(s.Substring(0, s.IndexOf(c) + Constants.PRECISION + 1)); return output; } Now I'll probably see if I can't just set the exponent directly. Edit 2: New bit-jugling precision method public static decimal Precision(decimal d) { if (d == 0) return 0.00000m; string exponent = System.Convert.ToString(Constants.PRECISION, 2); exponent = exponent.PadLeft(8, '0'); int positive = Convert.ToInt32("00000000" + exponent + "0000000000000000", 2); int negative = Convert.ToInt32("10000000" + exponent + "0000000000000000", 2); int preScaler = (int)Math.Pow(10, Constants.PRECISION); d *= preScaler; d = decimal.Truncate(d); int[] bits = decimal.GetBits(d); bits[3] = (bits[3] & 0x80000000) == 0 ? positive : negative; return new decimal(bits); }
You can use this function to determine the precision of a decimal: public int GetPrecision(decimal d) { return (Decimal.GetBits(d)[3] >> 16) & 0x000000FF; // bits 16-23 } So then your test would be something like: public void TestPrecisionSmall() { PRECISION = 5; decimal d = Precision(42); Assert.That(GetPrecision(d) == PRECISION); // or >= if that's more appropriate }
I ended up doing public void TestPrecisionSmall() { PRECISION = 5; decimal d = Precision(42); Assert.AreEqual(decimal.GetBits(d), decimal.GetBits(42.00000m)); }
Summing infinite series 1/n
I just started taking my first steps in learning coding and general (starting with c#)and I'm learning from a book currently. The book leaves questions at the end of every chapter. I'm currently unsure on how to proceed with this specific question. The question is as follows: Question: Write a program that calculates the sum (with precision of 0.001) of the following sequence: 1 + 1/2 - 1/3 + 1/4 - 1/5 + … 1/n The book has given the following guidelines for this problem: Guide Lines: Accumulate the sum of the sequence in a variable inside a while-loop (see the chapter "Loops"). At each step compare the old sum with the new sum. If the difference between the two sums Math.Abs(current_sum – old_sum) is less than the required precision (0.001), the calculation should finish because the difference is constantly decreasing and the precision is constantly increasing at each step of the loop. The expected result is 1.307 I have an idea on how to implement this but I do not know how or where to initiate and break the loop when the sum has reached the required precision. I currently use user input to enter n. I would like to know how to automate this process. Here is my code so far. I know its a cop out to use the format {N:2} but i am not sure how to proceed. Would very much appreciate the help! Thanks! using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Demo { class Program { static void Main() { Console.Write("Please enter n: "); double counter = double.Parse(Console.ReadLine()); double sum = 1 + AddSum(counter); // calculate infinite sum Console.WriteLine("Sum = {0:N3}", sum); } static double AddSum(double n) { double a = 0; for (double i = 1; i < n; i++) { if(i % 2 == 0) { a -= 1 / (i +1); // calculates negative fractions } else { a += 1 / (i +1); // calculates positive fractions } } return a; } }
Here's an example that doesn't suffer from subtractive cancellation: static double AddSum() { double pos = 1.0; double neg = 0.0; double delta = 0.001; double current = pos + neg; double previous = pos + 2.0 * delta; int i = 2; while (Math.Abs(current - previous) >= delta) { if (i % 2 == 0) { pos += 1.0 / i; } else { neg -= 1.0 / i; } previous = current; current = pos + neg; i++; } return current; }
You may want to follow the given guideline: don't enter n as the program shall finish automatically. Where is the suggested while loop? You'll figure the answer out quickly yourself then :-) Maybe start with this: static void Main() { decimal result = 1; int n = 1; do { // remember the current result result += 1 / (++n * DetermineMultiplier(n)); } while ( /* precision calculation here */ ); // print result and n } private int DetermineMultiplier(int n) { // return -1 if n is odd, 1 if it is even }
As you not want to specify n and the only exit condition is a precision check, you could do this. static double AddSum() { double a = 0; double oldvalue; int i = 1; do { oldvalue = a; a += (i % 2 == 0) ? (double)-1 / (i + 1) : (double)1 / (i + 1); i++; }while (!(i != 1 && Math.Abs(a - oldvalue) < 0.0001)); // we can remove i!=1 if we know and hard code first default value return a; } Working Example
smallest float that can be summed to another float
Assuming there's a float number with 7 digits how to find the smallest number that can be added to that float? Example 1: 1234.567f + 0000.001f = 1234.568f Example 2: 0.01234567 + 0.00000001f = 0.01234568f
OP added C# after posting of this C answer. Will leave this C solution up as a reference. Print the number out in exponential format and change each digit, to '0' or '1' float smallest_float_summable(float f, int digs) { char buf[20]; sprintf(buf, "%.*e", digs - 1, f); char *p = buf; while (*p != 'e') { if (isdigit(*p)) { *p = '0' + (p[1] == 'e'); } p++; } float y; sscanf(buf, "%e", &y); return y; } printf("%e\n", smallest_float_summable(1234.567f, 7)); // 1.000000e-03 This will not get the smallest as the typically a number near 1/2 the value of smallest_float_summable() will effect the change, yet this seems to match OP's intent. To get the smallest number that will effect some change, simple use nextafter() The nextafter functions determine the next representable value, in the type of the function, after x in the direction of y ... C11dr §7.12.11.3 2 #include <math.h> float smallest_change(float f) { float dif = f - nextafterf(f, 0); return dif; } [Edit] #aka.nice correctly points out that even smaller, maybe about 1/2 dif will effect a change. Will ponder this.
To find the smallest epsilon, you could start with 10eN where N is 1, and move down to a smaller number and add it. Then compare it to the original number. number = x N = 1 newnumber = 3 while (number <> newnumber){ newnumber = (number + 10eN) N = N - 1 } Then 10e(N+1) is the smallest epsilon.
How can I improve this square root method?
I know this sounds like a homework assignment, but it isn't. Lately I've been interested in algorithms used to perform certain mathematical operations, such as sine, square root, etc. At the moment, I'm trying to write the Babylonian method of computing square roots in C#. So far, I have this: public static double SquareRoot(double x) { if (x == 0) return 0; double r = x / 2; // this is inefficient, but I can't find a better way // to get a close estimate for the starting value of r double last = 0; int maxIters = 100; for (int i = 0; i < maxIters; i++) { r = (r + x / r) / 2; if (r == last) break; last = r; } return r; } It works just fine and produces the exact same answer as the .NET Framework's Math.Sqrt() method every time. As you can probably guess, though, it's slower than the native method (by around 800 ticks). I know this particular method will never be faster than the native method, but I'm just wondering if there are any optimizations I can make. The only optimization I saw immediately was the fact that the calculation would run 100 times, even after the answer had already been determined (at which point, r would always be the same value). So, I added a quick check to see if the newly calculated value is the same as the previously calculated value and break out of the loop. Unfortunately, it didn't make much of a difference in speed, but just seemed like the right thing to do. And before you say "Why not just use Math.Sqrt() instead?"... I'm doing this as a learning exercise and do not intend to actually use this method in any production code.
First, instead of checking for equality (r == last), you should be checking for convergence, wherein r is close to last, where close is defined by an arbitrary epsilon: eps = 1e-10 // pick any small number if (Math.Abs(r-last) < eps) break; As the wikipedia article you linked to mentions - you don't efficiently calculate square roots with Newton's method - instead, you use logarithms.
float InvSqrt (float x){ float xhalf = 0.5f*x; int i = *(int*)&x; i = 0x5f3759df - (i>>1); x = *(float*)&i; x = x*(1.5f - xhalf*x*x); return x;} This is my favorite fast square root. Actually it's the inverse of the square root, but you can invert it after if you want....I can't say if it's faster if you want the square root and not the inverse square root, but it's freaken cool just the same. http://www.beyond3d.com/content/articles/8/
What you are doing here is you execute Newton's method of finding a root. So you could just use some more efficient root-finding algorithm. You can start searching for it here.
Replacing the division by 2 with a bit shift is unlikely to make that big a difference; given that the division is by a constant I'd hope the compiler is smart enough to do that for you, but you may as well try it to see. You're much more likely to get an improvement by exiting from the loop early, so either store new r in a variable and compare with old r, or store x/r in a variable and compare that against r before doing the addition and division.
Instead of breaking the loop and then returning r, you could just return r. May not provide any noticable increase in performance.
With your method, each iteration doubles the number of correct bits. Using a table to obtain the initial 4 bits (for example), you will have 8 bits after the 1st iteration, then 16 bits after the second, and all the bits you need after the fourth iteration (since a double stores 52+1 bits of mantissa). For a table lookup, you can extract the mantissa in [0.5,1[ and exponent from the input (using a function like frexp), then normalize the mantissa in [64,256[ using multiplication by a suitable power of 2. mantissa *= 2^K exponent -= K After this, your input number is still mantissa*2^exponent. K must be 7 or 8, to obtain an even exponent. You can obtain the initial value for the iterations from a table containing all the square roots of the integral part of mantissa. Perform 4 iterations to get the square root r of mantissa. The result is r*2^(exponent/2), constructed using a function like ldexp. EDIT. I put some C++ code below to illustrate this. The OP's function sr1 with improved test takes 2.78s to compute 2^24 square roots; my function sr2 takes 1.42s, and the hardware sqrt takes 0.12s. #include <math.h> #include <stdio.h> double sr1(double x) { double last = 0; double r = x * 0.5; int maxIters = 100; for (int i = 0; i < maxIters; i++) { r = (r + x / r) / 2; if ( fabs(r - last) < 1.0e-10 ) break; last = r; } return r; } double sr2(double x) { // Square roots of values in 0..256 (rounded to nearest integer) static const int ROOTS256[] = { 0,1,1,2,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11, 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12, 12,12,12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, 13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, 14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, 15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16 }; // Normalize input int exponent; double mantissa = frexp(x,&exponent); // MANTISSA in [0.5,1[ unless X is 0 if (mantissa == 0) return 0; // X is 0 if (exponent & 1) { mantissa *= 128; exponent -= 7; } // odd exponent else { mantissa *= 256; exponent -= 8; } // even exponent // Here MANTISSA is in [64,256[ // Initial value on 4 bits double root = ROOTS256[(int)floor(mantissa)]; // Iterate for (int it=0;it<4;it++) { root = 0.5 * (root + mantissa / root); } // Restore exponent in result return ldexp(root,exponent>>1); } int main() { // Used to generate the table // for (int i=0;i<=256;i++) printf(",%.0f",sqrt(i)); double s = 0; int mx = 1<<24; // for (int i=0;i<mx;i++) s += sqrt(i); // 0.120s // for (int i=0;i<mx;i++) s += sr1(i); // 2.780s for (int i=0;i<mx;i++) s += sr2(i); // 1.420s }
Define a tolerance and return early when subsequent iterations fall within that tolerance.
Since you said the code below was not fast enough, try this: static double guess(double n) { return Math.Pow(10, Math.Log10(n) / 2); } It should be very accurate and hopefully fast. Here is code for the initial estimate described here. It appears to be pretty good. Use this code, and then you should also iterate until the values converge within an epsilon of difference. public static double digits(double x) { double n = Math.Floor(x); double d; if (d >= 1.0) { for (d = 1; n >= 1.0; ++d) { n = n / 10; } } else { for (d = 1; n < 1.0; ++d) { n = n * 10; } } return d; } public static double guess(double x) { double output; double d = Program.digits(x); if (d % 2 == 0) { output = 6*Math.Pow(10, (d - 2) / 2); } else { output = 2*Math.Pow(10, (d - 1) / 2); } return output; }
I have been looking at this as well for learning purposes. You may be interested in two modifications I tried. The first was to use a first order taylor series approximation in x0: Func<double, double> fNewton = (b) => { // Use first order taylor expansion for initial guess // http://www27.wolframalpha.com/input/?i=series+expansion+x^.5 double x0 = 1 + (b - 1) / 2; double xn = x0; do { x0 = xn; xn = (x0 + b / x0) / 2; } while (Math.Abs(xn - x0) > Double.Epsilon); return xn; }; The second was to try a third order (more expensive), iterate Func<double, double> fNewtonThird = (b) => { double x0 = b/2; double xn = x0; do { x0 = xn; xn = (x0*(x0*x0+3*b))/(3*x0*x0+b); } while (Math.Abs(xn - x0) > Double.Epsilon); return xn; }; I created a helper method to time the functions public static class Helper { public static long Time( this Func<double, double> f, double testValue) { int imax = 120000; double avg = 0.0; Stopwatch st = new Stopwatch(); for (int i = 0; i < imax; i++) { // note the timing is strictly on the function st.Start(); var t = f(testValue); st.Stop(); avg = (avg * i + t) / (i + 1); } Console.WriteLine("Average Val: {0}",avg); return st.ElapsedTicks/imax; } } The original method was faster, but again, might be interesting :)
Replacing "/ 2" by "* 0.5" makes this ~1.5 times faster on my machine, but of course not nearly as fast as the native implementation.
Well, the native Sqrt() function probably isn't implemented in C#, it'll most likely be done in a low-level language, and it'll certainly be using a more efficient algorithm. So trying to match its speed is probably futile. However, in regard to just trying to optimize your function for the heckuvit, the Wikipedia page you linked recommends the "starting guess" to be 2^floor(D/2), where D represents the number of binary digits in the number. You could give that an attempt, I don't see much else that could be optimized significantly in your code.
You can try r = x >> 1; instead of / 2 (also in the other place you device by 2). It might give you a slight edge. I would also move the 100 into the loop. Probably nothing, but we are talking about ticks in here. just checking it now. EDIT: Fixed the > into >>, but it doesn't work for doubles, so nevermind. the inlining of the 100 gave me no speed increase.