You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BuiltinOperators.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. /*
  2. Copyright (c) 2016 James Ahlborn
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package com.healthmarketscience.jackcess.impl.expr;
  14. import java.math.BigDecimal;
  15. import java.util.regex.Pattern;
  16. import com.healthmarketscience.jackcess.expr.EvalException;
  17. import com.healthmarketscience.jackcess.expr.LocaleContext;
  18. import com.healthmarketscience.jackcess.expr.Value;
  19. import com.healthmarketscience.jackcess.impl.NumberFormatter;
  20. import static com.healthmarketscience.jackcess.impl.expr.ValueSupport.*;
  21. /**
  22. *
  23. * @author James Ahlborn
  24. */
  25. public class BuiltinOperators
  26. {
  27. private static final String DIV_BY_ZERO = "/ by zero";
  28. private static final double MIN_INT = Integer.MIN_VALUE;
  29. private static final double MAX_INT = Integer.MAX_VALUE;
  30. private enum CoercionType {
  31. SIMPLE(true, true), GENERAL(false, true), COMPARE(false, false);
  32. final boolean _preferTemporal;
  33. final boolean _allowCoerceStringToNum;
  34. private CoercionType(boolean preferTemporal,
  35. boolean allowCoerceStringToNum) {
  36. _preferTemporal = preferTemporal;
  37. _allowCoerceStringToNum = allowCoerceStringToNum;
  38. }
  39. }
  40. private BuiltinOperators() {}
  41. // null propagation rules:
  42. // http://www.utteraccess.com/wiki/index.php/Nulls_And_Their_Behavior
  43. // https://theaccessbuddy.wordpress.com/2012/10/24/6-logical-operators-in-ms-access-that-you-must-know-operator-types-3-of-5/
  44. // - number ops
  45. // - comparison ops
  46. // - logical ops (some "special")
  47. // - And - can be false if one arg is false
  48. // - Or - can be true if one arg is true
  49. // - between, not, like, in
  50. // - *NOT* concal op '&'
  51. public static Value negate(LocaleContext ctx, Value param1) {
  52. if(param1.isNull()) {
  53. // null propagation
  54. return NULL_VAL;
  55. }
  56. Value.Type mathType = param1.getType();
  57. switch(mathType) {
  58. case DATE:
  59. case TIME:
  60. case DATE_TIME:
  61. // dates/times get converted to date doubles for arithmetic
  62. double result = -param1.getAsDouble(ctx);
  63. return toDateValue(ctx, mathType, result);
  64. case LONG:
  65. return toValue(-param1.getAsLongInt(ctx));
  66. case DOUBLE:
  67. return toValue(-param1.getAsDouble(ctx));
  68. case STRING:
  69. case BIG_DEC:
  70. return toValue(param1.getAsBigDecimal(ctx).negate(
  71. NumberFormatter.DEC_MATH_CONTEXT));
  72. default:
  73. throw new EvalException("Unexpected type " + mathType);
  74. }
  75. }
  76. public static Value add(LocaleContext ctx, Value param1, Value param2) {
  77. if(anyParamIsNull(param1, param2)) {
  78. // null propagation
  79. return NULL_VAL;
  80. }
  81. Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
  82. CoercionType.SIMPLE);
  83. switch(mathType) {
  84. case STRING:
  85. // string '+' is a null-propagation (handled above) concat
  86. return nonNullConcat(ctx, param1, param2);
  87. case DATE:
  88. case TIME:
  89. case DATE_TIME:
  90. // dates/times get converted to date doubles for arithmetic
  91. double result = param1.getAsDouble(ctx) + param2.getAsDouble(ctx);
  92. return toDateValue(ctx, mathType, result);
  93. case LONG:
  94. return toValue(param1.getAsLongInt(ctx) + param2.getAsLongInt(ctx));
  95. case DOUBLE:
  96. return toValue(param1.getAsDouble(ctx) + param2.getAsDouble(ctx));
  97. case BIG_DEC:
  98. return toValue(param1.getAsBigDecimal(ctx).add(
  99. param2.getAsBigDecimal(ctx),
  100. NumberFormatter.DEC_MATH_CONTEXT));
  101. default:
  102. throw new EvalException("Unexpected type " + mathType);
  103. }
  104. }
  105. public static Value subtract(LocaleContext ctx, Value param1, Value param2) {
  106. if(anyParamIsNull(param1, param2)) {
  107. // null propagation
  108. return NULL_VAL;
  109. }
  110. Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
  111. CoercionType.SIMPLE);
  112. switch(mathType) {
  113. // case STRING: break; unsupported
  114. case DATE:
  115. case TIME:
  116. case DATE_TIME:
  117. // dates/times get converted to date doubles for arithmetic
  118. double result = param1.getAsDouble(ctx) - param2.getAsDouble(ctx);
  119. return toDateValue(ctx, mathType, result);
  120. case LONG:
  121. return toValue(param1.getAsLongInt(ctx) - param2.getAsLongInt(ctx));
  122. case DOUBLE:
  123. return toValue(param1.getAsDouble(ctx) - param2.getAsDouble(ctx));
  124. case BIG_DEC:
  125. return toValue(param1.getAsBigDecimal(ctx).subtract(
  126. param2.getAsBigDecimal(ctx),
  127. NumberFormatter.DEC_MATH_CONTEXT));
  128. default:
  129. throw new EvalException("Unexpected type " + mathType);
  130. }
  131. }
  132. public static Value multiply(LocaleContext ctx, Value param1, Value param2) {
  133. if(anyParamIsNull(param1, param2)) {
  134. // null propagation
  135. return NULL_VAL;
  136. }
  137. Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
  138. CoercionType.GENERAL);
  139. switch(mathType) {
  140. // case STRING: break; unsupported
  141. // case DATE: break; promoted to double
  142. // case TIME: break; promoted to double
  143. // case DATE_TIME: break; promoted to double
  144. case LONG:
  145. return toValue(param1.getAsLongInt(ctx) * param2.getAsLongInt(ctx));
  146. case DOUBLE:
  147. return toValue(param1.getAsDouble(ctx) * param2.getAsDouble(ctx));
  148. case BIG_DEC:
  149. return toValue(param1.getAsBigDecimal(ctx).multiply(
  150. param2.getAsBigDecimal(ctx),
  151. NumberFormatter.DEC_MATH_CONTEXT));
  152. default:
  153. throw new EvalException("Unexpected type " + mathType);
  154. }
  155. }
  156. public static Value divide(LocaleContext ctx, Value param1, Value param2) {
  157. if(anyParamIsNull(param1, param2)) {
  158. // null propagation
  159. return NULL_VAL;
  160. }
  161. Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
  162. CoercionType.GENERAL);
  163. switch(mathType) {
  164. // case STRING: break; unsupported
  165. // case DATE: break; promoted to double
  166. // case TIME: break; promoted to double
  167. // case DATE_TIME: break; promoted to double
  168. case LONG:
  169. int lp1 = param1.getAsLongInt(ctx);
  170. int lp2 = param2.getAsLongInt(ctx);
  171. if((lp1 % lp2) == 0) {
  172. return toValue(lp1 / lp2);
  173. }
  174. return toValue((double)lp1 / (double)lp2);
  175. case DOUBLE:
  176. double d2 = param2.getAsDouble(ctx);
  177. if(d2 == 0.0d) {
  178. throw new ArithmeticException(DIV_BY_ZERO);
  179. }
  180. return toValue(param1.getAsDouble(ctx) / d2);
  181. case BIG_DEC:
  182. return toValue(divide(param1.getAsBigDecimal(ctx), param2.getAsBigDecimal(ctx)));
  183. default:
  184. throw new EvalException("Unexpected type " + mathType);
  185. }
  186. }
  187. public static Value intDivide(LocaleContext ctx, Value param1, Value param2) {
  188. if(anyParamIsNull(param1, param2)) {
  189. // null propagation
  190. return NULL_VAL;
  191. }
  192. Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
  193. CoercionType.GENERAL);
  194. if(mathType == Value.Type.STRING) {
  195. throw new EvalException("Unexpected type " + mathType);
  196. }
  197. return toValue(param1.getAsLongInt(ctx) / param2.getAsLongInt(ctx));
  198. }
  199. public static Value exp(LocaleContext ctx, Value param1, Value param2) {
  200. if(anyParamIsNull(param1, param2)) {
  201. // null propagation
  202. return NULL_VAL;
  203. }
  204. Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
  205. CoercionType.GENERAL);
  206. if(mathType == Value.Type.BIG_DEC) {
  207. // see if we can handle the limited options supported for BigDecimal
  208. // (must be a positive int exponent)
  209. try {
  210. BigDecimal result = param1.getAsBigDecimal(ctx).pow(
  211. param2.getAsBigDecimal(ctx).intValueExact(),
  212. NumberFormatter.DEC_MATH_CONTEXT);
  213. return toValue(result);
  214. } catch(ArithmeticException ae) {
  215. // fall back to general handling via doubles...
  216. }
  217. }
  218. // jdk only supports general pow() as doubles, let's go with that
  219. double result = Math.pow(param1.getAsDouble(ctx), param2.getAsDouble(ctx));
  220. // attempt to convert integral types back to integrals if possible
  221. if((mathType == Value.Type.LONG) && isIntegral(result)) {
  222. return toValue((int)result);
  223. }
  224. return toValue(result);
  225. }
  226. public static Value mod(LocaleContext ctx, Value param1, Value param2) {
  227. if(anyParamIsNull(param1, param2)) {
  228. // null propagation
  229. return NULL_VAL;
  230. }
  231. Value.Type mathType = getMathTypePrecedence(ctx, param1, param2,
  232. CoercionType.GENERAL);
  233. if(mathType == Value.Type.STRING) {
  234. throw new EvalException("Unexpected type " + mathType);
  235. }
  236. return toValue(param1.getAsLongInt(ctx) % param2.getAsLongInt(ctx));
  237. }
  238. public static Value concat(LocaleContext ctx, Value param1, Value param2) {
  239. // note, this op converts null to empty string
  240. if(param1.isNull()) {
  241. param1 = EMPTY_STR_VAL;
  242. }
  243. if(param2.isNull()) {
  244. param2 = EMPTY_STR_VAL;
  245. }
  246. return nonNullConcat(ctx, param1, param2);
  247. }
  248. private static Value nonNullConcat(
  249. LocaleContext ctx, Value param1, Value param2) {
  250. return toValue(param1.getAsString(ctx).concat(param2.getAsString(ctx)));
  251. }
  252. public static Value not(LocaleContext ctx, Value param1) {
  253. if(param1.isNull()) {
  254. // null propagation
  255. return NULL_VAL;
  256. }
  257. return toValue(!param1.getAsBoolean(ctx));
  258. }
  259. public static Value lessThan(LocaleContext ctx, Value param1, Value param2) {
  260. if(anyParamIsNull(param1, param2)) {
  261. // null propagation
  262. return NULL_VAL;
  263. }
  264. return toValue(nonNullCompareTo(ctx, param1, param2) < 0);
  265. }
  266. public static Value greaterThan(
  267. LocaleContext ctx, Value param1, Value param2) {
  268. if(anyParamIsNull(param1, param2)) {
  269. // null propagation
  270. return NULL_VAL;
  271. }
  272. return toValue(nonNullCompareTo(ctx, param1, param2) > 0);
  273. }
  274. public static Value lessThanEq(
  275. LocaleContext ctx, Value param1, Value param2) {
  276. if(anyParamIsNull(param1, param2)) {
  277. // null propagation
  278. return NULL_VAL;
  279. }
  280. return toValue(nonNullCompareTo(ctx, param1, param2) <= 0);
  281. }
  282. public static Value greaterThanEq(
  283. LocaleContext ctx, Value param1, Value param2) {
  284. if(anyParamIsNull(param1, param2)) {
  285. // null propagation
  286. return NULL_VAL;
  287. }
  288. return toValue(nonNullCompareTo(ctx, param1, param2) >= 0);
  289. }
  290. public static Value equals(LocaleContext ctx, Value param1, Value param2) {
  291. if(anyParamIsNull(param1, param2)) {
  292. // null propagation
  293. return NULL_VAL;
  294. }
  295. return toValue(nonNullCompareTo(ctx, param1, param2) == 0);
  296. }
  297. public static Value notEquals(LocaleContext ctx, Value param1, Value param2) {
  298. if(anyParamIsNull(param1, param2)) {
  299. // null propagation
  300. return NULL_VAL;
  301. }
  302. return toValue(nonNullCompareTo(ctx, param1, param2) != 0);
  303. }
  304. public static Value and(LocaleContext ctx, Value param1, Value param2) {
  305. // "and" uses short-circuit logic
  306. if(param1.isNull()) {
  307. return NULL_VAL;
  308. }
  309. boolean b1 = param1.getAsBoolean(ctx);
  310. if(!b1) {
  311. return FALSE_VAL;
  312. }
  313. if(param2.isNull()) {
  314. return NULL_VAL;
  315. }
  316. return toValue(param2.getAsBoolean(ctx));
  317. }
  318. public static Value or(LocaleContext ctx, Value param1, Value param2) {
  319. // "or" uses short-circuit logic
  320. if(param1.isNull()) {
  321. return NULL_VAL;
  322. }
  323. boolean b1 = param1.getAsBoolean(ctx);
  324. if(b1) {
  325. return TRUE_VAL;
  326. }
  327. if(param2.isNull()) {
  328. return NULL_VAL;
  329. }
  330. return toValue(param2.getAsBoolean(ctx));
  331. }
  332. public static Value eqv(LocaleContext ctx, Value param1, Value param2) {
  333. if(anyParamIsNull(param1, param2)) {
  334. // null propagation
  335. return NULL_VAL;
  336. }
  337. boolean b1 = param1.getAsBoolean(ctx);
  338. boolean b2 = param2.getAsBoolean(ctx);
  339. return toValue(b1 == b2);
  340. }
  341. public static Value xor(LocaleContext ctx, Value param1, Value param2) {
  342. if(anyParamIsNull(param1, param2)) {
  343. // null propagation
  344. return NULL_VAL;
  345. }
  346. boolean b1 = param1.getAsBoolean(ctx);
  347. boolean b2 = param2.getAsBoolean(ctx);
  348. return toValue(b1 ^ b2);
  349. }
  350. public static Value imp(LocaleContext ctx, Value param1, Value param2) {
  351. // "imp" uses short-circuit logic
  352. if(param1.isNull()) {
  353. if(param2.isNull() || !param2.getAsBoolean(ctx)) {
  354. // null propagation
  355. return NULL_VAL;
  356. }
  357. return TRUE_VAL;
  358. }
  359. boolean b1 = param1.getAsBoolean(ctx);
  360. if(!b1) {
  361. return TRUE_VAL;
  362. }
  363. if(param2.isNull()) {
  364. // null propagation
  365. return NULL_VAL;
  366. }
  367. return toValue(param2.getAsBoolean(ctx));
  368. }
  369. public static Value isNull(Value param1) {
  370. return toValue(param1.isNull());
  371. }
  372. public static Value isNotNull(Value param1) {
  373. return toValue(!param1.isNull());
  374. }
  375. public static Value like(LocaleContext ctx, Value param1, Pattern pattern) {
  376. if(param1.isNull()) {
  377. // null propagation
  378. return NULL_VAL;
  379. }
  380. return toValue(pattern.matcher(param1.getAsString(ctx)).matches());
  381. }
  382. public static Value notLike(
  383. LocaleContext ctx, Value param1, Pattern pattern) {
  384. return not(ctx, like(ctx, param1, pattern));
  385. }
  386. public static Value between(
  387. LocaleContext ctx, Value param1, Value param2, Value param3) {
  388. // null propagate any param. uses short circuit eval of params
  389. if(anyParamIsNull(param1, param2, param3)) {
  390. // null propagation
  391. return NULL_VAL;
  392. }
  393. // the between values can be in either order!?!
  394. Value min = param2;
  395. Value max = param3;
  396. Value gt = greaterThan(ctx, min, max);
  397. if(gt.getAsBoolean(ctx)) {
  398. min = param3;
  399. max = param2;
  400. }
  401. return and(ctx, greaterThanEq(ctx, param1, min), lessThanEq(ctx, param1, max));
  402. }
  403. public static Value notBetween(
  404. LocaleContext ctx, Value param1, Value param2, Value param3) {
  405. return not(ctx, between(ctx, param1, param2, param3));
  406. }
  407. public static Value in(LocaleContext ctx, Value param1, Value[] params) {
  408. // null propagate any param. uses short circuit eval of params
  409. if(param1.isNull()) {
  410. // null propagation
  411. return NULL_VAL;
  412. }
  413. for(Value val : params) {
  414. if(val.isNull()) {
  415. continue;
  416. }
  417. Value eq = equals(ctx, param1, val);
  418. if(eq.getAsBoolean(ctx)) {
  419. return TRUE_VAL;
  420. }
  421. }
  422. return FALSE_VAL;
  423. }
  424. public static Value notIn(LocaleContext ctx, Value param1, Value[] params) {
  425. return not(ctx, in(ctx, param1, params));
  426. }
  427. private static boolean anyParamIsNull(Value param1, Value param2) {
  428. return (param1.isNull() || param2.isNull());
  429. }
  430. private static boolean anyParamIsNull(Value param1, Value param2,
  431. Value param3) {
  432. return (param1.isNull() || param2.isNull() || param3.isNull());
  433. }
  434. protected static int nonNullCompareTo(
  435. LocaleContext ctx, Value param1, Value param2)
  436. {
  437. // note that comparison does not do string to num coercion
  438. Value.Type compareType = getMathTypePrecedence(ctx, param1, param2,
  439. CoercionType.COMPARE);
  440. switch(compareType) {
  441. case STRING:
  442. // string comparison is only valid if _both_ params are strings
  443. if(param1.getType() != param2.getType()) {
  444. throw new EvalException("Unexpected type " + compareType);
  445. }
  446. return param1.getAsString(ctx).compareToIgnoreCase(param2.getAsString(ctx));
  447. // case DATE: break; promoted to double
  448. // case TIME: break; promoted to double
  449. // case DATE_TIME: break; promoted to double
  450. case LONG:
  451. return param1.getAsLongInt(ctx).compareTo(param2.getAsLongInt(ctx));
  452. case DOUBLE:
  453. return param1.getAsDouble(ctx).compareTo(param2.getAsDouble(ctx));
  454. case BIG_DEC:
  455. return param1.getAsBigDecimal(ctx).compareTo(param2.getAsBigDecimal(ctx));
  456. default:
  457. throw new EvalException("Unexpected type " + compareType);
  458. }
  459. }
  460. private static Value.Type getMathTypePrecedence(
  461. LocaleContext ctx, Value param1, Value param2, CoercionType cType)
  462. {
  463. Value.Type t1 = param1.getType();
  464. Value.Type t2 = param2.getType();
  465. // note: for general math, date/time become double
  466. if(t1 == t2) {
  467. if(!cType._preferTemporal && t1.isTemporal()) {
  468. return t1.getPreferredNumericType();
  469. }
  470. return t1;
  471. }
  472. if((t1 == Value.Type.STRING) || (t2 == Value.Type.STRING)) {
  473. if(cType._allowCoerceStringToNum) {
  474. // see if this is mixed string/numeric and the string can be coerced
  475. // to a number
  476. Value.Type numericType = coerceStringToNumeric(
  477. ctx, param1, param2, cType);
  478. if(numericType != null) {
  479. // string can be coerced to number
  480. return numericType;
  481. }
  482. }
  483. // string always wins
  484. return Value.Type.STRING;
  485. }
  486. // for "simple" math, keep as date/times
  487. if(cType._preferTemporal &&
  488. (t1.isTemporal() || t2.isTemporal())) {
  489. return (t1.isTemporal() ?
  490. (t2.isTemporal() ?
  491. // for mixed temporal types, always go to date/time
  492. Value.Type.DATE_TIME : t1) :
  493. t2);
  494. }
  495. return getPreferredNumericType(t1.getPreferredNumericType(),
  496. t2.getPreferredNumericType());
  497. }
  498. private static Value.Type getPreferredNumericType(Value.Type t1, Value.Type t2)
  499. {
  500. // if both types are integral, choose "largest"
  501. if(t1.isIntegral() && t2.isIntegral()) {
  502. return max(t1, t2);
  503. }
  504. // choose largest relevant floating-point type
  505. return max(t1.getPreferredFPType(), t2.getPreferredFPType());
  506. }
  507. private static Value.Type coerceStringToNumeric(
  508. LocaleContext ctx, Value param1, Value param2, CoercionType cType) {
  509. Value.Type t1 = param1.getType();
  510. Value.Type t2 = param2.getType();
  511. Value.Type prefType = null;
  512. Value strParam = null;
  513. if(t1.isNumeric()) {
  514. prefType = t1;
  515. strParam = param2;
  516. } else if(t2.isNumeric()) {
  517. prefType = t2;
  518. strParam = param1;
  519. } else if(t1.isTemporal()) {
  520. prefType = (cType._preferTemporal ? t1 : t1.getPreferredNumericType());
  521. strParam = param2;
  522. } else if(t2.isTemporal()) {
  523. prefType = (cType._preferTemporal ? t2 : t2.getPreferredNumericType());
  524. strParam = param1;
  525. } else {
  526. // no numeric type involved
  527. return null;
  528. }
  529. try {
  530. // see if string can be coerced to a number
  531. strParam.getAsBigDecimal(ctx);
  532. if(prefType.isNumeric()) {
  533. // seems like when strings are coerced to numbers, they are usually
  534. // doubles, unless the current context is decimal
  535. prefType = ((prefType == Value.Type.BIG_DEC) ?
  536. Value.Type.BIG_DEC : Value.Type.DOUBLE);
  537. }
  538. return prefType;
  539. } catch(NumberFormatException ignored) {
  540. // not a number
  541. }
  542. return null;
  543. }
  544. private static Value.Type max(Value.Type t1, Value.Type t2) {
  545. return ((t1.compareTo(t2) > 0) ? t1 : t2);
  546. }
  547. static BigDecimal divide(BigDecimal num, BigDecimal denom) {
  548. return num.divide(denom, NumberFormatter.DEC_MATH_CONTEXT);
  549. }
  550. static boolean isIntegral(double d) {
  551. double id = Math.rint(d);
  552. return ((d == id) && (d >= MIN_INT) && (d <= MAX_INT) &&
  553. !Double.isInfinite(d) && !Double.isNaN(d));
  554. }
  555. }