如何将浮点值移到可以精确表示为特定小数位数的最近值
How to shift a floating-point value to the nearest one that can be represented exactly in a specific number of decimal places?
C++中是否有一种算法允许我在给定的T类型的浮点值V(例如,double或float)上返回在给定方向(向上或向下)上最接近V的值,该值可以用小于或等于指定小数位数D的精确地表示?
例如,给定
T = double
V = 670000.08267799998
D = 6
对于方向=朝向+inf,我希望结果为670000.082678,对于方向=朝-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(双格式)进行的计算结果相协调,所以我需要使用一些函数F()"调整"V1,使其尽可能接近K的P%,同时仍然可以使用最多D个小数点的字符串格式精确表示。V2不具有V1的任何限制,并且可以计算为V2=K-F(V1)(可以理解和接受的是,这可能导致V2使得V1+V2非常接近但不完全等于K)。
在较低级别,我希望编写该例程来"调整"V1,使其具有以下签名:
double F(double V, unsigned int D, bool roundUpIfTrueElseDown);
其中,通过取V并(如有必要,按照布尔参数指定的方向)将其四舍五入到小数点后第D位来计算输出。
我的期望是,当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);
则输出仅包含小数点后第D位以外的零。
需要注意的是,出于性能原因,我正在寻找一种不涉及在double和string格式之间来回转换的F()实现。尽管输出最终可能会转换为字符串格式,但在许多情况下,逻辑会在必要之前提前输出,我希望避免这种情况下的开销。
这是一个执行请求的程序的草图。它主要是为了找出这是否真的是人们想要的。我是用Java写的,因为这种语言对我想要依赖的浮点运算有一些保证。我只使用BigDecimal
来获得双打的精确显示,以表明答案是可以精确表示的,小数点后不超过D位。
具体来说,我依赖于根据IEEE 754 64位二进制算法的双重行为。对于C++来说,这是可能的,但标准并不能保证。我还依赖于Math.pow在简单精确的情况下是精确的,依赖于除以2的幂的精确性,以及能够使用BigDecimal获得精确的输出。
我没有处理过边缘案件。最大的遗漏是处理具有大D的大星等。我假设括号内的二进制分数可以精确地表示为二重。如果它们具有超过53个有效位,则情况并非如此。它还需要处理不定式和NaN的代码。二次幂除法的精确性的假设对于次正规数是不正确的。如果您需要您的代码来处理它们,则必须进行更正。
它基于这样一个概念,即一个既可以精确地表示为小数点后不超过D位的小数,又可以精确地表达为二进制分数的数字,必须可以表示为分母2提升到D次方的分数。如果它的分母需要更高的2次方,那么它的小数形式中小数点后需要超过D位数字。如果它根本不能表示为分母为2次方的分数,那么它就不能完全表示为二重。
尽管我运行了一些其他案例进行说明,但关键输出是:
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;
}
}
通常,十进制分数不能精确地表示为二进制分数。也有一些例外,比如0.5(1/2)和16.375(16/),因为所有二进制分数都可以精确地表示为十进制分数。(这是因为2是10的因子,但10不是2的因子,也不是2的任何幂。)但如果一个数字不是2的某个幂的倍数,它的二进制表示将是一个无限长的循环序列,就像1/3的表示一样;十进制(.333….)。
标准C库提供宏DBL_DIG
(通常为15);具有那么多精度的十进制数字的任何十进制数字都可以被转换为double
(例如,使用scanf
),然后被转换回十进制表示(例如,用printf
)。要在不丢失信息的情况下朝相反的方向前进——从double
开始,将其转换为十进制,然后再将其转换回——您需要17位十进制数字(DBL_DECIMAL_DIG
)。(我引用的值基于IEEE-754 64位双精度)。
提供接近该问题的一种方法是,如果浮点数是最接近十进制值的浮点数,则将精度不超过DBL_DIG
位的十进制数视为浮点数的"精确但不精确"表示。找到浮点数的一种方法是使用scanf
或strtod
将十进制数转换为浮点数,然后尝试附近的浮点数(使用nextafter
进行探索),以找到哪些浮点数转换为精度为DBL_DIG
位的相同表示。
如果您相信标准库实现不会太远,那么可以使用sprintf
将double
转换为十进制数字,在所需的数字位置递增十进制字符串(这只是一个字符串操作),然后使用strtod
将其转换回double
。
总重写。
根据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的精细解决方案之外,这里唯一添加的是匹配OP标签的C代码。
在C++中,整数必须用二进制表示,但浮点类型可以用十进制表示。
如果<limits.h>
中的FLT_RADIX
是10,或者是10的倍数,则可以实现精确表示十进制值的目标。
否则,总的来说,这是不可能实现的。
因此,作为第一步,尝试找到一个FLT_RADIX
为10的C++实现。
我不会担心算法或其效率,直到C++实现被安装并被证明在您的系统上工作。但作为一个暗示,你的目标似乎与所谓的“四舍五入";。我认为,在获得我的十进制浮点C++实现后,我;d首先研究四舍五入的技术,例如,在谷歌上搜索,也许是维基百科,…
- 表示"accepting anything for this template argument" C++概念的通配符
- C++将浮点指针值舍入为小数位数
- 如何将ampl中的集合表示为c++中的向量
- 从给定的 I 和 D 序列中形成最小数
- 如何防止 c++ 在从浮点型转换为双精度型(不适用于 IO)时添加额外的小数?
- std::is_base_of表示ctor编译错误
- 输入中的字符串数未知(以字母表示)
- 我可以信任表示整数的浮点或双精度来保持精度吗
- c++模板来表示多项式
- 询问在设计我的手臂模拟器功能表示格式1
- CMakeLists.txt中的命名空间表示法
- C++射线示踪剂ppm表示没有足够的数据来显示图像
- 如何计算Big-O表示法中的平均渐近运行时间
- 我应该如何表示我拥有的连续元素序列?
- 在C++中,使用带有 std::optional 参数的函数<T>来表示可选参数是否有意义?
- 以十六进制格式表示 fp16 最小数
- 如何解析小数分离器为结肠的数字的字符串表示:
- 如何将浮点值移到可以精确表示为特定小数位数的最近值
- 有多少种表示小数的方法
- 将表示小数的 int 转换为双精度的正确方法是什么