将'float'转换为NSNumber,再转回'float'时会丢失精度

9
我在Objective-C中遇到了一个奇怪的问题,即将float转换为NSNumber(为方便起见进行包装),然后再将其转换回float。
简而言之,我的一个类有一个属性“red”,它是从0.0到1.0的浮点数。
@property (nonatomic, assign) float red;

此对象将自身与从磁盘加载的值进行比较,以实现同步目的。(文件可能在应用程序外部更改,因此定期检查文件更改,将备用版本加载到内存中,并进行比较,合并差异。)

以下是一个有趣的片段,其中比较了这两个值:

if (localObject.red != remoteObject.red) {
    NSLog(@"Local red: %f Remote red: %f", localObject.red, remoteObject.red);
}

以下是我在日志中看到的内容:

2011-10-28 21:07:02.356 MyApp[12826:aa63] Local red: 0.205837 Remote red: 0.205837

奇怪。对吧?这段代码是如何被执行的呢?
文件中实际存储的值:
...red="0.205837"...

被转换为 float 使用以下方式:
currentObject.red = [[attributeDict valueForKey:@"red"] floatValue];

在代码的另一个位置,我能够从GDB中获取屏幕截图。它被打印到NSLog中,如下所示:(这也是它在磁盘上出现的精度。)

2011-10-28 21:21:19.894 MyApp[13214:1c03] Local red: 0.707199 Remote red: 0.707199

但在调试器中显示为:

GDB Screenshot

这种精度是如何在属性级别上获得的,但没有存储在文件中或在 NSLog 中正确打印?为什么看起来会有所不同?


可能在这里找到了一些东西,但不幸的是它并没有出现在之前的任何搜索结果中:https://dev59.com/CG445IYBdhLWcg3wH2zH#5025021 - Craig Otis
1
你是否在任何时候将其转换为/从字符串?如果是这样,请使用%+0.16f而不是%f。或者您可以使用其他精度代替.16。如果没有,请忽略此评论。 - chown
是的,我绝对会将它转换为/从NSString。不过我一直以为像这样创建它:**[NSString stringWithFormat:@"%f", 0.707199f]** 然后像这样转换它:**[... floatValue]** 会得到相同的值。看来并不是这样。谢谢你的提示,我想这会解决问题。请随意将其发布为答案。 - Craig Otis
如果数字最初是3.14159,那么字符串将会是@"3.14159"(使用"%0.16f"),但如果你使用了"%0.4f",那么它将四舍五入为@"3.1416" - chown
@DanielRHicks 很好,讲得非常清楚。谢谢! - Craig Otis
显示剩余5条评论
4个回答

5
如果您在任何时候将其转换为/从字符串,请尝试使用%0.16f而不是%f(或者您想要的任何精度,而不是.16)。
有关更多信息,请参见IEEE Std formatting
此外,请使用objectForKey而不是valueForKeyvalueForKey不适用于字典)。
currentObject.red = [[attributeDict objectForKey:@"red"] floatValue];

参见这个SO答案,更好地解释了objectForKeyvalueForKey的区别:

objectForKey和valueForKey有什么区别?


谢谢,那个可行!还有感谢你提醒 objectForKey:,我通常会更加关注这些细节。 - Craig Otis
@craig 不客气,很高兴能帮忙! - chown

4
您所遇到的问题是浮点数问题。浮点数并不能精确表示存储的数字(除了一些特定情况,在这里不必考虑)。链接中Craig发布的示例是一个很好的例子。
在您的代码中,当您将值写入文件时,您实际上是写入了浮点数存储的近似值。当您重新加载它时,另一个近似值被存储在浮点数中。然而,这两个数字可能不相等。
最好的解决方案是使用两个浮点数的模糊比较。我不是Objective C程序员,因此不知道语言是否包含内置函数来执行此比较。然而,这个链接提供了一组良好的示例,展示了各种进行此比较的方法。
您也可以尝试使用其他发布的解决方案,例如使用更高的精度写入到文件中,但您可能会浪费额外的不需要的空间。个人建议您使用模糊比较,因为它更加稳妥。

2
你说“remote”值是从磁盘加载的。我猜想在磁盘上的表示不是IEEE浮点位值,而是某种字符表示或类似的东西。因此,在IEEE浮点运作方式下转换到和从该表示中不可避免地会出现转换错误。你将无法得到精确的结果,因为浮点值只有约6个十进制数字的小数精度,但它很少映射到完全相同的6个十进制数字,而是像在十进制中表示1/3一样——没有精确的映射。

0

3
虽然这个回答理论上可以解答问题,但最好包含回答的关键部分并提供链接以供参考。 - Bill the Lizard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接