我怎样才能从分数中得到分子和分母
how can i get numerator and denominator from a fractional number?
如何从分数中获得分子和分母?例如,从"1.375"开始,我想得到"1375/1000"或"11/8"作为结果。我怎样才能用c++来实现它??我试着通过将点前和点后的数字分开来实现这一点,但它不知道如何获得我想要的输出。
您并没有真正指定是需要转换浮点还是字符串与比率,所以我假设是前者。
您可以直接使用IEEE-754编码的属性,而不是尝试基于字符串或算术的方法。
Floats(标准称为binary32
)在内存中编码如下:
S EEEEEEEE MMMMMMMMMMMMMMMMMMMMMMM
^ ^
bit 31 bit 0
其中S
是符号比特,E
s是指数比特(其中8个),M
s是尾数比特(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;
}
- 没有找到相关文章