有两个 ulong 数字 - 一个整数和一个小数部分,二进制形式看起来像 $"{IntegerPart.ToString("B")}.{FractionalPart.ToString("B")}",即小数部分是实际小数部分乘以 2^64。任务是在某种数字系统中显示近似的小数。以下是整个号码的代码。图片中有测试。
global using ExFixP = StandartPhysics.Math.ExtraFixedPoint;
using System.Numerics;
namespace StandartPhysics.Math
{
public struct ExtraFixedPoint
{
private readonly ulong IntegerPart;
private readonly ulong FractionalPart;
public readonly byte[] GetBytes()
{
return [.. new BigInteger(FractionalPart).ToByteArray(true).Concat(new BigInteger(IntegerPart).ToByteArray(true))];
}
#region ExtraFixedPoint
public ExtraFixedPoint()
{
}
internal ExtraFixedPoint(byte @int, byte frac)
{
IntegerPart = @int;
FractionalPart = (ulong)(new BigInteger(frac) << 56);
}
internal ExtraFixedPoint(ushort @int, ushort frac)
{
IntegerPart = @int;
FractionalPart = (ulong)(new BigInteger(frac) << 48);
}
internal ExtraFixedPoint(uint @int, uint frac)
{
IntegerPart = @int;
FractionalPart = (ulong)(new BigInteger(frac) << 32);
}
internal ExtraFixedPoint(ulong @int, ulong frac)
{
IntegerPart = @int;
FractionalPart = frac;
}
internal ExtraFixedPoint(Span<byte> @int, Span<byte> frac)
{
IntegerPart = (ulong)new BigInteger(@int, true);
FractionalPart = (ulong)new BigInteger(frac, true);
}
public ExtraFixedPoint(string text, int @base = 10)
{
string[] array = text.Split(new char[] { ',', '.' }, 2, StringSplitOptions.TrimEntries);
if (array.Length == 0) throw new ArgumentException("Wrong text value");
foreach (char digit in array[0].Replace(".", "").Replace(",", ""))
{
IntegerPart = IntegerPart * (ulong)@base + GetValueFromChar(digit);
}
if (array.Length == 2)
{
BigInteger baseMultiplier = new BigInteger(1) << 64;
for (int i = 0; i < array[1].Length; i++)
{
int digit = Convert.ToInt32(array[1][i].ToString(), @base);
baseMultiplier /= (ulong)@base;
FractionalPart += (ulong)((digit * baseMultiplier) & ulong.MaxValue);
}
}
}
private static ulong GetValueFromChar(char val)
{
val = char.ToLower(val);
return val switch
{
'0' => 0,
'1' => 1,
'2' => 2,
'3' => 3,
'4' => 4,
'5' => 5,
'6' => 6,
'7' => 7,
'8' => 8,
'9' => 9,
'a' => 10,
'b' => 11,
'c' => 12,
'd' => 13,
'e' => 14,
'f' => 15,
'g' => 16,
'h' => 17,
'i' => 18,
'j' => 19,
'k' => 20,
'l' => 21,
'm' => 22,
'n' => 23,
'o' => 24,
'p' => 25,
'q' => 26,
'r' => 27,
's' => 28,
't' => 29,
'u' => 30,
'v' => 31,
'w' => 32,
'x' => 33,
'y' => 34,
'z' => 35,
_ => 0
};
}
#endregion
#region operators
public static ExFixP operator +(ExFixP a, ExFixP b)
{
BigInteger bigInteger = new BigInteger(a.GetBytes(), true) + new BigInteger(b.GetBytes(), true);
ulong fractional = (ulong)(bigInteger & ulong.MaxValue);
bigInteger >>= 8;
ulong integer = (ulong)(bigInteger & ulong.MaxValue);
return new ExFixP(integer, fractional);
}
public static ExFixP operator -(ExFixP a, ExFixP b)
{
BigInteger bigInteger = new BigInteger(a.GetBytes(), true) - new BigInteger(b.GetBytes(), true);
ulong fractional = (ulong)(bigInteger & ulong.MaxValue);
bigInteger >>= 8;
ulong integer = (ulong)(bigInteger & ulong.MaxValue);
return new ExFixP(integer, fractional);
}
public static ExFixP operator *(ExFixP a, ExFixP b)
{
BigInteger bigInteger = new BigInteger(a.GetBytes(), true) * new BigInteger(b.GetBytes(), true);
bigInteger >>= 8;
ulong fractional = (ulong)(bigInteger & ulong.MaxValue);
bigInteger >>= 8;
ulong integer = (ulong)(bigInteger & ulong.MaxValue);
return new ExFixP(integer, fractional);
}
//public static exfixp operator /(exfixp a, exfixp b)
//public static exfixp operator %(exfixp a, exfixp b)
//public static exfixp operator -(exfixp a)
//public static exfixp operator +(exfixp a)
//public static exfixp operator >>(exfixp a, int b)
//public static exfixp operator <<(exfixp a, int b)
//public static exfixp operator ++(exfixp a)
//public static exfixp operator --(exfixp a)
#endregion
#region other
public readonly override string ToString()
{
return ToString();
}
public readonly string ToDebugString()
{
return $"{IntegerPart:B}.{FractionalPart.ToString("B").PadLeft(64, '0')}";
}
public readonly string ToString(int Base = 10, int accuracy = 30)
{
Base = int.Clamp(Base, 2, 36);
string integerPartBaseN = ConvertToBaseN(IntegerPart, Base);
string fractionalPartBaseN = ConvertFractionalPartToBaseN(FractionalPart, Base, accuracy);
return $"{integerPartBaseN}.{fractionalPartBaseN}";
}
private static string ConvertToBaseN(ulong number, int n)
{
if (number == 0) return "0";
string result = "";
while (number > 0)
{
ulong remainder = number % (ulong)n;
result = GetCharFromNumber(remainder) + result;
number /= (ulong)n;
}
return result;
}
private static string ConvertFractionalPartToBaseN(ulong fractionalPart, int n, int accuracy)
{
accuracy = int.Clamp(accuracy, 2, 40);
string result = "";
const ulong denominator = 1UL << 64; // 2^64
for (int i = 0; i < accuracy; i++) // ограничим вывод accuracy разрядами для дробной части
{
fractionalPart *= (ulong)n;
ulong digit = fractionalPart / denominator;
result += GetCharFromNumber(digit);
fractionalPart %= denominator;
}
return result.TrimEnd('0').PadRight(1, '0'); // удалим нули в конце
}
private static char GetCharFromNumber(ulong number)
{
if (number < 10)
return (char)('0' + number);
else
return (char)('A' + (number - 10));
}
#endregion
}
}
值得尝试解释为什么我需要这个。我最近(很久以前)研究了 IEEE754 标准的操作原理,并尝试重现其操作原理,但针对的是 128 位数字。但在实现它之前,我决定首先尝试实现定点数,然后再转向浮点数。构造函数和 ToString 中的 Base 指示在哪个数字系统中输入或输出数字,以十六进制(我最喜欢)、十进制(标准)或任何其他(尽管大约)输出。

