在C ++中是否有一个允许我的算法,给定T类型的浮点值V(例如double或float),在给定方向(向上或向下)返回最接近V的值,表示完全小于或等于指定的小数位数D?
例如,给定
T = double
V = 670000.08267799998
D = 6
对于direction =朝向+ inf我希望结果为670000.082678,而对于direction =朝向-inf,我希望结果为670000.082677
这有点类似于std :: nexttoward(),但是受到了' next'的限制。值必须使用最多D个小数位才能准确表示。
我考虑过一个天真的解决方案,包括将小数部分分开并将其缩放10 ^ D,将其截断,然后再将其缩放10 ^ -D并将其重新加回到整数部分,但是我不相信保证结果值在底层类型中可以准确表示。
我希望有一种方法可以做到这一点,但到目前为止,我还是找不到一个。
修改:我认为我原来的解释没有恰当地传达我的要求。根据@ patricia-shanahan的建议,我会尝试描述我的更高层次的目标,然后在这种背景下稍微改变问题。
在最高级别,我需要这个例程的原因是由于一些业务逻辑,其中我必须采用双值K和百分比P,将其分成两个双组分V1和V2,其中V1~ = P百分比K和V1 + V2~ = K.捕获的是V1在进一步计算之前用于通过有线协议发送给第三方,该有线协议接受具有最多D个小数位的字符串格式的浮点值。因为发送给第三方的值(以字符串格式)需要与使用V1(双格式)进行的计算结果相协调,我需要"调整" V1使用一些函数F()使其尽可能接近K的P%,同时仍然可以使用最多D个小数位以字符串格式表示。 V2没有V1的限制,并且可以计算为V2 = K-F(V1)(可以理解并且可以接受这可能导致V2使得V1 + V2非常接近但不完全等于K) 。
在较低的层面,我希望写下这个例程来调整' V1作为具有以下签名的东西:
double F(double V, unsigned int D, bool roundUpIfTrueElseDown);
其中输出是通过将V和(如果需要,并且在bool param指定的方向上)舍入到Dth小数位来计算的。
我的期望是当V序列化如下时
const auto maxD = std::numeric_limits<double>::digits10;
assert(D <= maxD); // D will be less than maxD... e.g. typically 1-6, definitely <= 13
std::cout << std::fixed
<< std::setprecision(maxD)
<< F(V, D, true);
然后输出只包含超出Dth小数位的零。
值得注意的是,出于性能原因,我正在寻找F()的实现,它不涉及在double和string格式之间来回转换。虽然输出最终可能会转换为字符串格式,但在许多情况下逻辑会在必要之前提前输出,我希望避免在这种情况下的开销。
答案 0 :(得分:2)
这是执行所请求的程序的草图。它主要是为了找出这是否真的是想要的。我用Java编写它,因为该语言对我想要依赖的浮点运算有一些保证。我只使用BigDecimal
来准确显示双打,以表明答案可以准确表示,小数点后不超过D位。
具体来说,我依赖于IEEE 754 64位二进制算法的双重表现。对于C ++,这很可能,但不是标准的保证。我还依赖于Math.pow对于简单的精确情况,精确度除以2的幂,以及能够使用BigDecimal获得精确输出。
我没有处理边缘情况。最大的缺失是处理大D的大幅度数字。我假设包围二进制分数可以完全表示为双精度。如果他们有超过53个有效位,那将不是这种情况。它还需要代码来处理无穷大和NaN。对于次正规数,对2的幂除法的准确性的假设是不正确的。如果您需要代码来处理它们,则必须进行更正。
这是基于这样一个概念:一个数字既可以精确表示为十进制,小数点后不超过D位数,并且可以精确表示为二进制分数,必须可以表示为分母2提升到分数D电源。如果它在分母中需要更高的2的幂,则在小数形式的小数点后面需要多于D位。如果它不能作为具有二次幂分母的分数表示,则它不能完全表示为双精度。
虽然我运行了一些其他案例来说明,但关键输出是:
670000.082678 to 6 digits Up: 670000.09375 Down: 670000.078125
以下是该计划:
import java.math.BigDecimal;
public class Test {
public static void main(String args[]) {
testIt(2, 0.000001);
testIt(10, 0.000001);
testIt(6, 670000.08267799998);
}
private static void testIt(int d, double in) {
System.out.print(in + " to " + d + " digits");
System.out.print(" Up: " + new BigDecimal(roundUpExact(d, in)).toString());
System.out.println(" Down: "
+ new BigDecimal(roundDownExact(d, in)).toString());
}
public static double roundUpExact(int d, double in) {
double factor = Math.pow(2, d);
double roundee = factor * in;
roundee = Math.ceil(roundee);
return roundee / factor;
}
public static double roundDownExact(int d, double in) {
double factor = Math.pow(2, d);
double roundee = factor * in;
roundee = Math.floor(roundee);
return roundee / factor;
}
}
答案 1 :(得分:1)
完全重写。
基于OP的新要求并使用@Patricia Shanahan建议的2次幂,简单的C解决方案:
double roundedV = ldexp(round(ldexp(V, D)),-D); // for nearest
double roundedV = ldexp(ceil (ldexp(V, D)),-D); // at or just greater
double roundedV = ldexp(floor(ldexp(V, D)),-D); // at or just less
除了@Patricia Shanahan精细解决方案之外,唯一添加的是C代码以匹配OP的标记。
答案 2 :(得分:1)
通常,小数部分不能精确表示为二进制分数。有一些例外,例如0.5(½)和16.375(16⅜),因为所有二进制分数都可以精确表示为小数部分。 (那是因为2是因子10,但10不是2的因子,或2的任何幂。)但是如果数不是2的某个幂的倍数,它的二进制表示将是无限长的循环序列,如十进制中的representation的表示(.333 ....)。
标准C库提供宏DBL_DIG
(通常为15);具有许多精度十进制数字的任何十进制数可以转换为double
(例如,使用scanf
),然后转换回十进制表示(例如,使用printf
) 。在不丢失信息的情况下向相反方向前进 - 从double
开始,将其转换为十进制然后将其转换回来 - 您需要17个十进制数字(DBL_DECIMAL_DIG
)。 (我引用的值基于IEEE-754 64位双精度数。)
提供接近问题的一种方法是将精度不超过DBL_DIG
位的十进制数视为浮点的“精确但非精确”表示如果该浮点数是最接近十进制数值的浮点数,则为number。找到浮点数的一种方法是使用scanf
或strtod
将十进制数转换为浮点数,然后尝试附近的浮点数(使用{{1}探索)找到哪些转换为具有nextafter
精度数字的相同表示。
如果您信任标准库实现不太远,可以使用DBL_DIG
将double
转换为十进制数,在所需的数字位置递增十进制字符串(这只是字符串操作),然后使用sprintf
将其转换回double
。
答案 3 :(得分:0)
在C ++中,整数必须用二进制表示,但浮点类型可以用十进制表示。
如果来自FLT_RADIX
的 <limits.h>
为10,或10的某个倍数,那么您可以实现精确表示小数值的目标。
否则,一般情况下,这是不可能实现的。
因此,作为第一步,尝试找到FLT_RADIX
为10的C ++实现。
在安装C ++实现并证明在您的系统上工作之前,我不担心算法或效率。但作为一个暗示,你的目标似乎与被称为“四舍五入”的行动相似。我认为,在获得我的十进制浮点C ++实现之后,我首先要研究四舍五入的技术,例如谷歌搜索,也许维基百科,......