Esto puede parecer una duplicación, pero no pude encontrar una respuesta adecuada (las preguntas estaban lo suficientemente cerca pero...) Tengo una cadena que representa un número decimal, que siempre tiene muchos lugares decimales, al menos 20, a veces hasta 2000 ( representa cálculos de verificación específicos, es decir, como 'son dígitos 135 a 147, número primo X, etc., solo para darle un poco de contexto)
Por ejemplo:
123.829743892473218762329384241002373824970132871283923423961723816273823447623528347662123874999Estoy tratando de redondear al penúltimo dígito. Construí un pequeño método que (más o menos) funciona. PERO (como en el ejemplo anterior).. si el último dígito es >4 y el penúltimo dígito es 9, esto significa que debo aumentar también el dígito anterior y cortar el último 0 y si el número anterior también es 9, eso significa que debo subir también el dígito anterior, y así sucesivamente.
Ex.
123.99999 // must become 124 123.823467283762378 // must become 123.82346728376238 123.823467283762398 // must become 123.8234672837624 (notice the last 0 has gone) 123.09999999 // must become 123.1 (notice no trailing zeros needed..) 122.00000009 // must become 122.0000001 124.81379281 // must become simply 124.8137928 129.07872345 // must become 129.0787235 129.07872344 // must become 129.0787234y así sucesivamente y así sucesivamente. En otras palabras, es solo el redondeo del número (¡que es una cadena!) Cortando solo el último dígito, pero continúa el redondeo hacia la izquierda hasta que no sea necesario. El redondeo es necesario solo para el último lugar decimal (no si el número es un número entero) y la regla es que si el dígito en el último lugar es> 4, entonces el dígito se corta y el dígito anterior (a la izquierda) se eleva en 1, ignorando el cero final si lo hay, y la regla continúa hasta el último dígito de la parte entera (es decir, 123,99999 se convierte en 124, pero el entero 124 permanece como está, etc.).
¿Alguien podría ayudarme a construir una extensión de cadena para esto?
using System; public class Example { public string round(string LargeDecimal) { Console.WriteLine("Number as string is: " + LargeDecimal); int lastDigit = (int)char.GetNumericValue(LargeDecimal[LargeDecimal.Length -1]); // get last character Console.WriteLine("lastDigit = " + lastDigit.ToString()); string number = LargeDecimal.Remove(LargeDecimal.Length - 1); // delete last character Console.WriteLine("Now number is " + number); if (lastDigit > 4) { Console.WriteLine("Last digit {0} was >4", lastDigit.ToString()); int newLastDigit = (int)char.GetNumericValue(number[number.Length -1]); Console.WriteLine("Next to left last digit is {0} which will be raised by 1 and become {1}", newLastDigit.ToString(), (newLastDigit +1).ToString()); newLastDigit += 1; //increase by one number = number.Remove(number.Length - 1); // delete ex-penultimate (and now last) character number = number + newLastDigit.ToString(); return number; // and add a digit increased by 1 } else { return LargeDecimal.Remove(LargeDecimal.Length - 2); } } public Example() {} } public class Program { public static void Main(string[] args) { string myNumber = "124.2398478278268985738276523548769"; Example myExample = new Example(); string result = myExample.round(myNumber); Console.WriteLine("Now I have " + result); } }Aquí hay una solución:
public static decimal RoundLastChar(this string input) { decimal inputDecimal = Convert.ToDecimal(input, new CultureInfo("en-US")); int decimalPlaces = BitConverter.GetBytes(decimal.GetBits(inputDecimal)[3])[2]; if (decimalPlaces == 0) return inputDecimal; decimal result = Math.Round(inputDecimal, decimalPlaces - 1, MidpointRounding.AwayFromZero).Normalize(); return result; } public static decimal Normalize(this decimal value) { return value / 1.000000000000000000000000000000000m; }El método para obtener el número de lugares decimales viene de aquí , y el método para Normalizar un decimal viene de aquí .
Debido a que su valor de entrada es un tipo de cadena, usaría decimal.TryParse para asegurarme de que la entrada sea un número decimal válido.
Luego, puede intentar usar un algoritmo simple para calcular la longitud flotante a partir de su entrada y luego hacer Math.Round
static decimal RoundFirstSignificantDigit(string input) { decimal significantDigit; if (!decimal.TryParse(input,out significantDigit)) { throw new ArgumentException("Invalid input!!"); } var floatLength = input.Split('.')[1].Length; return Math.Round(significantDigit, floatLength - 1, MidpointRounding.AwayFromZero); }Editar
si su entrada con decimales grandes, debido a c # decimal solo permite el rango entre ±1.0 × 10-28 to ±7.9228 × 1028 y no es compatible BigDecimal como Java actualmente.
Creo que hay dos formas de hacerlo.
BigDecimal .BigDecimal desde Java.Puede hacer su expectativa por IKVM simplemente.
static string RoundFirstSignificantDigit(string input) { BigDecimal significantDigit = new BigDecimal(input); var floatLength = input.Split('.')[1].Length; return significantDigit.setScale(floatLength - 1, BigDecimal.ROUND_HALF_UP).toString().TrimEnd('0'); }Bueno, esto es complicado porque con 2000 lugares decimales no puedes usar decimal . Entonces, tal vez haya algo más fácil que esto, pero podría funcionar para usted ( demostración de .NET fiddle ):
public static string RoundLongNumber(this string input) { if (!ValidLongNumber(input, out bool isInteger) || isInteger) return input; int index = input.IndexOf('.'); string part1 = input.Remove(index); string part2 = input.Substring(index + 1); StringBuilder sb = new StringBuilder(part2); while(true) { if (LastInt() <= 4) { return BuildNumber(); } sb.Length = sb.Length - 1; // remove last int lastInt = LastInt() + 1; while (lastInt == 10) { sb.Length = sb.Length - 1; if (sb.Length == 0) { // just integer remaining int num = int.Parse(part1); return (++num).ToString(); } lastInt = LastInt() + 1; } sb[sb.Length - 1] = (char)(lastInt + '0'); if (lastInt != 9) return BuildNumber(); } int LastInt() => sb[sb.Length - 1] - '0'; string BuildNumber() => part1 + "." + sb.ToString(); } private static bool ValidLongNumber(string number, out bool isInteger) { isInteger = true; if (string.IsNullOrWhiteSpace(number)) return false; int pointCount = 0; foreach(char c in number) { bool isDigit = char.IsDigit(c); bool isPoint = c == '.'; if (!isDigit) isInteger = false; if (isPoint) pointCount++; if(pointCount > 1 || (!isPoint && !isDigit)) return false; } return true; }Aquí está su muestra:
public static void Main() { var strings = new List<string> { "123.99999", // must become 124 "129.99999", // must become 130 "123.823467283762378", // must become 123.82346728376238 "123.823467283762398", // must become 123.8234672837624 (notice the last 0 has gone) "123.09999999", // must become 123.1 (notice no trailing zeros needed..) "122.00000009", // must become 122.0000001 "124.81379281", // must become simply 124.8137928 >>> Why? Should remain same "129.07872345", // must become 129.0787235 "129.07872344", // must become 129.0787234 >>> Why? Should remain same }; IEnumerable<string> results = strings.Select(s => s.RoundLongNumber()); foreach(var res in results) { Console.WriteLine(res); } }Tenga en cuenta que dos resultados son diferentes, pero no ha explicado esa regla o su expectativa era incorrecta:
124.81379281 // must become simply 124.8137928 129.07872344 // must become 129.0787234¿Por qué? Para mi entendimiento, ambos deberían permanecer iguales.