C# 十进制(字符串类型)在最后一个字符处舍入

C# decimal (type string) rounded at the last char

提问人:Nick 提问时间:1/29/2022 最后编辑:EnigmativityNick 更新时间:1/29/2022 访问量:258

问:

这似乎是重复的,但我找不到正确的答案(问题足够接近,但是..) 我有一个字符串,它代表一个十进制数,它总是有很多小数位,至少 20 位,有时最多 2000 位(代表特定的验证计算,即像“数字 135 到 147 是质数 X 等 - 只是为了给你一些上下文)

例如:

123.829743892473218762329384241002373824970132871283923423961723816273823447623528347662123874999

我正在尝试四舍五入到倒数第二位。我建立了一个(有点)有效的小方法。但是(如上例所示)..如果最后一位数字是 >4,倒数第二位是 9,这意味着我必须提高前一位数字,并削减最后一位数字,如果前一位数字也是 9,这意味着我也必须提高前一位数字,依此类推。

前任。

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.0787234

依此类推。换句话说,它只是通过仅切割最后一位数字来对数字(这是一个字符串!!)进行四舍五入,但继续向左四舍五入,直到不需要为止。 只有最后一位小数才需要舍入(如果数字是整数,则不需要),规则是,如果最后一位的数字是 >4,则数字被剪切,前一位(左边)数字被提高 1,忽略尾随的零(如果有的话),规则一直持续到整数部分的最后一位(即 123.99999 变为 124, 但整数 124 保持原样等)。

有人可以帮我为此构建一个字符串扩展吗?

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);
    }
}
C# 字符串

评论

0赞 pm100 1/29/2022
你应该发布你拥有的代码。
0赞 Nick 1/29/2022
@pm100 这是截至目前: dotnetfiddle.net/Ezlqj8
0赞 Tim Schmelter 1/29/2022
这不是错吗?“124.81379281 // 必须简单地变成 124.8137928” 为什么要删除最后一个 1?与上一个问题相同的问题。
0赞 Enigmativity 1/29/2022
@Nick - 请不要在评论中发布您的代码,也不要发布链接。将实际代码直接放在您的问题中。

答:

1赞 Léo SALVADOR 1/29/2022 #1

这是一个解决方案:

    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;
    }

获取小数位数的方法来自此处,归一化小数的方法来自此处

评论

0赞 Nick 1/29/2022
谢谢!但是,我不想使用十进制,我正在处理一个巨大的字符串(现值有一个整数和 862 个小数位!
0赞 Nick 1/29/2022
为优秀的编码点赞!
1赞 D-Shih 1/29/2022 #2

因为您的输入值是字符串类型,所以我会用它来确保输入是有效的数字。decimal.TryParsedecimal

然后,您可以尝试使用一个简单的算法从输入中计算浮点长度,然后执行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);
}

C# 联机

编辑

如果你的输入有大小数,由于C#十进制只允许&之间的范围,目前不支持像Java那样。±1.0 × 10-28 to ±7.9228 × 1028 BigDecimal

我认为有两种方法可以做到这一点。

  1. 制作自己的圆形逻辑。BigDecimal
  2. 您可以尝试使用支持您从 java 调用的 IKVM 库。BigDecimal

您可以简单地通过IKVM实现您的期望。

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');
}

C# 联机

评论

0赞 Nick 1/29/2022
亲爱的,你的代码非常优雅,我会保留它。我相信这是有益的。我的问题是,我有非常大的小数(有时是数千位数字)——总是作为一个字符串。它会因小数点而失败。.例如,尝试 0.007841274592873578162948753498742458787548748723465349872135100699999(四舍五入,以上应以 ...007)
0赞 Tim Schmelter 1/29/2022
他提到了小数点后2000位
0赞 D-Shih 1/29/2022
@Nick我已经编辑了我的问题。对不起,我没有看到你提到巨大的小数。
1赞 Tim Schmelter 1/29/2022 #3

嗯,这很棘手,因为小数点后 2000 位你不能使用 .所以也许有比这更简单的东西,但它可能对你有用(.NET 小提琴演示):decimal

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;
}

下面是你的示例:

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);
    }
}

请注意,两个结果是不同的,但要么你没有解释该规则,要么你的预期是错误的:

124.81379281 // must become simply 124.8137928
129.07872344 // must become 129.0787234

为什么?根据我的理解,两者应该保持不变。

评论

0赞 Nick 1/29/2022
亲爱的蒂姆,非常感谢你,我现在正在检查你的解决方案!另外,我确实理解你的问题,这也是事情的“奇怪之处”:所以,字符串中的最后一位数字必须被切断 - 这是一个功能要求 - 但是,如果我只是使用子字符串和 length-1 切断它,它实际上(可能)损害了字符串上留下的“新”最后一个数字 - 并不是说我“只是”四舍五入最后一个数字。
0赞 Nick 1/29/2022
谢谢你的帮助!我只是做了一个小改动,如果不需要四舍五入,最后一位数字就会被简单地剪掉!但是,我建议我们保持解决方案不变,以便其他人可以从中受益!你太棒了,非常感谢你!
0赞 Tim Schmelter 1/29/2022
@Nick:不客气。你看到我一小时前解决了一个小问题吗?返回整数的部分可以简化,
0赞 Nick 1/29/2022
是的,刚刚注意到!这是一个绝妙的扩展!谢谢!