我怎样才能从分数中得到分子和分母

how can i get numerator and denominator from a fractional number?

本文关键字:      更新时间:2023-10-16

如何从分数中获得分子和分母?例如,从"1.375"开始,我想得到"1375/1000"或"11/8"作为结果。我怎样才能用c++来实现它??我试着通过将点前和点后的数字分开来实现这一点,但它不知道如何获得我想要的输出。

您并没有真正指定是需要转换浮点还是字符串与比率,所以我假设是前者。

您可以直接使用IEEE-754编码的属性,而不是尝试基于字符串或算术的方法。

Floats(标准称为binary32)在内存中编码如下:

S EEEEEEEE MMMMMMMMMMMMMMMMMMMMMMM
^                                ^
bit 31                           bit 0

其中S是符号比特,Es是指数比特(其中8个),Ms是尾数比特(23个比特)。

数字可以这样解码:

value = (-1)^S * significand * 2 ^ expoenent
where:
significand = 1.MMMMMMMMMMMMMMMMMMMMMMM (as binary)
exponent = EEEEEEEE (as binary) - 127

(注意:这是所谓的"常数值",也有零、子常数值、无穷大和NaN-请参阅我链接的维基百科页面)

这可以在这里使用。我们可以这样重写上面的等式:

(-1)^S * significand * exponent = (-1)^s * (significand * 2^23) * 2 ^ (exponent - 23)

关键是significand * 2^23是一个整数(等于1.MMMMMMMMMMMMMMMMMMMMMMM,二进制-乘以2^23,我们将点向右移动了23位)。显然,2 ^ (exponent - 23)也是一个整数。

换句话说:我们可以将数字写为:

(significand * 2^23) / 2^(-(exponent - 23))    (when exponent - 23 < 0)
or
[(significand * 2^23) * 2^(exponent - 23)] / 1 (when exponent - 23 >= 0)

所以我们有分子和分母-直接从数字的二进制表示。


以上所有内容都可以像这样在C++中实现:

struct Ratio
{
int64_t numerator; // numerator includes sign
uint64_t denominator;
float toFloat() const
{
return static_cast<float>(numerator) / denominator;
}
static Ratio fromFloat(float v)
{
// First, obtain bitwise representation of the value
const uint32_t bitwiseRepr = *reinterpret_cast<uint32_t*>(&v);
// Extract sign, exponent and mantissa bits (as stored in memory) for convenience:
const uint32_t signBit = bitwiseRepr >> 31u;
const uint32_t expBits = (bitwiseRepr >> 23u) & 0xffu; // 8 bits set
const uint32_t mntsBits = bitwiseRepr & 0x7fffffu; // 23 bits set
// Handle some special cases:
if(expBits == 0 && mntsBits == 0)
{
// special case: +0 and -0
return {0, 1};
}
else if(expBits == 255u && mntsBits == 0)
{
// special case: +inf, -inf
// Let's agree that infinity is always represented as 1/0 in Ratio 
return {signBit ? -1 : 1, 0};
}
else if(expBits == 255u)
{
// special case: nan
// Let's agree, that if we get NaN, we returns max int64_t by 0
return {std::numeric_limits<int64_t>::max(), 0};
}
// mask lowest 23 bits (mantissa)
uint32_t significand = (1u << 23u) | mntsBits;
const int64_t signFactor = signBit ? -1 : 1;
const int32_t exp = expBits - 127 - 23;
if(exp < 0)
{
return {signFactor * static_cast<int64_t>(significand), 1u << static_cast<uint32_t>(-exp)};
}
else
{
return {signFactor * static_cast<int64_t>(significand * (1u << static_cast<uint32_t>(exp))), 1};
}
}
};

(希望上面的评论和描述可以理解-如果有需要改进的地方,请告诉我)

为了简单起见,我省略了对超出范围值的检查。

我们可以这样使用:

float fv = 1.375f;
Ratio rv = Ratio::fromFloat(fv);
std::cout << "fv = " << fv << ", rv = " << rv << ", rv.toFloat() = " << rv.toFloat() << "n";

输出为:

fv=1.375,rv=115334336/8388608,rv.toFloat()=1.375

如您所见,两端的值完全相同。


问题是分子和分母都很大。这是因为代码总是将有效位乘以2^23,即使较小的值足以使其成为整数(这相当于将0.2写成2000000/100000,而不是2/10——这是一样的,只是写得不同)。

这可以通过将代码更改为用最小数乘以有效位(并除以指数)来解决,如下所示(省略号代表与上述相同的部分):

