我知道双打准确性存在问题,但这种情况让我感到惊讶。
我从文件中读到了一些双打。这些是值:
90.720000 33.800000 43.150000 37.970000 46.810000 48.770000 81.800000 19.360000 6.760000
由于它们代表货币,因此它们始终具有最多2位小数的精度。我希望这些值存储为无符号值。所以,我将它们全部乘以100.0,并将它们转换为无符号数组。
当我打印无符号值时,我得到了这个:
9071 3379 4314 3796 4681 4877 8179 1935 675
为什么其中一些数字有错误,有些则没有?还有另一种方法吗?为什么代码告诉我它的值为90.72,如果它真的有90.71?
根据要求,这是相关代码:
unsigned itemprices[MAXITEMS];
unsigned u_itemweights[MAXITEMS];
double itemweights[MAXITEMS];
numberofobjects=0;
do{
fscanf(fp, " (%*u,%lf,$%u)", &itemweights[numberofobjects], &itemprices[numberofobjects]);
numberofobjects++;
if(fgetc(fp)==' ') continue;
else break;
}while(1);
puts("\nitemweights before convert:");
for(i=0; i<numberofobjects; i++) printf("%f ", itemweights[i]);
// convert itemweights to unsigned.
for(i=0; i<numberofobjects; i++) u_itemweights[i] = itemweights[i] * 100.0;
puts("\nitemweights after convert:");
for(i=0; i<numberofobjects; i++) printf("%u ", u_itemweights[i]);
这是一个输出样本:
itemweights before convert:
90.720000 33.800000 43.150000 37.970000 46.810000 48.770000 81.800000 19.360000 6.760000
itemweights after convert:
9071 3379 4314 3796 4681 4877 8179 1935 675
答案 0 :(得分:2)
实际值为90.719999999999998863131622783839702606201171875
。如果您乘以100
,则结果为9071.9999999999998863131622783839702606201171875
,并将结果转换为9071
。
在将其转换为整数之前,您可以将0.5
(或任何其他小数字<1)添加到乘法结果中。
另一种选择是使用round
函数。
答案 1 :(得分:2)
如果文件中包含您引用的值,则以美分打印它们,并以相同的2位十进制数字显示。
#include <stdio.h>
int main(void)
{
FILE *inf;
unsigned cents;
double money;
if((inf = fopen("test.txt", "r")) == NULL)
return 1;
while (fscanf(inf, "%lf", &money) == 1) {
cents = (unsigned)(money * 100.0 + 0.1);
printf("File %f, cents %u\n", money, cents);
}
fclose(inf);
return 0;
}
节目输出:
File 90.720000, cents 9072
File 33.800000, cents 3380
File 43.150000, cents 4315
File 37.970000, cents 3797
File 46.810000, cents 4681
File 48.770000, cents 4877
File 81.800000, cents 8180
File 19.360000, cents 1936
File 6.760000, cents 676
对于评论过的非信徒, 编辑。这需要最多32位unsigned
美分,转换为double
美元,然后返回美分而不会丢失。 double
尾数有53位,比int
多21位。
#include <stdio.h>
#include <limits.h>
int main()
{
double money;
unsigned cents = UINT_MAX;
printf("cents = %u\n", cents);
money = ((double)cents) / 100;
printf("money = %.2f\n", money);
cents = (unsigned)(money * 100.0 + 0.1);
printf("cents = %u\n", cents);
return 0;
}
Progam输出:
cents = 4294967295
money = 42949672.95
cents = 4294967295
答案 2 :(得分:2)
老实说,如果人们鼓励将值解析为浮点值并对这些值进行任何数学运算,我真的有点失望。
有两个问题。第一个是十进制值不能在固定精度浮点中表示。例如,0.01不能以浮点表示。
下一个问题是乘法和除法的基础。两者都改变小数点后的位数。从根本上说,对于double
或uint32_t
等任何有限精度数据类型,您无法获得无限精度。
十进制值(如货币)可以使用定点算术处理,但计算中仍会丢失精度。
例如,0.50美元的1%为0.005美元,最高为0.01美元。但是,使用两个精度位置的定点运算...
0.50
x0.01
-----
=0.00
这里的结果是$ 0,但实际值应该是0.01美元。并且,如果将其乘以25(例如复利),则结果现在偏离2500%。
对于后代,这里是不使用浮点读取值的代码。
#include <stdio.h>
int main(int argc, char* argv[argc]) {
unsigned dollars = 0;
char dimes = 0;
char pennies = 0;
unsigned fixed = 0;
FILE* values;
values = fopen("values", "r");
while (fscanf(values, "%u.%c%c%*i\n", &dollars, &dimes, &pennies) != EOF) {
dimes -= '0';
pennies -= '0';
fixed = (dollars * 100) + (dimes * 10) + pennies;
printf("$%u.%u%u -> %u (cents)\n", dollars, dimes, pennies, fixed);
}
return 0;
}
...输出
$90.72 -> 9072 (cents)
$33.80 -> 3380 (cents)
$43.15 -> 4315 (cents)
$37.97 -> 3797 (cents)
$46.81 -> 4681 (cents)
$48.77 -> 4877 (cents)
$81.80 -> 8180 (cents)
$19.36 -> 1936 (cents)
$6.76 -> 676 (cents)
答案 3 :(得分:1)
如果你从文件中读取的那些双打是文本格式(你没有指定),那么你可以将它作为文本阅读并手动解析文本,而不是将它们读成双打然后再进行转换。 (例如,删除句点并跳过零,然后转换为uint
答案 4 :(得分:1)
为什么其中一些数字有错误,有些则没有?在那儿 另一种方式呢?为什么代码会告诉我它有价值 90.72,如果确实有90.71?
您的问题的答案与浮点数如何存储在内存中有关。浮点值以 IEEE-754单精度(32-bit float
)或 IEEE-754双精度(64-bit double
)存储在内存中浮点格式。格式(二进制)由3部分编码的数字组成,其中最高有效位(31或63)是sign bit
,接下来的8或11位(浮点数/双精度)是超出格式的指数,最后接下来的23或52位(浮点/双精度)是标准化的尾数/有效数。
例如,90.72
的 double 值存储在内存中:
IEEE-754 dbl : 0100000001010110101011100001010001111010111000010100011110101110
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
|s| exp | mantissa |
该格式允许分别在内存中32或64位的各自范围内存储所有浮点数或双精度数。但是,格式受限于可用的有限位数所带来的精度限制。
您可以通过考虑表示每个浮点值的二进制数来最好地理解此限制。在内存中,它只不过是32-bit
64-bit
'0's
的{{1}}序列。由于每种格式(单/双精度)采用的位数与'1's
或unsigned int
(或某些硬件上的unsigned long
)使用的位数相同,因为每个浮点值,都有一个unsigned int或unsigned long,内存中的完全相同的二进制表示。
这将有助于揭示为什么你的某些数字有错误而有些则没有。如果考虑等值为unsigned long long
的无符号整数,您会发现在内存中以{IEEE} 754双精度格式表示90.72
的方式存在限制。具体做法是:
90.72
这是考虑 unsigned long 等效帮助的地方。可以在内存中表示的下一个可能更大的数字是什么? (答案: Actual Value as double, and unsigned long equivalent:
double : 90.7199999999999989
long unsigned : 4636084269408667566
binary : 01000000-01010110-10101110-00010100-01111010-11100001-01000111-10101110
超过无符号长等效值的当前值
1
(注意: 无符号长等效内容Closest next larger value:
double : 90.7200000000000131
long unsigned : 4636084269408667567
binary : 01000000-01010110-10101110-00010100-01111010-11100001-01000111-10101111
的此更改(或内存中一位的更改)只会影响小数点后13位附近的值,但是如果你试图乘以1
并按照你的发现进行投射,会产生巨大的后果)
看看你当前的100.0
的两倍是如何存储在内存中的,以及下一个可能存储的较大值,应该清楚地告诉你为什么你的某些值有错误而有些值没有的
如果任何给定的double值是在内存中以略小于货币值的值表示的值(例如90.72
而不是90.719...
),则会创建舍入错误使用乘以100.0并投射方法。这就是为什么你可以更好地使用其他答案中提供的不受此类错误影响的方案,以及为什么你想在处理钱时避免(或妥善管理)浮点不准确性。
答案 5 :(得分:0)
从货币到整数的音乐会。
1)规模。
2)通过round()
来回。 不使用+0.5技巧 - 太多问题
3)保险范围。
4)演员
unsigned convert_double_to_unsigned_scaled_rounded(double x, double scale) {
double x_scaled = x*scale;
double x_rounded = round(x_scaled);
assert(x_rounded >= 0 && x_rounded <= UINT_MAX);
return (unsigned) x_rounded;
}
// u_itemweights[i] = itemweights[i] * 100.0;
u_itemweights[i] = convert_double_to_unsigned_scaled_rounded(itemweights[i], 100.0);
货币编码的一个关键问题是需要准确性 - 这涉及四舍五入来纠正。一个简单的(unsigned) x
经常会出现问题 - 如你所见 - 因为它会截断分数。另一个例子:如果代码使用double
或整数,则计算贷款的7.3%是一个问题。