关于浮点数计算时的精度问题
来源:互联网 发布:数控编程软件ug 编辑:程序博客网 时间:2024/06/05 18:19
那个有问题的缩略图生成的方法发布之后,短短半天就有很多朋友响应,其中指出了不少方法中的不少问题,有些也是我没有意识到的。果然集体的智慧是无穷的,一段代码在许多人的眼皮底下经过,想留有bug也不容易。不过,我在这里只能谈一下我写那篇文章的本意了,我认为那篇文章中最主要的问题是,在计算图片尺寸时没有处理好浮点数计算的精度问题。
为了凸现主要逻辑,我把之前那个方法中计算图片尺寸的代码单独抽取成一个方法:
public static void GetThumbnailSize( int originalWidth, int originalHeight, int desiredWidth, int desiredHeight, out int newWidth, out int newHeight){ // If the image is smaller than a thumbnail just return it if (originalWidth <= desiredWidth && originalHeight <= desiredHeight) { newWidth = originalWidth; newHeight = originalHeight; return; } // scale down the smaller dimension if ((decimal)desiredWidth / originalWidth < (decimal)desiredHeight / originalHeight) { decimal desiredRatio = (decimal)desiredWidth / originalWidth; newWidth = desiredWidth; newHeight = (int)(originalHeight * desiredRatio); } else { decimal desiredRatio = (decimal)desiredHeight / originalHeight; newHeight = desiredHeight; newWidth = (int)(originalWidth * desiredRatio); }}
我们通过简单的代码试验一下:
int newWidth, newHeight;GetThumbnailSize(200, 200, 100, 100, out newWidth, out newHeight);Console.WriteLine("{0}, {1}", newWidth, newHeight);GetThumbnailSize(300, 300, 100, 100, out newWidth, out newHeight);Console.WriteLine("{0}, {1}", newWidth, newHeight);
得到的结果是:
100, 10099, 100
第一个结果自然没有问题,但是在第二个结果中为什么是99而不是100?为此,我们再通过以下的代码来观察一番:
ratio: 0.3333333333333333333333333333new value: 99.99999999999999999999999999to int: 99
可见,虽然使用了decimal,精度已经非常高的,但是在经过了一除一乘,它还是没有恢复到最精确值。虽然一直说要注意浮点数计算时的精度问题,但是对于这个问题许多朋友往往只是理解到“不能直接两个浮点数相等”,包括我自己的第一印象。但事实上,从上面的结果也可以看出,把一个浮点数直接转换成整形,它便是使用了“去尾”而不是“四舍五入”的方法。因此,虽然newValue的值无比接近100,但是在强制去尾后它还是变成了99。
如果要在原来的方法中改变这个问题,最简单的方法可能是把最后的强制转型替换成Math.Round方法。Math.Round方法使用四舍五入,应该能够解决问题。不过如果只是这样的话收获不大,我们再仔细想想,应该如何做到尽可能的精确。
两个浮点数相除可能会丧失精度,但如果是乘法操作,在一般情况下精度是不会丢失的,除非发生了溢出的话,或者小数位数太多。因此在计算过程中为了保持精度,我们应该尽可能的做乘法,而不是作除法。例如以下的判断:
if ((decimal)desiredWidth / originalWidth < (decimal)desiredHeight / originalHeight)
其实最好改写成“等价”的乘法操作(假设没有溢出):
if (desiredWidth * originalHeight < desiredHeight * originalWidth)
同理,如果可以的话,在作计算的时候,也最好先乘再除:
if (desiredWidth * originalHeight < desiredHeight * originalWidth){ newWidth = desiredWidth; newHeight = (int)Math.Round((decimal)originalHeight * desiredWidth / originalWidth);}else{ newHeight = desiredHeight; newWidth = (int)Math.Round((decimal)originalWidth * desiredHeight / originalHeight);}
这么做,我们就避免了使用scaleRatio这个已经丧失部分精度的值来参与计算,这样1 * 3 / 3便可以等于1,而不像1 / 3 * 3等于0.99…。因此,最终我们CreateThumbnail的代码便修改为:
/// <summary>/// Creates a thumbnail from an existing image. Sets the biggest dimension of the/// thumbnail to either desiredWidth or Height and scales the other dimension down/// to preserve the aspect ratio/// </summary>/// <param name="imageStream">stream to create thumbnail for</param>/// <param name="desiredWidth">maximum desired width of thumbnail</param>/// <param name="desiredHeight">maximum desired height of thumbnail</param>/// <returns>Bitmap thumbnail</returns>public Bitmap CreateThumbnail(Bitmap originalBmp, int desiredWidth, int desiredHeight){ // If the image is smaller than a thumbnail just return it if (originalBmp.Width <= desiredWidth && originalBmp.Height <= desiredHeight) { return originalBmp; } int newWidth, newHeight; // scale down the smaller dimension if (desiredWidth * originalBmp.Height < desiredHeight * originalBmp.Width) { newWidth = desiredWidth; newHeight = (int)Math.Round((decimal)originalBmp.Height * desiredWidth / originalBmp.Width); } else { newHeight = desiredHeight; newWidth = (int)Math.Round((decimal)originalBmp.Width * desiredHeight / originalBmp.Height); } // This code creates cleaner (though bigger) thumbnails and properly // and handles GIF files better by generating a white background for // transparent images (as opposed to black) // This is preferred to calling Bitmap.GetThumbnailImage() Bitmap bmpOut = new Bitmap(newWidth, newHeight); using (Graphics graphics = Graphics.FromImage(bmpOut)) { graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.FillRectangle(Brushes.White, 0, 0, newWidth, newHeight); graphics.DrawImage(originalBmp, 0, 0, newWidth, newHeight); } return bmpOut;}
当然,在前文中很多朋友指出的其他一些问题也很有道理,例如:
- 没有做参数校验。
- 直接返回源图片的做法让方法的含义不同。
- 经过计算后newWidth和newHeight可能为0。
例如还有朋友提出对GIF的处理不很妥当等等——如果您有其他想法的话,也可以继续讨论。或者,你也来分享一下代码或工作中发现的问题?
from: http://blog.zhaojie.me/2009/11/precision-of-float-point-calculation.html
- 关于浮点数计算时的精度问题
- 关于计算订单价格,转换后差一分钱的解决方法(浮点数精度问题)
- java中floatdouble浮点数的计算失精度问题
- 浮点数的精度问题
- 浮点数计算注意精度问题
- Java浮点数计算精度问题总结
- 浮点数精度计算
- 关于编程中浮点数精度的问题
- 记录关于JavaScript 浮点数运算的精度问题
- 浮点数精度问题
- 浮点数精度问题
- 浮点数精度问题
- C++ 字符串转换为浮点数时的精度问题
- 浮点数计算精度控制
- JAVA中浮点数的精度问题
- 浮点数精度的一点问题
- Java中浮点数的精度问题
- Java中浮点数的精度问题
- 购物车自动结算功能
- Android api23中删除HttpClient的相关类的解决方法
- HTML 排版与标签(二)
- 您能看出这个生成缩略图的方法有什么问题吗?
- 自然数各位数字求和(第2届第1题)
- 关于浮点数计算时的精度问题
- javascript 绑定事件 阻止冒泡和默认事件
- POJ 题目3009 Curling 2.0(DFS)
- 重构代码
- 牛人的体会慢慢理会
- sphinx入门篇之安装与基本使用
- python局部变量和全局变量
- 总结C++中三种关于"new"的使用方法
- TListbox中的item根据内容显示不同颜色的方法