// counts number of subsequent least significant bits equal to 0
// example: for 1001000 (binary) returns 3
uint32_t countTrailingZeroes(uint32_t v)
{
uint32_t counter = 0;
while(counter < 32 && (v & 1u) == 0)
{
v >>= 1u;
++counter;
}
return counter;
}
struct Ratio
{
...
static Ratio fromFloat(float v)
{
... 
uint32_t significand = (1u << 23u) | mntsBits;
const uint32_t nTrailingZeroes = countTrailingZeroes(significand);
significand >>= nTrailingZeroes;
const int64_t signFactor = signBit ? -1 : 1;
const int32_t exp = expBits - 127 - 23 + nTrailingZeroes;
if(exp < 0)
{
return {signFactor * static_cast<int64_t>(significand), 1u << static_cast<uint32_t>(-exp)};
}
else
{
return {signFactor * static_cast<int64_t>(significand * (1u << static_cast<uint32_t>(exp))), 1};
}
}
};

现在,对于以下代码:

float fv = 1.375f;
Ratio rv = Ratio::fromFloat(fv);
std::cout << "fv = " << fv << ", rv = " << rv << ", rv.toFloat() = " << rv.toFloat() << "n";

我们得到:

fv=1.375,rv=11/8,rv.toFloat()=1.375

在C++中,您可以使用Boost rational类。但你需要给出分子和分母。

为此,您需要找出输入字符串中小数点后的位数。您可以通过字符串操作函数来实现这一点。逐字符读取输入的字符,在.之后找不到任何字符

char inputstr[30]; 
int noint=0, nodec=0;
char intstr[30], dec[30];
int decimalfound = 0;
int denominator = 1;
int numerator;
scanf("%s",inputstr);
len = strlen(inputstr);
for (int i=0; i<len; i++)
{
if (decimalfound ==0)
{
if (inputstr[i] == '.')
{
decimalfound = 1;
}
else
{
intstr[noint++] = inputstr[i];
}
}
else
{
dec[nodec++] = inputstr[i];
denominator *=10;
}
}
dec[nodec] = '';
intstr[noint] = '';
numerator = atoi(dec) + (atoi(intstr) * 1000);
// You can now use the numerator and denominator as the fraction, 
// either in the Rational class or you can find gcd and divide by 
// gcd.

这个简单的代码:

double n = 1.375;
int num = 1, den = 1;
double frac = (num * 1.f / den);
double margin = 0.000001;
while (abs(frac - n) > margin){
if (frac > n){
den++;
}
else{
num++;
}
frac = (num * 1.f / den);
}

我真的没有测试太多,这只是一个想法。

我希望大家原谅我发布了一个"仅使用C语言"的答案。我知道你用C++标记了这个问题,但我不能拒绝,对不起。这至少仍然是有效的C++(尽管无可否认,它主要使用C字符串处理技术)。

int num_string_float_to_rat(char *input, long *num, long *den) {
char *tok = NULL, *end = NULL;
char buf[128] = {''};
long a = 0, b = 0;
int den_power = 1;
strncpy(buf, input, sizeof(buf) - 1);
tok = strtok(buf, ".");
if (!tok) return 1;
a = strtol(tok, &end, 10);
if (*end != '') return 2;
tok = strtok(NULL, ".");
if (!tok) return 1;
den_power = strlen(tok);  // Denominator power of 10
b = strtol(tok, &end, 10);
if (*end != '') return 2;
*den = static_cast<int>(pow(10.00, den_power));
*num = a * *den + b;
num_simple_fraction(num, den);
return 0;
}

示例用法:

int rc = num_string_float_to_rat("0015.0235", &num, &den);
// Check return code -> should be 0!
printf("%ld/%ldn", num, den);

输出:

30047/2000

完整示例位于http://codepad.org/CFQQEZkc。

注意:

  • strtok()用于将输入解析为令牌(在这方面不需要重新发明轮子)。strtok()修改其输入,因此为了安全起见使用了临时缓冲区
  • 它检查无效字符,如果找到,将返回非零返回代码
  • 使用了strtol()而不是atoi(),因为它可以检测输入中的非数字字符
  • scanf()被用于模糊输入-由于浮点数字的舍入问题
  • strtol()的基数已明确设置为10,以避免前导零的问题(否则前导零将导致数字被解释为八进制)
  • 它使用num_simple_fraction()辅助对象(未显示),而gcd()辅助对象(也未显示)将结果转换为简单分数
  • 分子的log10()是通过计算小数点后的令牌长度来确定的

我会分三步来完成。

1) 找到小数点,这样你就知道分母有多大。

2) 获取分子。这只是去掉小数点的原始文本。

3) 得到分母。如果没有小数点,分母是1。否则,分母为10^n,其中n是小数点(现已删除)右侧的位数。

struct fraction {
std::string num, den;
};
fraction parse(std::string input) {
// 1:
std::size_t dec_point = input.find('.');
// 2:
if (dec_point == std::string::npos)
dec_point = 0;
else {
dec_point = input.length() - dec_point;
input.erase(input.begin() + dec_point);
}
// 3:
int denom = 1;
for (int i = 1; i < dec_point; ++i)
denom *= 10;
string result = { input, std::to_string(denom) };
return result;
}
相关文章:
  • 没有找到相关文章