由于问题是算法问题,所以我不会编写任何代码。我只会指出错误。
有一个描述IEEE-754浮点数的标准。他告诉我们,带点的数字包含两个部分:尾数和度数。尾数是数字的重要部分,度数是您需要提高数字系统的基数以将尾数乘以结果并获得该数字系统中数字的表示形式。
例如,要获得
1.25十进制系统,您需要一个尾数125和一个指数(幂)-2。粗略地说,指数告诉您该点需要向左移动 2 个位置。这对于任何其他数字系统都完全相同。顺便说一句,指数也可以是正数,例如数字的表示法125000是尾数125和指数3。也就是说,数据结构可能如下所示
由于指数取决于数字系统的基数,因此 IEEE-754 假定它是十进制的。如果您打算将指数存储在另一个数字系统中,则还必须将其相加。
从数学上来说
这是您可以构建逻辑的地方。
至于数学运算,你认为将 3 个 bigint 粘合在一起就能成功,这是徒劳的。
要对两个数字执行数学运算,必须将它们化简为共同指数,以便尾数不会四舍五入。例如,
1.25 + 3this 位于125 + 300给定的尾数中, 和1.25 + 300this 分别位于12500 + 30000。也就是说,您需要对两个指数进行足够的加或减,以使它们的最小值变为0。然后对结果进行指数归一化以获得最佳的 mmantissa,右侧没有零。单独存储整数和小数部分(定点数)的方法也是可以接受的,但在我看来,实现起来更困难,因为您将不得不努力解决小数部分的准确性,因为最大小数部分的值必须等于数字系统的基数减一的数字,并在规定的精度允许的范围内重复多次。也就是说,对于
ulong十进制系统,这9999999999999999999是 19 个 9。对于不同的数字系统,会有不同的数字,依此类推。也就是说,即使在定点数的存储选项中,也涉及到数字系统,并且这种格式的处理算法要复杂得多。因此,处理器使用更简单的系统——浮点和指数。我能够解决这个问题。问题在于将存储的数字转换为文本(即特定分母)时。它被表示为 ulong,并向其写入 1 << 64,这将 1 变回 1。我将类型更改为 BigInteger,现在一切正常。现在看起来像这样