Comments 54
Интересная проблема, попробуйте с 'strictfp' в Java или с аналогичными опциями clang. Смущает только, что слишком большая разница, как для ошибки округления, я бы продолжил поиски)
Добавление strictfp к бенчмарк классу в Java ошибку не убрало. Не уверен как это можно включить для clang. В интернете я нашел что можно проверить std::numeric_limits::is_iec559 — «возвращает» 1.
strictfp на современных процессорах ни на что не влияет. Его даже убрать хотят из джавы вообще.
— Обновить ОС не помогло.
— Я попробовал запустить докер контейнер с убунту и собрать бинарник там. Внутри контейнера ошибка не повторилась! (проверил gdb что sse инструкции используются). Проверить на реальном live cd пока нет возможности так как на ноутбуке только usb-c порты :( Проверю на «чистом» линуксе как появится возможность.
Внутри контейнера ошибка не повторилась! (проверил gdb что sse инструкции используются).если не повторилась — значит точно не железо, а операционка.
Странно только почему (т.е. какая версия+билд ОС и какая версия фирмвари в ЦПУ)…
➜ ~ system_profiler SPSoftwareDataType
Software:
System Software Overview:
System Version: macOS 10.13.5 (17F77)
Kernel Version: Darwin 17.6.0
Boot Volume: Macintosh HD
Boot Mode: Normal
Computer Name: MacBook Pro (46)
User Name: Aleksei Kutuzov (alekseik)
Secure Virtual Memory: Enabled
System Integrity Protection: Enabled
Time since boot: 12:39
➜ ~ sysctl -a | grep machdep.cpu
machdep.cpu.max_basic: 22
machdep.cpu.max_ext: 2147483656
machdep.cpu.vendor: GenuineIntel
machdep.cpu.brand_string: Intel(R) Core(TM) i7-7567U CPU @ 3.50GHz
machdep.cpu.family: 6
machdep.cpu.model: 142
machdep.cpu.extmodel: 8
machdep.cpu.extfamily: 0
machdep.cpu.stepping: 9
machdep.cpu.feature_bits: 9221959987971750911
machdep.cpu.leaf7_feature_bits: 43804591
machdep.cpu.extfeature_bits: 1241984796928
machdep.cpu.signature: 526057
machdep.cpu.brand: 0
machdep.cpu.features: FPU VME DE PSE TSC MSR PAE MCE CX8 APIC SEP MTRR PGE MCA CMOV PAT PSE36 CLFSH DS ACPI MMX FXSR SSE SSE2 SS HTT TM PBE SSE3 PCLMULQDQ DTES64 MON DSCPL VMX EST TM2 SSSE3 FMA CX16 TPR PDCM SSE4.1 SSE4.2 x2APIC MOVBE POPCNT AES PCID XSAVE OSXSAVE SEGLIM64 TSCTMR AVX1.0 RDRAND F16C
machdep.cpu.leaf7_features: SMEP ERMS RDWRFSGS TSC_THREAD_OFFSET BMI1 AVX2 BMI2 INVPCID SMAP RDSEED ADX IPT SGX FPU_CSDS MPX CLFSOPT
machdep.cpu.extfeatures: SYSCALL XD 1GBPAGE EM64T LAHF LZCNT PREFETCHW RDTSCP TSCI
machdep.cpu.logical_per_package: 16
machdep.cpu.cores_per_package: 8
machdep.cpu.microcode_version: 132
machdep.cpu.processor_flag: 6
machdep.cpu.mwait.linesize_min: 64
machdep.cpu.mwait.linesize_max: 64
machdep.cpu.mwait.extensions: 3
machdep.cpu.mwait.sub_Cstates: 286531872
machdep.cpu.thermal.sensor: 1
machdep.cpu.thermal.dynamic_acceleration: 1
machdep.cpu.thermal.invariant_APIC_timer: 1
machdep.cpu.thermal.thresholds: 2
machdep.cpu.thermal.ACNT_MCNT: 1
machdep.cpu.thermal.core_power_limits: 1
machdep.cpu.thermal.fine_grain_clock_mod: 1
machdep.cpu.thermal.package_thermal_intr: 1
machdep.cpu.thermal.hardware_feedback: 0
machdep.cpu.thermal.energy_policy: 1
machdep.cpu.xsave.extended_state: 31 832 1088 0
machdep.cpu.xsave.extended_state1: 15 832 256 0
machdep.cpu.arch_perf.version: 4
machdep.cpu.arch_perf.number: 4
machdep.cpu.arch_perf.width: 48
machdep.cpu.arch_perf.events_number: 7
machdep.cpu.arch_perf.events: 0
machdep.cpu.arch_perf.fixed_number: 3
machdep.cpu.arch_perf.fixed_width: 48
machdep.cpu.cache.linesize: 64
machdep.cpu.cache.L2_associativity: 4
machdep.cpu.cache.size: 256
machdep.cpu.tlb.inst.large: 8
machdep.cpu.tlb.data.small: 64
machdep.cpu.tlb.data.small_level1: 64
Действительно странно. ОС вчера обновил до актуальной.
diskutil list
, чтобы на флешке не было EFI(ESP)-раздела, ибо он, похоже, и мешает стандартному Boot Menu «увидеть» устройство. Потому воспользуйтесь rEFInd, он у меня Debian обнаруживал всегда.А можно вывести результат приведения float к double, когда происходит ошибка?
Т.е. вместо:
float result = 1.0;
if (result != 1.0) {
error++;
...
}
Написать что то вроде:
float result = 1.0;
double resultd = result;
if (resultd != 1.0) {
error++;
std::cout << "(double)1.0f = " << resultd << std::endl;
...
}
Сравнение дабла: yadi.sk/i/1z-WK0H03Y8ota
Сравнение флоата: yadi.sk/i/bnn8n6tm3Y8oyk
А можно еще проверить, точно там 0.0 получается. Может cout просто выводит денормализованные значения как 0.
Можно заменить:
std::cout << "(double)1.0f = " << resultd << std::endl;
на
std::cout << "(double)1.0f = " << *(long long*)&resultd << std::endl;
yadi.sk/i/nVpyIvDJ3Y8qzs
А другие приложения, использующие floating point, не глючат на этой машине?
Еще интересно — по совету из комментов выше пробовал запускать тест внутри докер контейнера на убунту и ошибка не повторяется (хотя инструкции там те же).
Немного удивляет такое, так как прямое сравнение чисел с плавающей точкой — один из древнейших антипаттернов.
К сожалению, у меня на i7 (предыдущего поколения) с build 1.8.0_144 баг не повторяется (ни со строкой, ни с последними примерами). Но и java без изменения не даёт собрать ваш код: по умолчанию все дробные числа имеют тип double, поэтому строчка из последних примеров
float result = 1.0;
собирается только с явным указанием типа (без преобразования самим компилятором, иначе ошибка: «incompatible types: possible lossy conversion from double to float»). То есть так: float result = 1.0f;
Влияет ли это как-то на генерацию дальнейшего асм-кода ещё не смотрел.P.S. Об этом даже в вики сказано и есть аналогичные примеры:
float pi = 3.14f; // При использовании типа float требуется указывать суффикс f или F
float anotherPi = (float) 3.14; // Можно привести явно
bool compare() {
float val = 1.0;
return val != 1.0;
}
То автор его на с++ написал. Это не java
int a = 16777217 * 1.0f;
int b = 16777217 * 1.0;
printf("%d %d\n", a, b); //16777216 16777217
float val = 1.0;
будет делать преобразование double во float. Мне тоже первой пришла в голову мысль, что надо было бы = 1f
, но скорее всего компилятор и так возьмёт 1f
, а не будет делать явное преобразование double во float.Хотя для удобочитаемости — да, стоило б:
bool compare() {
float val = 1.0f;
return val != 1.0;
}
return val != 1.0f;
Но в принципе такое в коде — уже плохо.
Я бы наоборот, явно это выделил (забыл это сделать в прошлом комменте; хотя, возможно, автор специально так не сделал, потому что тогда какие-то другие эффекты всплывают?):
bool compare() {
float val = 1.0f;
return (double)val != 1.0;
}
А не связано ли это с CVE-2018-3665 и её патчем?
(float)3 != (double)3
. А о том, что (int)(double)f
, где float f = 3
регулярно возвращает не 3 (и даже не 2, что ещё хоть как-то можно было бы списать на неточность), а 0.И в таком случае действительно вариант занизить частоту, может быть такое, что какой-то процесс некорректно отрабатывает на предельных частотах в этом конкретном «камне», например, не выставляется верхний бит экспоненты double в регистре xmm0, из-за чего число в регистре становится «почти нулем», и потом округляется в ноль. Это могло бы объяснить, почему float -> int проходит без проблем — тот бит не задействуется при таком преобразовании (да и вроде бы код float->int использует xmm1, а не xmm0). Возможно, удастся отловить проблему, например, записью в xmm0 статического значения 3.0 из памяти, потом чтение его в два разных места в памяти и побитового сравнения прочитанных результатов между собой и с исходным значением.
Если, как тут пишут проблема в том, что (int)(double)(float)3 возвращает 0, то это, конечно, баг на каком то уровне — процессор или другой чип, драйвер, ОС, etc.
Однако, как уже отметили вопрос тут прежде всего по применимости float чисел к данной задаче.
Дело не в том, что float числа нужно сравнивать с применением epsilon, а в более фундаментальной вещи: зачем вообще в стандартной библиотеке делать реализацию метода, с использованием float, который работает с вещами, которые к float вообще не имеют отношения? Это похоже на хак с использованием каких-то особенностей float, который в редких кейсах приводит к неожиданным проблемам.
С float нужно работать только там, где входные и/или выходные данные float.
В .NET тоже есть похожие реализации с использованием float там, где можно обойтись целочисленными типами (DateTime, TimeSpan — внутри хранят количество тиков в long, часть операций реализована как сложение/вычитание long, а часть — через операции с double).
Видимо потому что множитель дробный. Можно обойтись fixed-point представлением, но это потребует умножения в широких интах, можно эмулировать через сдвиг и сложение, но зачем это всё, если есть флоаты? Только потому что у кого-то железо может глючить?
Видимо потому что множитель дробный.
Понадобился дробный множитель, и, естественно, для этого было выбран тип данных дробных чисел, а, точнее — тип данных с плавающей точкой?
Суть вопроса то в этом и заключается — зачем здесь дробный множитель, если есть в памяти строка из последовательности символов (UTF-16 или UTF-8 для java.lang.String? — кажется, первая), представленных целочисленными байтами, и нужно эти байты перекодировать так, чтобы они представляли ту же самую строку, но уже в другой кодировке?
Судя по всему, автор кода пытался таким образом уберечься от переполнения.
Выражение 2000000000*3
даст в результате 1705032704, в то время как (int)(2000000000*3.0)
даст 2147483647...
Выше я привёл хороший пример и даже угадал, вот наш автор. Он пытался решить проблему java.nio.BufferOverflowException при вызове getBytes(), когда строка имеет определенную длину (от 16777216 символов).
Есть ещё кое-что интересное, но не об этом.
«2000000000 не можем умножить на 3», т.к. в результате «String.getBytes() does not work on some strings larger than 16MB», давайте тогда приведем все это к double перед умножением.
Сорри, но это уровень студенческой лабораторной работы на тройку.
Уж лучше тогда использовать BigIngteger, хотя это оверхед, да и на момент выявления бага BigInteger в Java еще не было.
Если нужен был Workaround, то можно было б какой то свой Int128 (обертку над парой long) реализовать…
Во всяком случае, если посмотрим исходники JDK 8 по Unsigned-арифметике, там BigInteger вполне используется для борьбы с переполнением.
И наверняка можно было бы все решить проще и элегантнее — только не говорите, что надо было «быстро» — это ж библиотечная функция JDK, а не энтерпрайз-фичи по аджайлу и скраму.
На мой взгляд нормальным исправлением должно бы было быть изменение возвращаемого типа maxCharsPerByte() на целый, но видимо это невозможно по каким то причинам, поэтому приведение к double вполне корректное решение.
Тогда совсем печально.
Но позитив тут в том, что тут пример того, почему контракты библиотечных функций должны тщательно продумываться — ибо это влияет не только на продуктовый код, но на на другие библиотечные функции, и просто так это уже не вычистишь, пометив устаревшую функцию как deprecated.
2.8 GHz Intel Core i7
OS: 10.13.4 (17E202)
Memory: 16 GB 2133 MHz LPDDR3
Проблема не воспроизводится. Ни на C++ ни Java.
Проблема может быть конкретно в вашем ноутбуке.
Но проблема весьма занятна. Обычно если 0.1+0.2 != 0.3 то оно всегда так и не меняется от количества вызовов.
Жду продолжения, если найдете причину.
И программные способы залочить отдельные ядра (вдруг проблема в каком-то одном из)?
Как у меня сломался String.getBytes(UTF_8) и что я с этим делал