JSValue计算数学公式

由JSValue 计算引起的bug

公司在做订货方面的东西,在下单界面需要根据公式计算数据结果。我们公司H老大做了一套处理方法,利用API种JSContext自带的evaluateScript方法。之前没多少项目有小数的处理,但是最近有一家客户,单款商品单价包含两位小数,出现如下情况:

  • 加法计算: (76.32) + (2.11) = 78.429999999

  • 乘法计算: (76.82) * 10 = 768.200000001

后面的小数位数 可能有一定的出入,但是大致可以看出波动是在0.00000001,或者-0.00000001的出入。

方法使用错误?或者是精度缺失?

我查看了代码,发现我们返回数据是用的方法竟然是JSValue的toString方法,这个方法会把真正的数据类型再转为String,强转数据有可能会造成数据丢失。

  • 我看API有toObject方法,就使用这个方法,但是返回的类型不是NSString了,我想按照类型再一个一个转,H领导说,为啥要那样做,不是有description吗?突然脑子反应过来,是呀,怎么把这个方法给遗忘了。

最后使用如下代码:

1
2
3
JSContext *context = [[JSContext alloc] init];
JSValue *value = [context evaluateScript:script];
return [[value toObject] description];

但是依然有一部分数据计算结果是错误的。

👶很不开森,很郁闷,这是为什么呢?

我朋友问了他们的技术总监,对方说,有可能在计算的过程中精确缺失掉了。

  • 突然想起,这个运算的处理是将字符串转为了数字,然后再计算的结果返回的,这么说倒是对的,然后就去查找JavaScript的计算处理,google js的精度丢失,发现还真有这么一个bug。
世界瞬间美好的不行不行滴,然后我就开始出各种馊主意

两种方法处理:

  • 固定精度法;
    计算数据的时候,直接调用toFixed(2),这样的形式保留两位小数。

  • 乘除位数法;
    计算数据的时候,直接把数据转为整数,再除以整数倍数,如(数据 * 100)/100。

结果呢?!!

😒心情好的不行不行的。然而下午的时候java方面同事说不行。

  • 第一种,固定精度,会造成精度后面的数据直接被丢弃,不进行四舍五入😓。

  • 第二种,乘除位数吧,加法的时候 还是有多位,他说搞不定搞不定,不行呀不行呀,怎么办呢?

那怎么办,凉拌!!

四舍五入呀,我就去摸索四舍五入了。

断点调试,在lldb时输入

1
po [[[JSContext alloc] init] evaluateScript:@"Math.round(76.8399999*100)/100"]

Bing🐶,出现了我想要的结果,然后就让他们修改传入公式,然后就在演示前十三分钟搞定了。

总结

精度这种东西还是要注意的,尤其是在强制转换数据的时候,有时候不小心,或者稍微大意就会造成这种错误,虽然swift里对于强转对象会有warning显示,但是还是自己严谨处理代码好些,避免类似的bug重现。

附上JAvaScriptCore.h 的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef JavaScriptCore_h
#define JavaScriptCore_h

#include <JavaScriptCore/JavaScript.h>
#include <JavaScriptCore/JSStringRefCF.h>

#if defined(__OBJC__) && JSC_OBJC_API_ENABLED

#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"

#endif

#endif